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