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