2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.browser;
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;
26 import java.util.ArrayList;
27 import java.util.Vector;
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;
47 * Construct a new TabControl object that interfaces with the given
48 * BrowserActivity instance.
49 * @param activity A BrowserActivity instance that TabControl will interface
52 TabControl(BrowserActivity activity) {
54 mThumbnailDir = activity.getDir("thumbnails", 0);
57 File getThumbnailDir() {
61 BrowserActivity getBrowserActivity() {
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.
70 WebView getCurrentWebView() {
71 Tab t = getTab(mCurrentTab);
75 return t.getWebView();
79 * Return the current tab's top-level WebView. This can return a subwindow
81 * @return The top-level WebView of the current tab.
83 WebView getCurrentTopWebView() {
84 Tab t = getTab(mCurrentTab);
88 return t.getTopWindow();
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.
95 WebView getCurrentSubWindow() {
96 Tab t = getTab(mCurrentTab);
100 return t.getSubWebView();
104 * Return the tab at the specified index.
105 * @return The Tab for the specified index or null if the tab does not
108 Tab getTab(int index) {
109 if (index >= 0 && index < mTabs.size()) {
110 return mTabs.get(index);
116 * Return the current tab.
117 * @return The current tab.
119 Tab getCurrentTab() {
120 return getTab(mCurrentTab);
124 * Return the current tab index.
125 * @return The current tab index
127 int getCurrentIndex() {
132 * Given a Tab, find it's index
134 * @return index of Tab or -1 if not found
136 int getTabIndex(Tab tab) {
140 return mTabs.indexOf(tab);
143 boolean canCreateNewTab() {
144 return MAX_TABS != mTabs.size();
149 * @return The newly createTab or null if we have reached the maximum
150 * number of open tabs.
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) {
158 final WebView w = createNewWebView();
160 // Create a new tab and add it to the tab list
161 Tab t = new Tab(mActivity, w, closeOnExit, appId, url);
163 // Initially put the tab in the background.
169 * Create a new tab with default values for closeOnExit(false),
170 * appId(null), and url(null).
173 return createNewTab(false, null, null);
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.
181 boolean removeTab(Tab t) {
186 // Only remove the tab if it is the current one.
187 if (getCurrentTab() == t) {
194 // clear it's references to parent and children
196 // Remove it from our list of tabs.
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);
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();
219 // Remove it from the queue of viewed tabs.
225 * Destroy all the tabs and subwindows
228 for (Tab t : mTabs) {
236 * Returns the number of tabs created.
237 * @return The number of tabs created.
245 * Save the state of all the Tabs.
246 * @param outState The Bundle to save the state to.
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);
256 outState.putBundle(Tab.WEBVIEW + i, t.getSavedState());
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.
267 boolean restoreState(Bundle inState) {
268 final int numTabs = (inState == null)
269 ? -1 : inState.getInt(Tab.NUMTABS, -1);
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.
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()
286 // Create a new tab and don't restore the state yet, add it
288 Tab t = new Tab(mActivity, null, false, null, null);
289 Bundle state = inState.getBundle(Tab.WEBVIEW + i);
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));
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);
322 * Free the memory in this order, 1) free the background tab; 2) free the
326 if (getTabCount() == 0) return;
328 // free the least frequently used background tab
329 Tab t = getLeastUsedTab(getCurrentTab());
331 Log.w(LOGTAG, "Free a tab in the browser");
332 // store the WebView's state.
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();
347 private Tab getLeastUsedTab(Tab current) {
348 // Don't do anything if we only have 1 tab or if the current tab is
350 if (getTabCount() == 1 || current == null) {
354 // Rip through the queue starting at the beginning and tear down the
355 // next available tab.
358 final int queueSize = mTabQueue.size();
359 if (queueSize == 0) {
363 t = mTabQueue.get(i++);
364 } while (i < queueSize
365 && ((t != null && t.getWebView() == null)
366 || t == current.getParentTab()));
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) {
378 * Show the tab that contains the given WebView.
379 * @param view The WebView used to find the tab.
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) {
393 * Return the tab with the matching application id.
394 * @param id The application identifier.
396 Tab getTabFromId(String id) {
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())) {
411 * Stop loading in all opened WebView including subWindows.
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();
421 final WebView subview = t.getSubWebView();
422 if (subview != null) {
423 webview.stopLoading();
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) {
434 WebView webview = t.getWebView();
435 if (webview == null) {
437 } else if (url.equals(webview.getUrl())
438 || url.equals(webview.getOriginalUrl())) {
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.
449 Tab findUnusedTabWithUrl(String url) {
453 // Check the current tab first.
454 Tab t = getCurrentTab();
455 if (t != null && tabMatchesUrl(t, url)) {
458 // Now check all the rest.
459 final int size = getTabCount();
460 for (int i = 0; i < size; i++) {
462 if (tabMatchesUrl(t, url)) {
470 * Recreate the main WebView of the given tab. Returns true if the WebView
473 boolean recreateWebView(Tab t, String url) {
474 final WebView w = t.getWebView();
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();
482 w.goBackOrForward(-list.getCurrentIndex());
483 w.clearHistory(); // maintains the current page.
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);
495 // Clear the saved state and picker data
496 t.setSavedState(null);
498 // Save the new url in order to avoid deleting the WebView.
499 t.setOriginalUrl(url);
504 * Creates a new WebView and registers it with the global settings.
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
521 final BrowserSettings s = BrowserSettings.getInstance();
522 s.addObserver(w.getSettings()).update(s, null);
525 //w.setDragTracker(new MeshTracker());
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
534 boolean setCurrentTab(Tab newTab) {
535 return setCurrentTab(newTab, false);
538 void pauseCurrentTab() {
539 Tab t = getCurrentTab();
545 void resumeCurrentTab() {
546 Tab t = getCurrentTab();
553 * If force is true, this method skips the check for newTab == current.
555 private boolean setCurrentTab(Tab newTab, boolean force) {
556 Tab current = getTab(mCurrentTab);
557 if (current == newTab && !force) {
560 if (current != null) {
561 current.putInBackground();
564 if (newTab == null) {
568 // Move the newTab to the end of the queue
569 int index = mTabQueue.indexOf(newTab);
571 mTabQueue.remove(index);
573 mTabQueue.add(newTab);
575 // Display the new current tab
576 mCurrentTab = mTabs.indexOf(newTab);
577 WebView mainView = newTab.getWebView();
578 boolean needRestore = (mainView == null);
580 // Same work as in createNewTab() except don't do new Tab()
581 mainView = createNewWebView();
582 newTab.setWebView(mainView);
584 newTab.putInForeground();
586 // Have to finish setCurrentTab work before calling restoreState
587 if (!newTab.restoreState(newTab.getSavedState())) {
588 mainView.loadUrl(BrowserSettings.getInstance().getHomePage());