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