OSDN Git Service

Deprecate fill_parent and introduce match_parent.
[android-x86/packages-apps-Browser.git] / src / com / android / browser / TabControl.java
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
17 package com.android.browser;
18
19 import android.os.Bundle;
20 import android.util.Log;
21 import android.view.View;
22 import android.webkit.WebBackForwardList;
23 import android.webkit.WebView;
24
25 import java.io.File;
26 import java.util.ArrayList;
27 import java.util.Vector;
28
29 class TabControl {
30     // Log Tag
31     private static final String LOGTAG = "TabControl";
32     // Maximum number of tabs.
33     private static final int MAX_TABS = 8;
34     // Private array of WebViews that are used as tabs.
35     private ArrayList<Tab> mTabs = new ArrayList<Tab>(MAX_TABS);
36     // Queue of most recently viewed tabs.
37     private ArrayList<Tab> mTabQueue = new ArrayList<Tab>(MAX_TABS);
38     // Current position in mTabs.
39     private int mCurrentTab = -1;
40     // A private instance of BrowserActivity to interface with when adding and
41     // switching between tabs.
42     private final BrowserActivity mActivity;
43     // Directory to store thumbnails for each WebView.
44     private final File mThumbnailDir;
45
46     /**
47      * Construct a new TabControl object that interfaces with the given
48      * BrowserActivity instance.
49      * @param activity A BrowserActivity instance that TabControl will interface
50      *                 with.
51      */
52     TabControl(BrowserActivity activity) {
53         mActivity = activity;
54         mThumbnailDir = activity.getDir("thumbnails", 0);
55     }
56
57     File getThumbnailDir() {
58         return mThumbnailDir;
59     }
60
61     BrowserActivity getBrowserActivity() {
62         return mActivity;
63     }
64
65     /**
66      * Return the current tab's main WebView. This will always return the main
67      * WebView for a given tab and not a subwindow.
68      * @return The current tab's WebView.
69      */
70     WebView getCurrentWebView() {
71         Tab t = getTab(mCurrentTab);
72         if (t == null) {
73             return null;
74         }
75         return t.getWebView();
76     }
77
78     /**
79      * Return the current tab's top-level WebView. This can return a subwindow
80      * if one exists.
81      * @return The top-level WebView of the current tab.
82      */
83     WebView getCurrentTopWebView() {
84         Tab t = getTab(mCurrentTab);
85         if (t == null) {
86             return null;
87         }
88         return t.getTopWindow();
89     }
90
91     /**
92      * Return the current tab's subwindow if it exists.
93      * @return The subwindow of the current tab or null if it doesn't exist.
94      */
95     WebView getCurrentSubWindow() {
96         Tab t = getTab(mCurrentTab);
97         if (t == null) {
98             return null;
99         }
100         return t.getSubWebView();
101     }
102
103     /**
104      * Return the tab at the specified index.
105      * @return The Tab for the specified index or null if the tab does not
106      *         exist.
107      */
108     Tab getTab(int index) {
109         if (index >= 0 && index < mTabs.size()) {
110             return mTabs.get(index);
111         }
112         return null;
113     }
114
115     /**
116      * Return the current tab.
117      * @return The current tab.
118      */
119     Tab getCurrentTab() {
120         return getTab(mCurrentTab);
121     }
122
123     /**
124      * Return the current tab index.
125      * @return The current tab index
126      */
127     int getCurrentIndex() {
128         return mCurrentTab;
129     }
130     
131     /**
132      * Given a Tab, find it's index
133      * @param Tab to find
134      * @return index of Tab or -1 if not found
135      */
136     int getTabIndex(Tab tab) {
137         if (tab == null) {
138             return -1;
139         }
140         return mTabs.indexOf(tab);
141     }
142
143     boolean canCreateNewTab() {
144         return MAX_TABS != mTabs.size();
145     }
146
147     /**
148      * Create a new tab.
149      * @return The newly createTab or null if we have reached the maximum
150      *         number of open tabs.
151      */
152     Tab createNewTab(boolean closeOnExit, String appId, String url) {
153         int size = mTabs.size();
154         // Return false if we have maxed out on tabs
155         if (MAX_TABS == size) {
156             return null;
157         }
158         final WebView w = createNewWebView();
159
160         // Create a new tab and add it to the tab list
161         Tab t = new Tab(mActivity, w, closeOnExit, appId, url);
162         mTabs.add(t);
163         // Initially put the tab in the background.
164         t.putInBackground();
165         return t;
166     }
167
168     /**
169      * Create a new tab with default values for closeOnExit(false),
170      * appId(null), and url(null).
171      */
172     Tab createNewTab() {
173         return createNewTab(false, null, null);
174     }
175
176     /**
177      * Remove the tab from the list. If the tab is the current tab shown, the
178      * last created tab will be shown.
179      * @param t The tab to be removed.
180      */
181     boolean removeTab(Tab t) {
182         if (t == null) {
183             return false;
184         }
185
186         // Only remove the tab if it is the current one.
187         if (getCurrentTab() == t) {
188             t.putInBackground();
189             mCurrentTab = -1;
190         }
191
192         // destroy the tab
193         t.destroy();
194         // clear it's references to parent and children
195         t.removeFromTree();
196         // Remove it from our list of tabs.
197         mTabs.remove(t);
198
199         // The tab indices have shifted, update all the saved state so we point
200         // to the correct index.
201         for (Tab tab : mTabs) {
202             Vector<Tab> children = tab.getChildTabs();
203             if (children != null) {
204                 for (Tab child : children) {
205                     child.setParentTab(tab);
206                 }
207             }
208         }
209
210         // This tab may have been pushed in to the background and then closed.
211         // If the saved state contains a picture file, delete the file.
212         Bundle savedState = t.getSavedState();
213         if (savedState != null) {
214             if (savedState.containsKey(Tab.CURRPICTURE)) {
215                 new File(savedState.getString(Tab.CURRPICTURE)).delete();
216             }
217         }
218
219         // Remove it from the queue of viewed tabs.
220         mTabQueue.remove(t);
221         return true;
222     }
223
224     /**
225      * Destroy all the tabs and subwindows
226      */
227     void destroy() {
228         for (Tab t : mTabs) {
229             t.destroy();
230         }
231         mTabs.clear();
232         mTabQueue.clear();
233     }
234
235     /**
236      * Returns the number of tabs created.
237      * @return The number of tabs created.
238      */
239     int getTabCount() {
240         return mTabs.size();
241     }
242
243
244     /**
245      * Save the state of all the Tabs.
246      * @param outState The Bundle to save the state to.
247      */
248     void saveState(Bundle outState) {
249         final int numTabs = getTabCount();
250         outState.putInt(Tab.NUMTABS, numTabs);
251         final int index = getCurrentIndex();
252         outState.putInt(Tab.CURRTAB, (index >= 0 && index < numTabs) ? index : 0);
253         for (int i = 0; i < numTabs; i++) {
254             final Tab t = getTab(i);
255             if (t.saveState()) {
256                 outState.putBundle(Tab.WEBVIEW + i, t.getSavedState());
257             }
258         }
259     }
260
261     /**
262      * Restore the state of all the tabs.
263      * @param inState The saved state of all the tabs.
264      * @return True if there were previous tabs that were restored. False if
265      *         there was no saved state or restoring the state failed.
266      */
267     boolean restoreState(Bundle inState) {
268         final int numTabs = (inState == null)
269                 ? -1 : inState.getInt(Tab.NUMTABS, -1);
270         if (numTabs == -1) {
271             return false;
272         } else {
273             final int currentTab = inState.getInt(Tab.CURRTAB, -1);
274             for (int i = 0; i < numTabs; i++) {
275                 if (i == currentTab) {
276                     Tab t = createNewTab();
277                     // Me must set the current tab before restoring the state
278                     // so that all the client classes are set.
279                     setCurrentTab(t);
280                     if (!t.restoreState(inState.getBundle(Tab.WEBVIEW + i))) {
281                         Log.w(LOGTAG, "Fail in restoreState, load home page.");
282                         t.getWebView().loadUrl(BrowserSettings.getInstance()
283                                 .getHomePage());
284                     }
285                 } else {
286                     // Create a new tab and don't restore the state yet, add it
287                     // to the tab list
288                     Tab t = new Tab(mActivity, null, false, null, null);
289                     Bundle state = inState.getBundle(Tab.WEBVIEW + i);
290                     if (state != null) {
291                         t.setSavedState(state);
292                         t.populatePickerDataFromSavedState();
293                         // Need to maintain the app id and original url so we
294                         // can possibly reuse this tab.
295                         t.setAppId(state.getString(Tab.APPID));
296                         t.setOriginalUrl(state.getString(Tab.ORIGINALURL));
297                     }
298                     mTabs.add(t);
299                     mTabQueue.add(t);
300                 }
301             }
302             // Rebuild the tree of tabs. Do this after all tabs have been
303             // created/restored so that the parent tab exists.
304             for (int i = 0; i < numTabs; i++) {
305                 final Bundle b = inState.getBundle(Tab.WEBVIEW + i);
306                 final Tab t = getTab(i);
307                 if (b != null && t != null) {
308                     final int parentIndex = b.getInt(Tab.PARENTTAB, -1);
309                     if (parentIndex != -1) {
310                         final Tab parent = getTab(parentIndex);
311                         if (parent != null) {
312                             parent.addChildTab(t);
313                         }
314                     }
315                 }
316             }
317         }
318         return true;
319     }
320
321     /**
322      * Free the memory in this order, 1) free the background tab; 2) free the
323      * WebView cache;
324      */
325     void freeMemory() {
326         if (getTabCount() == 0) return;
327
328         // free the least frequently used background tab
329         Tab t = getLeastUsedTab(getCurrentTab());
330         if (t != null) {
331             Log.w(LOGTAG, "Free a tab in the browser");
332             // store the WebView's state.
333             t.saveState();
334             // destroy the tab
335             t.destroy();
336             return;
337         }
338
339         // free the WebView's unused memory (this includes the cache)
340         Log.w(LOGTAG, "Free WebView's unused memory and cache");
341         WebView view = getCurrentWebView();
342         if (view != null) {
343             view.freeMemory();
344         }
345     }
346
347     private Tab getLeastUsedTab(Tab current) {
348         // Don't do anything if we only have 1 tab or if the current tab is
349         // null.
350         if (getTabCount() == 1 || current == null) {
351             return null;
352         }
353
354         // Rip through the queue starting at the beginning and tear down the
355         // next available tab.
356         Tab t = null;
357         int i = 0;
358         final int queueSize = mTabQueue.size();
359         if (queueSize == 0) {
360             return null;
361         }
362         do {
363             t = mTabQueue.get(i++);
364         } while (i < queueSize
365                 && ((t != null && t.getWebView() == null)
366                     || t == current.getParentTab()));
367
368         // Don't do anything if the last remaining tab is the current one or if
369         // the last tab has been freed already.
370         if (t == current || t.getWebView() == null) {
371             return null;
372         }
373
374         return t;
375     }
376
377     /**
378      * Show the tab that contains the given WebView.
379      * @param view The WebView used to find the tab.
380      */
381     Tab getTabFromView(WebView view) {
382         final int size = getTabCount();
383         for (int i = 0; i < size; i++) {
384             final Tab t = getTab(i);
385             if (t.getSubWebView() == view || t.getWebView() == view) {
386                 return t;
387             }
388         }
389         return null;
390     }
391
392     /**
393      * Return the tab with the matching application id.
394      * @param id The application identifier.
395      */
396     Tab getTabFromId(String id) {
397         if (id == null) {
398             return null;
399         }
400         final int size = getTabCount();
401         for (int i = 0; i < size; i++) {
402             final Tab t = getTab(i);
403             if (id.equals(t.getAppId())) {
404                 return t;
405             }
406         }
407         return null;
408     }
409
410     /**
411      * Stop loading in all opened WebView including subWindows.
412      */
413     void stopAllLoading() {
414         final int size = getTabCount();
415         for (int i = 0; i < size; i++) {
416             final Tab t = getTab(i);
417             final WebView webview = t.getWebView();
418             if (webview != null) {
419                 webview.stopLoading();
420             }
421             final WebView subview = t.getSubWebView();
422             if (subview != null) {
423                 webview.stopLoading();
424             }
425         }
426     }
427
428     // This method checks if a non-app tab (one created within the browser)
429     // matches the given url.
430     private boolean tabMatchesUrl(Tab t, String url) {
431         if (t.getAppId() != null) {
432             return false;
433         }
434         WebView webview = t.getWebView();
435         if (webview == null) {
436             return false;
437         } else if (url.equals(webview.getUrl())
438                 || url.equals(webview.getOriginalUrl())) {
439             return true;
440         }
441         return false;
442     }
443
444     /**
445      * Return the tab that has no app id associated with it and the url of the
446      * tab matches the given url.
447      * @param url The url to search for.
448      */
449     Tab findUnusedTabWithUrl(String url) {
450         if (url == null) {
451             return null;
452         }
453         // Check the current tab first.
454         Tab t = getCurrentTab();
455         if (t != null && tabMatchesUrl(t, url)) {
456             return t;
457         }
458         // Now check all the rest.
459         final int size = getTabCount();
460         for (int i = 0; i < size; i++) {
461             t = getTab(i);
462             if (tabMatchesUrl(t, url)) {
463                 return t;
464             }
465         }
466         return null;
467     }
468
469     /**
470      * Recreate the main WebView of the given tab. Returns true if the WebView
471      * was deleted.
472      */
473     boolean recreateWebView(Tab t, String url) {
474         final WebView w = t.getWebView();
475         if (w != null) {
476             if (url != null && url.equals(t.getOriginalUrl())) {
477                 // The original url matches the current url. Just go back to the
478                 // first history item so we can load it faster than if we
479                 // rebuilt the WebView.
480                 final WebBackForwardList list = w.copyBackForwardList();
481                 if (list != null) {
482                     w.goBackOrForward(-list.getCurrentIndex());
483                     w.clearHistory(); // maintains the current page.
484                     return false;
485                 }
486             }
487             t.destroy();
488         }
489         // Create a new WebView. If this tab is the current tab, we need to put
490         // back all the clients so force it to be the current tab.
491         t.setWebView(createNewWebView());
492         if (getCurrentTab() == t) {
493             setCurrentTab(t, true);
494         }
495         // Clear the saved state and picker data
496         t.setSavedState(null);
497         t.clearPickerData();
498         // Save the new url in order to avoid deleting the WebView.
499         t.setOriginalUrl(url);
500         return true;
501     }
502
503     /**
504      * Creates a new WebView and registers it with the global settings.
505      */
506     private WebView createNewWebView() {
507         // Create a new WebView
508         WebView w = new WebView(mActivity);
509         w.setScrollbarFadingEnabled(true);
510         w.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
511         w.setMapTrackballToArrowKeys(false); // use trackball directly
512         // Enable the built-in zoom
513         w.getSettings().setBuiltInZoomControls(true);
514         // Attach DownloadManager so that downloads can start in an active or
515         // a non-active window. This can happen when going to a site that does
516         // a redirect after a period of time. The user could have switched to
517         // another tab while waiting for the download to start.
518         w.setDownloadListener(mActivity);
519         // Add this WebView to the settings observer list and update the
520         // settings
521         final BrowserSettings s = BrowserSettings.getInstance();
522         s.addObserver(w.getSettings()).update(s, null);
523
524         // disable for now
525         //w.setDragTracker(new MeshTracker());
526         return w;
527     }
528
529     /**
530      * Put the current tab in the background and set newTab as the current tab.
531      * @param newTab The new tab. If newTab is null, the current tab is not
532      *               set.
533      */
534     boolean setCurrentTab(Tab newTab) {
535         return setCurrentTab(newTab, false);
536     }
537
538     void pauseCurrentTab() {
539         Tab t = getCurrentTab();
540         if (t != null) {
541             t.pause();
542         }
543     }
544
545     void resumeCurrentTab() {
546         Tab t = getCurrentTab();
547         if (t != null) {
548             t.resume();
549         }
550     }
551
552     /**
553      * If force is true, this method skips the check for newTab == current.
554      */
555     private boolean setCurrentTab(Tab newTab, boolean force) {
556         Tab current = getTab(mCurrentTab);
557         if (current == newTab && !force) {
558             return true;
559         }
560         if (current != null) {
561             current.putInBackground();
562             mCurrentTab = -1;
563         }
564         if (newTab == null) {
565             return false;
566         }
567
568         // Move the newTab to the end of the queue
569         int index = mTabQueue.indexOf(newTab);
570         if (index != -1) {
571             mTabQueue.remove(index);
572         }
573         mTabQueue.add(newTab);
574
575         // Display the new current tab
576         mCurrentTab = mTabs.indexOf(newTab);
577         WebView mainView = newTab.getWebView();
578         boolean needRestore = (mainView == null);
579         if (needRestore) {
580             // Same work as in createNewTab() except don't do new Tab()
581             mainView = createNewWebView();
582             newTab.setWebView(mainView);
583         }
584         newTab.putInForeground();
585         if (needRestore) {
586             // Have to finish setCurrentTab work before calling restoreState
587             if (!newTab.restoreState(newTab.getSavedState())) {
588                 mainView.loadUrl(BrowserSettings.getInstance().getHomePage());
589             }
590         }
591         return true;
592     }
593 }