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