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