TabControl.java revision f59ec877363eaf43118677f249008eddc7a9ce11
1/*
2 * Copyright (C) 2007 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
17package com.android.browser;
18
19import android.content.Context;
20import android.net.http.SslError;
21import android.os.Bundle;
22import android.os.Message;
23import android.util.Config;
24import android.util.Log;
25import android.view.Gravity;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.ViewGroup;
29import android.view.View.OnClickListener;
30import android.webkit.HttpAuthHandler;
31import android.webkit.JsPromptResult;
32import android.webkit.JsResult;
33import android.webkit.SslErrorHandler;
34import android.webkit.WebBackForwardList;
35import android.webkit.WebChromeClient;
36import android.webkit.WebHistoryItem;
37import android.webkit.WebView;
38import android.webkit.WebViewClient;
39import android.widget.FrameLayout;
40import android.widget.ImageButton;
41
42import java.io.File;
43import java.util.ArrayList;
44import java.util.Vector;
45
46class TabControl {
47    // Log Tag
48    private static final String LOGTAG = "TabControl";
49    // Maximum number of tabs.
50    static final int MAX_TABS = 8;
51    // Static instance of an empty callback.
52    private static final WebViewClient mEmptyClient =
53            new WebViewClient();
54    // Instance of BackgroundChromeClient for background tabs.
55    private final BackgroundChromeClient mBackgroundChromeClient =
56            new BackgroundChromeClient();
57    // Private array of WebViews that are used as tabs.
58    private ArrayList<Tab> mTabs = new ArrayList<Tab>(MAX_TABS);
59    // Queue of most recently viewed tabs.
60    private ArrayList<Tab> mTabQueue = new ArrayList<Tab>(MAX_TABS);
61    // Current position in mTabs.
62    private int mCurrentTab = -1;
63    // A private instance of BrowserActivity to interface with when adding and
64    // switching between tabs.
65    private final BrowserActivity mActivity;
66    // Inflation service for making subwindows.
67    private final LayoutInflater mInflateService;
68    // Subclass of WebViewClient used in subwindows to notify the main
69    // WebViewClient of certain WebView activities.
70    private class SubWindowClient extends WebViewClient {
71        // The main WebViewClient.
72        private final WebViewClient mClient;
73
74        SubWindowClient(WebViewClient client) {
75            mClient = client;
76        }
77        @Override
78        public void doUpdateVisitedHistory(WebView view, String url,
79                boolean isReload) {
80            mClient.doUpdateVisitedHistory(view, url, isReload);
81        }
82        @Override
83        public boolean shouldOverrideUrlLoading(WebView view, String url) {
84            return mClient.shouldOverrideUrlLoading(view, url);
85        }
86        @Override
87        public void onReceivedSslError(WebView view, SslErrorHandler handler,
88                SslError error) {
89            mClient.onReceivedSslError(view, handler, error);
90        }
91        @Override
92        public void onReceivedHttpAuthRequest(WebView view,
93                HttpAuthHandler handler, String host, String realm) {
94            mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
95        }
96        @Override
97        public void onFormResubmission(WebView view, Message dontResend,
98                Message resend) {
99            mClient.onFormResubmission(view, dontResend, resend);
100        }
101        @Override
102        public void onReceivedError(WebView view, int errorCode,
103                String description, String failingUrl) {
104            mClient.onReceivedError(view, errorCode, description, failingUrl);
105        }
106    }
107    // Subclass of WebChromeClient to display javascript dialogs.
108    private class SubWindowChromeClient extends WebChromeClient {
109        // This subwindow's tab.
110        private final Tab mTab;
111        // The main WebChromeClient.
112        private final WebChromeClient mClient;
113
114        SubWindowChromeClient(Tab t, WebChromeClient client) {
115            mTab = t;
116            mClient = client;
117        }
118        @Override
119        public void onProgressChanged(WebView view, int newProgress) {
120            mClient.onProgressChanged(view, newProgress);
121        }
122        @Override
123        public boolean onCreateWindow(WebView view, boolean dialog,
124                boolean userGesture, android.os.Message resultMsg) {
125            return mClient.onCreateWindow(view, dialog, userGesture, resultMsg);
126        }
127        @Override
128        public void onCloseWindow(WebView window) {
129            if (Config.DEBUG && window != mTab.mSubView) {
130                throw new AssertionError("Can't close the window");
131            }
132            mActivity.dismissSubWindow(mTab);
133        }
134    }
135    // Background WebChromeClient for focusing tabs
136    private class BackgroundChromeClient extends WebChromeClient {
137        @Override
138        public void onRequestFocus(WebView view) {
139            Tab t = getTabFromView(view);
140            if (t != getCurrentTab()) {
141                mActivity.showTab(t);
142            }
143        }
144    }
145
146    /**
147     * Private class for maintaining Tabs with a main WebView and a subwindow.
148     */
149    public class Tab {
150        // Main WebView
151        private WebView mMainView;
152        // Subwindow WebView
153        private WebView mSubView;
154        // Subwindow container
155        private View mSubViewContainer;
156        // Subwindow callback
157        private SubWindowClient mSubViewClient;
158        // Subwindow chrome callback
159        private SubWindowChromeClient mSubViewChromeClient;
160        // Saved bundle for when we are running low on memory. It contains the
161        // information needed to restore the WebView if the user goes back to
162        // the tab.
163        private Bundle mSavedState;
164        // Extra saved information for displaying the tab in the picker.
165        private String mUrl;
166        private String mTitle;
167
168        // Parent Tab. This is the Tab that created this Tab, or null
169        // if the Tab was created by the UI
170        private Tab mParentTab;
171        // Tab that constructed by this Tab. This is used when this
172        // Tab is destroyed, it clears all mParentTab values in the
173        // children.
174        private Vector<Tab> mChildTabs;
175
176        private Boolean mCloseOnExit;
177        // Application identifier used to find tabs that another application
178        // wants to reuse.
179        private String mAppId;
180        // Keep the original url around to avoid killing the old WebView if the
181        // url has not changed.
182        private String mOriginalUrl;
183
184        // Construct a new tab
185        private Tab(WebView w, boolean closeOnExit, String appId, String url) {
186            mMainView = w;
187            mCloseOnExit = closeOnExit;
188            mAppId = appId;
189            mOriginalUrl = url;
190        }
191
192        /**
193         * Return the top window of this tab; either the subwindow if it is not
194         * null or the main window.
195         * @return The top window of this tab.
196         */
197        public WebView getTopWindow() {
198            if (mSubView != null) {
199                return mSubView;
200            }
201            return mMainView;
202        }
203
204        /**
205         * Return the main window of this tab. Note: if a tab is freed in the
206         * background, this can return null. It is only guaranteed to be
207         * non-null for the current tab.
208         * @return The main WebView of this tab.
209         */
210        public WebView getWebView() {
211            return mMainView;
212        }
213
214        /**
215         * Return the subwindow of this tab or null if there is no subwindow.
216         * @return The subwindow of this tab or null.
217         */
218        public WebView getSubWebView() {
219            return mSubView;
220        }
221
222        /**
223         * Return the subwindow container of this tab or null if there is no
224         * subwindow.
225         * @return The subwindow's container View.
226         */
227        public View getSubWebViewContainer() {
228            return mSubViewContainer;
229        }
230
231        /**
232         * Get the url of this tab.  Valid after calling populatePickerData, but
233         * before calling wipePickerData, or if the webview has been destroyed.
234         *
235         * @return The WebView's url or null.
236         */
237        public String getUrl() {
238            return mUrl;
239        }
240
241        /**
242         * Get the title of this tab.  Valid after calling populatePickerData,
243         * but before calling wipePickerData, or if the webview has been
244         * destroyed.  If the url has no title, use the url instead.
245         *
246         * @return The WebView's title (or url) or null.
247         */
248        public String getTitle() {
249            return mTitle;
250        }
251
252        private void setParentTab(Tab parent) {
253            mParentTab = parent;
254            // This tab may have been freed due to low memory. If that is the
255            // case, the parent tab index is already saved. If we are changing
256            // that index (most likely due to removing the parent tab) we must
257            // update the parent tab index in the saved Bundle.
258            if (mSavedState != null) {
259                if (parent == null) {
260                    mSavedState.remove(PARENTTAB);
261                } else {
262                    mSavedState.putInt(PARENTTAB, getTabIndex(parent));
263                }
264            }
265        }
266
267        /**
268         * When a Tab is created through the content of another Tab, then
269         * we associate the Tabs.
270         * @param child the Tab that was created from this Tab
271         */
272        public void addChildTab(Tab child) {
273            if (mChildTabs == null) {
274                mChildTabs = new Vector<Tab>();
275            }
276            mChildTabs.add(child);
277            child.setParentTab(this);
278        }
279
280        private void removeFromTree() {
281            // detach the children
282            if (mChildTabs != null) {
283                for(Tab t : mChildTabs) {
284                    t.setParentTab(null);
285                }
286            }
287
288            // Find myself in my parent list
289            if (mParentTab != null) {
290                mParentTab.mChildTabs.remove(this);
291            }
292        }
293
294        /**
295         * If this Tab was created through another Tab, then this method
296         * returns that Tab.
297         * @return the Tab parent or null
298         */
299        public Tab getParentTab() {
300            return mParentTab;
301        }
302
303        /**
304         * Return whether this tab should be closed when it is backing out of
305         * the first page.
306         * @return TRUE if this tab should be closed when exit.
307         */
308        public boolean closeOnExit() {
309            return mCloseOnExit;
310        }
311    };
312
313    // Directory to store thumbnails for each WebView.
314    private final File mThumbnailDir;
315
316    /**
317     * Construct a new TabControl object that interfaces with the given
318     * BrowserActivity instance.
319     * @param activity A BrowserActivity instance that TabControl will interface
320     *                 with.
321     */
322    TabControl(BrowserActivity activity) {
323        mActivity = activity;
324        mInflateService =
325                ((LayoutInflater) activity.getSystemService(
326                        Context.LAYOUT_INFLATER_SERVICE));
327        mThumbnailDir = activity.getDir("thumbnails", 0);
328    }
329
330    File getThumbnailDir() {
331        return mThumbnailDir;
332    }
333
334    BrowserActivity getBrowserActivity() {
335        return mActivity;
336    }
337
338    /**
339     * Return the current tab's main WebView. This will always return the main
340     * WebView for a given tab and not a subwindow.
341     * @return The current tab's WebView.
342     */
343    WebView getCurrentWebView() {
344        Tab t = getTab(mCurrentTab);
345        if (t == null) {
346            return null;
347        }
348        return t.mMainView;
349    }
350
351    /**
352     * Return the current tab's top-level WebView. This can return a subwindow
353     * if one exists.
354     * @return The top-level WebView of the current tab.
355     */
356    WebView getCurrentTopWebView() {
357        Tab t = getTab(mCurrentTab);
358        if (t == null) {
359            return null;
360        }
361        return t.mSubView != null ? t.mSubView : t.mMainView;
362    }
363
364    /**
365     * Return the current tab's subwindow if it exists.
366     * @return The subwindow of the current tab or null if it doesn't exist.
367     */
368    WebView getCurrentSubWindow() {
369        Tab t = getTab(mCurrentTab);
370        if (t == null) {
371            return null;
372        }
373        return t.mSubView;
374    }
375
376    /**
377     * Return the tab at the specified index.
378     * @return The Tab for the specified index or null if the tab does not
379     *         exist.
380     */
381    Tab getTab(int index) {
382        if (index >= 0 && index < mTabs.size()) {
383            return mTabs.get(index);
384        }
385        return null;
386    }
387
388    /**
389     * Return the current tab.
390     * @return The current tab.
391     */
392    Tab getCurrentTab() {
393        return getTab(mCurrentTab);
394    }
395
396    /**
397     * Return the current tab index.
398     * @return The current tab index
399     */
400    int getCurrentIndex() {
401        return mCurrentTab;
402    }
403
404    /**
405     * Given a Tab, find it's index
406     * @param Tab to find
407     * @return index of Tab or -1 if not found
408     */
409    int getTabIndex(Tab tab) {
410        return mTabs.indexOf(tab);
411    }
412
413    /**
414     * Create a new tab.
415     * @return The newly createTab or null if we have reached the maximum
416     *         number of open tabs.
417     */
418    Tab createNewTab(boolean closeOnExit, String appId, String url) {
419        int size = mTabs.size();
420        // Return false if we have maxed out on tabs
421        if (MAX_TABS == size) {
422            return null;
423        }
424        final WebView w = createNewWebView();
425        // Create a new tab and add it to the tab list
426        Tab t = new Tab(w, closeOnExit, appId, url);
427        mTabs.add(t);
428        // Initially put the tab in the background.
429        putTabInBackground(t);
430        return t;
431    }
432
433    /**
434     * Create a new tab with default values for closeOnExit(false),
435     * appId(null), and url(null).
436     */
437    Tab createNewTab() {
438        return createNewTab(false, null, null);
439    }
440
441    /**
442     * Remove the tab from the list. If the tab is the current tab shown, the
443     * last created tab will be shown.
444     * @param t The tab to be removed.
445     */
446    boolean removeTab(Tab t) {
447        if (t == null) {
448            return false;
449        }
450        // Only remove the tab if it is the current one.
451        if (getCurrentTab() == t) {
452            putTabInBackground(t);
453        }
454
455        // Only destroy the WebView if it still exists.
456        if (t.mMainView != null) {
457            // Take down the sub window.
458            dismissSubWindow(t);
459            // Remove the WebView's settings from the BrowserSettings list of
460            // observers.
461            BrowserSettings.getInstance().deleteObserver(
462                    t.mMainView.getSettings());
463            // Destroy the main view and subview
464            t.mMainView.destroy();
465            t.mMainView = null;
466        }
467        // clear it's references to parent and children
468        t.removeFromTree();
469
470        // Remove it from our list of tabs.
471        mTabs.remove(t);
472
473        // The tab indices have shifted, update all the saved state so we point
474        // to the correct index.
475        for (Tab tab : mTabs) {
476            if (tab.mChildTabs != null) {
477                for (Tab child : tab.mChildTabs) {
478                    child.setParentTab(tab);
479                }
480            }
481        }
482
483
484        // This tab may have been pushed in to the background and then closed.
485        // If the saved state contains a picture file, delete the file.
486        if (t.mSavedState != null) {
487            if (t.mSavedState.containsKey("picture")) {
488                new File(t.mSavedState.getString("picture")).delete();
489            }
490        }
491
492        // Remove it from the queue of viewed tabs.
493        mTabQueue.remove(t);
494        mCurrentTab = -1;
495        return true;
496    }
497
498    /**
499     * Clear the back/forward list for all the current tabs.
500     */
501    void clearHistory() {
502        int size = getTabCount();
503        for (int i = 0; i < size; i++) {
504            Tab t = mTabs.get(i);
505            // TODO: if a tab is freed due to low memory, its history is not
506            // cleared here.
507            if (t.mMainView != null) {
508                t.mMainView.clearHistory();
509            }
510            if (t.mSubView != null) {
511                t.mSubView.clearHistory();
512            }
513        }
514    }
515
516    /**
517     * Destroy all the tabs and subwindows
518     */
519    void destroy() {
520        BrowserSettings s = BrowserSettings.getInstance();
521        for (Tab t : mTabs) {
522            if (t.mMainView != null) {
523                dismissSubWindow(t);
524                s.deleteObserver(t.mMainView.getSettings());
525                t.mMainView.destroy();
526                t.mMainView = null;
527            }
528        }
529        mTabs.clear();
530        mTabQueue.clear();
531    }
532
533    /**
534     * Returns the number of tabs created.
535     * @return The number of tabs created.
536     */
537    int getTabCount() {
538        return mTabs.size();
539    }
540
541    // Used for saving and restoring each Tab
542    private static final String WEBVIEW = "webview";
543    private static final String NUMTABS = "numTabs";
544    private static final String CURRTAB = "currentTab";
545    private static final String CURRURL = "currentUrl";
546    private static final String CURRTITLE = "currentTitle";
547    private static final String CLOSEONEXIT = "closeonexit";
548    private static final String PARENTTAB = "parentTab";
549    private static final String APPID = "appid";
550    private static final String ORIGINALURL = "originalUrl";
551
552    /**
553     * Save the state of all the Tabs.
554     * @param outState The Bundle to save the state to.
555     */
556    void saveState(Bundle outState) {
557        final int numTabs = getTabCount();
558        outState.putInt(NUMTABS, numTabs);
559        final int index = getCurrentIndex();
560        outState.putInt(CURRTAB, (index >= 0 && index < numTabs) ? index : 0);
561        for (int i = 0; i < numTabs; i++) {
562            final Tab t = getTab(i);
563            if (saveState(t)) {
564                outState.putBundle(WEBVIEW + i, t.mSavedState);
565            }
566        }
567    }
568
569    /**
570     * Restore the state of all the tabs.
571     * @param inState The saved state of all the tabs.
572     * @return True if there were previous tabs that were restored. False if
573     *         there was no saved state or restoring the state failed.
574     */
575    boolean restoreState(Bundle inState) {
576        final int numTabs = (inState == null)
577                ? -1 : inState.getInt(NUMTABS, -1);
578        if (numTabs == -1) {
579            return false;
580        } else {
581            final int currentTab = inState.getInt(CURRTAB, -1);
582            for (int i = 0; i < numTabs; i++) {
583                if (i == currentTab) {
584                    Tab t = createNewTab();
585                    // Me must set the current tab before restoring the state
586                    // so that all the client classes are set.
587                    setCurrentTab(t);
588                    if (!restoreState(inState.getBundle(WEBVIEW + i), t)) {
589                        Log.w(LOGTAG, "Fail in restoreState, load home page.");
590                        t.mMainView.loadUrl(BrowserSettings.getInstance()
591                                .getHomePage());
592                    }
593                } else {
594                    // Create a new tab and don't restore the state yet, add it
595                    // to the tab list
596                    Tab t = new Tab(null, false, null, null);
597                    t.mSavedState = inState.getBundle(WEBVIEW + i);
598                    if (t.mSavedState != null) {
599                        t.mUrl = t.mSavedState.getString(CURRURL);
600                        t.mTitle = t.mSavedState.getString(CURRTITLE);
601                        // Need to maintain the app id and original url so we
602                        // can possibly reuse this tab.
603                        t.mAppId = t.mSavedState.getString(APPID);
604                        t.mOriginalUrl = t.mSavedState.getString(ORIGINALURL);
605                    }
606                    mTabs.add(t);
607                    mTabQueue.add(t);
608                }
609            }
610            // Rebuild the tree of tabs. Do this after all tabs have been
611            // created/restored so that the parent tab exists.
612            for (int i = 0; i < numTabs; i++) {
613                final Bundle b = inState.getBundle(WEBVIEW + i);
614                final Tab t = getTab(i);
615                if (b != null && t != null) {
616                    final int parentIndex = b.getInt(PARENTTAB, -1);
617                    if (parentIndex != -1) {
618                        final Tab parent = getTab(parentIndex);
619                        if (parent != null) {
620                            parent.addChildTab(t);
621                        }
622                    }
623                }
624            }
625        }
626        return true;
627    }
628
629    /**
630     * Free the memory in this order, 1) free the background tab; 2) free the
631     * WebView cache;
632     */
633    void freeMemory() {
634        // free the least frequently used background tab
635        Tab t = getLeastUsedTab();
636        if (t != null) {
637            Log.w(LOGTAG, "Free a tab in the browser");
638            freeTab(t);
639            // force a gc
640            System.gc();
641            return;
642        }
643
644        // free the WebView cache
645        Log.w(LOGTAG, "Free WebView cache");
646        WebView view = getCurrentWebView();
647        if (view != null) {
648            view.clearCache(false);
649        }
650        // force a gc
651        System.gc();
652    }
653
654    private Tab getLeastUsedTab() {
655        // Don't do anything if we only have 1 tab.
656        if (getTabCount() == 1) {
657            return null;
658        }
659
660        // Rip through the queue starting at the beginning and teardown the
661        // next available tab.
662        Tab t = null;
663        int i = 0;
664        final int queueSize = mTabQueue.size();
665        if (queueSize == 0) {
666            return null;
667        }
668        do {
669            t = mTabQueue.get(i++);
670        } while (i < queueSize && t != null && t.mMainView == null);
671
672        // Don't do anything if the last remaining tab is the current one.
673        if (t == getCurrentTab()) {
674            return null;
675        }
676
677        return t;
678    }
679
680    private void freeTab(Tab t) {
681        // Store the WebView's state.
682        saveState(t);
683
684        // Tear down the tab.
685        dismissSubWindow(t);
686        // Remove the WebView's settings from the BrowserSettings list of
687        // observers.
688        BrowserSettings.getInstance().deleteObserver(t.mMainView.getSettings());
689        t.mMainView.destroy();
690        t.mMainView = null;
691    }
692
693    /**
694     * Create a new subwindow unless a subwindow already exists.
695     * @return True if a new subwindow was created. False if one already exists.
696     */
697    void createSubWindow() {
698        Tab t = getTab(mCurrentTab);
699        if (t != null && t.mSubView == null) {
700            final View v = mInflateService.inflate(R.layout.browser_subwindow, null);
701            final WebView w = (WebView) v.findViewById(R.id.webview);
702            w.setMapTrackballToArrowKeys(false); // use trackball directly
703            final SubWindowClient subClient =
704                    new SubWindowClient(mActivity.getWebViewClient());
705            final SubWindowChromeClient subChromeClient =
706                    new SubWindowChromeClient(t,
707                            mActivity.getWebChromeClient());
708            w.setWebViewClient(subClient);
709            w.setWebChromeClient(subChromeClient);
710            w.setDownloadListener(mActivity);
711            w.setOnCreateContextMenuListener(mActivity);
712            final BrowserSettings s = BrowserSettings.getInstance();
713            s.addObserver(w.getSettings()).update(s, null);
714            t.mSubView = w;
715            t.mSubViewClient = subClient;
716            t.mSubViewChromeClient = subChromeClient;
717            // FIXME: I really hate having to know the name of the view
718            // containing the webview.
719            t.mSubViewContainer = v.findViewById(R.id.subwindow_container);
720            final ImageButton cancel =
721                    (ImageButton) v.findViewById(R.id.subwindow_close);
722            cancel.setOnClickListener(new OnClickListener() {
723                    public void onClick(View v) {
724                        subChromeClient.onCloseWindow(w);
725                    }
726                });
727        }
728    }
729
730    /**
731     * Show the tab that contains the given WebView.
732     * @param view The WebView used to find the tab.
733     */
734    Tab getTabFromView(WebView view) {
735        final int size = getTabCount();
736        for (int i = 0; i < size; i++) {
737            final Tab t = getTab(i);
738            if (t.mSubView == view || t.mMainView == view) {
739                return t;
740            }
741        }
742        return null;
743    }
744
745    /**
746     * Return the tab with the matching application id.
747     * @param id The application identifier.
748     */
749    Tab getTabFromId(String id) {
750        if (id == null) {
751            return null;
752        }
753        final int size = getTabCount();
754        for (int i = 0; i < size; i++) {
755            final Tab t = getTab(i);
756            if (id.equals(t.mAppId)) {
757                return t;
758            }
759        }
760        return null;
761    }
762
763    /**
764     * Recreate the main WebView of the given tab. Returns true if the WebView
765     * was deleted.
766     */
767    boolean recreateWebView(Tab t, String url) {
768        final WebView w = t.mMainView;
769        if (w != null) {
770            if (url != null && url.equals(t.mOriginalUrl)) {
771                // The original url matches the current url. Just go back to the
772                // first history item so we can load it faster than if we
773                // rebuilt the WebView.
774                final WebBackForwardList list = w.copyBackForwardList();
775                if (list != null) {
776                    w.goBackOrForward(-list.getCurrentIndex());
777                    w.clearHistory(); // maintains the current page.
778                    return false;
779                }
780            }
781            // Remove the settings object from the global settings and destroy
782            // the WebView.
783            BrowserSettings.getInstance().deleteObserver(
784                    t.mMainView.getSettings());
785            t.mMainView.destroy();
786        }
787        // Create a new WebView. If this tab is the current tab, we need to put
788        // back all the clients so force it to be the current tab.
789        t.mMainView = createNewWebView();
790        if (getCurrentTab() == t) {
791            setCurrentTab(t, true);
792        }
793        // Clear the saved state except for the app id and close-on-exit
794        // values.
795        t.mSavedState = null;
796        t.mUrl = null;
797        t.mTitle = null;
798        // Save the new url in order to avoid deleting the WebView.
799        t.mOriginalUrl = url;
800        return true;
801    }
802
803    /**
804     * Creates a new WebView and registers it with the global settings.
805     */
806    private WebView createNewWebView() {
807        // Create a new WebView
808        WebView w = new WebView(mActivity);
809        w.setMapTrackballToArrowKeys(false); // use trackball directly
810        // Add this WebView to the settings observer list and update the
811        // settings
812        final BrowserSettings s = BrowserSettings.getInstance();
813        s.addObserver(w.getSettings()).update(s, null);
814        return w;
815    }
816
817    /**
818     * Put the current tab in the background and set newTab as the current tab.
819     * @param newTab The new tab. If newTab is null, the current tab is not
820     *               set.
821     */
822    boolean setCurrentTab(Tab newTab) {
823        return setCurrentTab(newTab, false);
824    }
825
826    /**
827     * If force is true, this method skips the check for newTab == current.
828     */
829    private boolean setCurrentTab(Tab newTab, boolean force) {
830        Tab current = getTab(mCurrentTab);
831        if (current == newTab && !force) {
832            return true;
833        }
834        if (current != null) {
835            // Remove the current WebView and the container of the subwindow
836            putTabInBackground(current);
837        }
838
839        if (newTab == null) {
840            return false;
841        }
842
843        // Move the newTab to the end of the queue
844        int index = mTabQueue.indexOf(newTab);
845        if (index != -1) {
846            mTabQueue.remove(index);
847        }
848        mTabQueue.add(newTab);
849
850        WebView mainView;
851        WebView subView;
852
853        // Display the new current tab
854        mCurrentTab = mTabs.indexOf(newTab);
855        mainView = newTab.mMainView;
856        boolean needRestore = (mainView == null);
857        if (needRestore) {
858            // Same work as in createNewTab() except don't do new Tab()
859            newTab.mMainView = mainView = createNewWebView();
860        }
861        mainView.setWebViewClient(mActivity.getWebViewClient());
862        mainView.setWebChromeClient(mActivity.getWebChromeClient());
863        mainView.setOnCreateContextMenuListener(mActivity);
864        mainView.setDownloadListener(mActivity);
865        // Add the subwindow if it exists
866        if (newTab.mSubViewContainer != null) {
867            subView = newTab.mSubView;
868            subView.setWebViewClient(newTab.mSubViewClient);
869            subView.setWebChromeClient(newTab.mSubViewChromeClient);
870            subView.setOnCreateContextMenuListener(mActivity);
871            subView.setDownloadListener(mActivity);
872        }
873        if (needRestore) {
874            // Have to finish setCurrentTab work before calling restoreState
875            if (!restoreState(newTab.mSavedState, newTab)) {
876                mainView.loadUrl(BrowserSettings.getInstance().getHomePage());
877            }
878        }
879        return true;
880    }
881
882    /*
883     * Put the tab in the background using all the empty/background clients.
884     */
885    private void putTabInBackground(Tab t) {
886        WebView mainView = t.mMainView;
887        // Set an empty callback so that default actions are not triggered.
888        mainView.setWebViewClient(mEmptyClient);
889        mainView.setWebChromeClient(mBackgroundChromeClient);
890        mainView.setOnCreateContextMenuListener(null);
891        // Leave the DownloadManager attached so that downloads can start in
892        // a non-active window. This can happen when going to a site that does
893        // a redirect after a period of time. The user could have switched to
894        // another tab while waiting for the download to start.
895        mainView.setDownloadListener(mActivity);
896        WebView subView = t.mSubView;
897        if (subView != null) {
898            // Set an empty callback so that default actions are not triggered.
899            subView.setWebViewClient(mEmptyClient);
900            subView.setWebChromeClient(mBackgroundChromeClient);
901            subView.setOnCreateContextMenuListener(null);
902            subView.setDownloadListener(mActivity);
903        }
904    }
905
906    /*
907     * Dismiss the subwindow for the given tab.
908     */
909    void dismissSubWindow(Tab t) {
910        if (t != null && t.mSubView != null) {
911            BrowserSettings.getInstance().deleteObserver(
912                    t.mSubView.getSettings());
913            t.mSubView.destroy();
914            t.mSubView = null;
915            t.mSubViewContainer = null;
916        }
917    }
918
919    /**
920     * Ensure that Tab t has a title, url, and favicon.
921     * @param  t   Tab to populate.
922     */
923    /* package */ void populatePickerData(Tab t) {
924        if (t == null || t.mMainView == null) {
925            return;
926        }
927        // FIXME: The only place we cared about subwindow was for
928        // bookmarking (i.e. not when saving state). Was this deliberate?
929        final WebBackForwardList list = t.mMainView.copyBackForwardList();
930        final WebHistoryItem item =
931                list != null ? list.getCurrentItem() : null;
932        populatePickerData(t, item);
933    }
934
935    // Populate the picker data
936    private void populatePickerData(Tab t, WebHistoryItem item) {
937        if (item != null) {
938            t.mUrl = item.getUrl();
939            t.mTitle = item.getTitle();
940            if (t.mTitle == null) {
941                t.mTitle = t.mUrl;
942            }
943        }
944    }
945
946    /**
947     * Clean up the data for all tabs.
948     */
949    /* package */ void wipeAllPickerData() {
950        int size = getTabCount();
951        for (int i = 0; i < size; i++) {
952            final Tab t = getTab(i);
953            if (t != null && t.mSavedState == null) {
954                t.mUrl = null;
955                t.mTitle = null;
956            }
957        }
958    }
959
960    /*
961     * Save the state for an individual tab.
962     */
963    private boolean saveState(Tab t) {
964        if (t != null) {
965            final WebView w = t.mMainView;
966            // If the WebView is null it means we ran low on memory and we
967            // already stored the saved state in mSavedState.
968            if (w == null) {
969                return true;
970            }
971            final Bundle b = new Bundle();
972            final WebBackForwardList list = w.saveState(b);
973            if (list != null) {
974                final File f = new File(mThumbnailDir, w.hashCode()
975                        + "_pic.save");
976                if (w.savePicture(b, f)) {
977                    b.putString("picture", f.getPath());
978                }
979            }
980
981            // Store some extra info for displaying the tab in the picker.
982            final WebHistoryItem item =
983                    list != null ? list.getCurrentItem() : null;
984            populatePickerData(t, item);
985            if (t.mUrl != null) {
986                b.putString(CURRURL, t.mUrl);
987            }
988            if (t.mTitle != null) {
989                b.putString(CURRTITLE, t.mTitle);
990            }
991            b.putBoolean(CLOSEONEXIT, t.mCloseOnExit);
992            if (t.mAppId != null) {
993                b.putString(APPID, t.mAppId);
994            }
995            if (t.mOriginalUrl != null) {
996                b.putString(ORIGINALURL, t.mOriginalUrl);
997            }
998
999            // Remember the parent tab so the relationship can be restored.
1000            if (t.mParentTab != null) {
1001                b.putInt(PARENTTAB, getTabIndex(t.mParentTab));
1002            }
1003
1004            // Remember the saved state.
1005            t.mSavedState = b;
1006            return true;
1007        }
1008        return false;
1009    }
1010
1011    /*
1012     * Restore the state of the tab.
1013     */
1014    private boolean restoreState(Bundle b, Tab t) {
1015        if (b == null) {
1016            return false;
1017        }
1018        // Restore the internal state even if the WebView fails to restore.
1019        // This will maintain the app id, original url and close-on-exit values.
1020        t.mSavedState = null;
1021        t.mUrl = null;
1022        t.mTitle = null;
1023        t.mCloseOnExit = b.getBoolean(CLOSEONEXIT);
1024        t.mAppId = b.getString(APPID);
1025        t.mOriginalUrl = b.getString(ORIGINALURL);
1026
1027        final WebView w = t.mMainView;
1028        final WebBackForwardList list = w.restoreState(b);
1029        if (list == null) {
1030            return false;
1031        }
1032        if (b.containsKey("picture")) {
1033            final File f = new File(b.getString("picture"));
1034            w.restorePicture(b, f);
1035            f.delete();
1036        }
1037        return true;
1038    }
1039}
1040