blob: cfe63a682d9d30882733ab6ef90091ac9c60d6e0 [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) {
Patrick Scottae641ac2009-04-20 13:51:49 -0400409 if (tab == null) {
410 return -1;
411 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800412 return mTabs.indexOf(tab);
413 }
414
415 /**
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700416 * Create a new tab.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800417 * @return The newly createTab or null if we have reached the maximum
418 * number of open tabs.
419 */
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700420 Tab createNewTab(boolean closeOnExit, String appId, String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800421 int size = mTabs.size();
422 // Return false if we have maxed out on tabs
423 if (MAX_TABS == size) {
424 return null;
425 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700426 final WebView w = createNewWebView();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800427 // Create a new tab and add it to the tab list
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700428 Tab t = new Tab(w, closeOnExit, appId, url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800429 mTabs.add(t);
The Android Open Source Project4e5f5872009-03-09 11:52:14 -0700430 // Initially put the tab in the background.
431 putTabInBackground(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800432 return t;
433 }
434
435 /**
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700436 * Create a new tab with default values for closeOnExit(false),
437 * appId(null), and url(null).
438 */
439 Tab createNewTab() {
440 return createNewTab(false, null, null);
441 }
442
443 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800444 * Remove the tab from the list. If the tab is the current tab shown, the
445 * last created tab will be shown.
446 * @param t The tab to be removed.
447 */
448 boolean removeTab(Tab t) {
449 if (t == null) {
450 return false;
451 }
452 // Only remove the tab if it is the current one.
453 if (getCurrentTab() == t) {
454 putTabInBackground(t);
455 }
456
457 // Only destroy the WebView if it still exists.
458 if (t.mMainView != null) {
459 // Take down the sub window.
460 dismissSubWindow(t);
461 // Remove the WebView's settings from the BrowserSettings list of
462 // observers.
463 BrowserSettings.getInstance().deleteObserver(
464 t.mMainView.getSettings());
465 // Destroy the main view and subview
466 t.mMainView.destroy();
467 t.mMainView = null;
468 }
469 // clear it's references to parent and children
470 t.removeFromTree();
471
472 // Remove it from our list of tabs.
473 mTabs.remove(t);
474
475 // The tab indices have shifted, update all the saved state so we point
476 // to the correct index.
477 for (Tab tab : mTabs) {
478 if (tab.mChildTabs != null) {
479 for (Tab child : tab.mChildTabs) {
480 child.setParentTab(tab);
481 }
482 }
483 }
484
485
486 // This tab may have been pushed in to the background and then closed.
487 // If the saved state contains a picture file, delete the file.
488 if (t.mSavedState != null) {
489 if (t.mSavedState.containsKey("picture")) {
490 new File(t.mSavedState.getString("picture")).delete();
491 }
492 }
493
494 // Remove it from the queue of viewed tabs.
495 mTabQueue.remove(t);
496 mCurrentTab = -1;
497 return true;
498 }
499
500 /**
501 * Clear the back/forward list for all the current tabs.
502 */
503 void clearHistory() {
504 int size = getTabCount();
505 for (int i = 0; i < size; i++) {
506 Tab t = mTabs.get(i);
507 // TODO: if a tab is freed due to low memory, its history is not
508 // cleared here.
509 if (t.mMainView != null) {
510 t.mMainView.clearHistory();
511 }
512 if (t.mSubView != null) {
513 t.mSubView.clearHistory();
514 }
515 }
516 }
517
518 /**
519 * Destroy all the tabs and subwindows
520 */
521 void destroy() {
522 BrowserSettings s = BrowserSettings.getInstance();
523 for (Tab t : mTabs) {
524 if (t.mMainView != null) {
525 dismissSubWindow(t);
526 s.deleteObserver(t.mMainView.getSettings());
527 t.mMainView.destroy();
528 t.mMainView = null;
529 }
530 }
531 mTabs.clear();
532 mTabQueue.clear();
533 }
534
535 /**
536 * Returns the number of tabs created.
537 * @return The number of tabs created.
538 */
539 int getTabCount() {
540 return mTabs.size();
541 }
542
543 // Used for saving and restoring each Tab
544 private static final String WEBVIEW = "webview";
545 private static final String NUMTABS = "numTabs";
546 private static final String CURRTAB = "currentTab";
547 private static final String CURRURL = "currentUrl";
548 private static final String CURRTITLE = "currentTitle";
549 private static final String CLOSEONEXIT = "closeonexit";
550 private static final String PARENTTAB = "parentTab";
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700551 private static final String APPID = "appid";
552 private static final String ORIGINALURL = "originalUrl";
The Android Open Source Project0c908882009-03-03 19:32:16 -0800553
554 /**
555 * Save the state of all the Tabs.
556 * @param outState The Bundle to save the state to.
557 */
558 void saveState(Bundle outState) {
559 final int numTabs = getTabCount();
560 outState.putInt(NUMTABS, numTabs);
561 final int index = getCurrentIndex();
562 outState.putInt(CURRTAB, (index >= 0 && index < numTabs) ? index : 0);
563 for (int i = 0; i < numTabs; i++) {
564 final Tab t = getTab(i);
565 if (saveState(t)) {
566 outState.putBundle(WEBVIEW + i, t.mSavedState);
567 }
568 }
569 }
570
571 /**
572 * Restore the state of all the tabs.
573 * @param inState The saved state of all the tabs.
574 * @return True if there were previous tabs that were restored. False if
575 * there was no saved state or restoring the state failed.
576 */
577 boolean restoreState(Bundle inState) {
578 final int numTabs = (inState == null)
579 ? -1 : inState.getInt(NUMTABS, -1);
580 if (numTabs == -1) {
581 return false;
582 } else {
583 final int currentTab = inState.getInt(CURRTAB, -1);
584 for (int i = 0; i < numTabs; i++) {
585 if (i == currentTab) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700586 Tab t = createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800587 // Me must set the current tab before restoring the state
588 // so that all the client classes are set.
589 setCurrentTab(t);
590 if (!restoreState(inState.getBundle(WEBVIEW + i), t)) {
591 Log.w(LOGTAG, "Fail in restoreState, load home page.");
592 t.mMainView.loadUrl(BrowserSettings.getInstance()
593 .getHomePage());
594 }
595 } else {
596 // Create a new tab and don't restore the state yet, add it
597 // to the tab list
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700598 Tab t = new Tab(null, false, null, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800599 t.mSavedState = inState.getBundle(WEBVIEW + i);
600 if (t.mSavedState != null) {
601 t.mUrl = t.mSavedState.getString(CURRURL);
602 t.mTitle = t.mSavedState.getString(CURRTITLE);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700603 // Need to maintain the app id and original url so we
604 // can possibly reuse this tab.
605 t.mAppId = t.mSavedState.getString(APPID);
606 t.mOriginalUrl = t.mSavedState.getString(ORIGINALURL);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800607 }
608 mTabs.add(t);
609 mTabQueue.add(t);
610 }
611 }
612 // Rebuild the tree of tabs. Do this after all tabs have been
613 // created/restored so that the parent tab exists.
614 for (int i = 0; i < numTabs; i++) {
615 final Bundle b = inState.getBundle(WEBVIEW + i);
616 final Tab t = getTab(i);
617 if (b != null && t != null) {
618 final int parentIndex = b.getInt(PARENTTAB, -1);
619 if (parentIndex != -1) {
620 final Tab parent = getTab(parentIndex);
621 if (parent != null) {
622 parent.addChildTab(t);
623 }
624 }
625 }
626 }
627 }
628 return true;
629 }
630
631 /**
632 * Free the memory in this order, 1) free the background tab; 2) free the
633 * WebView cache;
634 */
635 void freeMemory() {
636 // free the least frequently used background tab
637 Tab t = getLeastUsedTab();
638 if (t != null) {
639 Log.w(LOGTAG, "Free a tab in the browser");
640 freeTab(t);
641 // force a gc
642 System.gc();
643 return;
644 }
645
646 // free the WebView cache
647 Log.w(LOGTAG, "Free WebView cache");
648 WebView view = getCurrentWebView();
649 if (view != null) {
650 view.clearCache(false);
651 }
652 // force a gc
653 System.gc();
654 }
655
656 private Tab getLeastUsedTab() {
657 // Don't do anything if we only have 1 tab.
658 if (getTabCount() == 1) {
659 return null;
660 }
661
662 // Rip through the queue starting at the beginning and teardown the
663 // next available tab.
664 Tab t = null;
665 int i = 0;
666 final int queueSize = mTabQueue.size();
667 if (queueSize == 0) {
668 return null;
669 }
670 do {
671 t = mTabQueue.get(i++);
672 } while (i < queueSize && t != null && t.mMainView == null);
673
674 // Don't do anything if the last remaining tab is the current one.
675 if (t == getCurrentTab()) {
676 return null;
677 }
678
679 return t;
680 }
681
682 private void freeTab(Tab t) {
683 // Store the WebView's state.
684 saveState(t);
685
686 // Tear down the tab.
687 dismissSubWindow(t);
688 // Remove the WebView's settings from the BrowserSettings list of
689 // observers.
690 BrowserSettings.getInstance().deleteObserver(t.mMainView.getSettings());
691 t.mMainView.destroy();
692 t.mMainView = null;
693 }
694
695 /**
696 * Create a new subwindow unless a subwindow already exists.
697 * @return True if a new subwindow was created. False if one already exists.
698 */
699 void createSubWindow() {
700 Tab t = getTab(mCurrentTab);
701 if (t != null && t.mSubView == null) {
702 final View v = mInflateService.inflate(R.layout.browser_subwindow, null);
703 final WebView w = (WebView) v.findViewById(R.id.webview);
704 w.setMapTrackballToArrowKeys(false); // use trackball directly
705 final SubWindowClient subClient =
706 new SubWindowClient(mActivity.getWebViewClient());
707 final SubWindowChromeClient subChromeClient =
708 new SubWindowChromeClient(t,
709 mActivity.getWebChromeClient());
710 w.setWebViewClient(subClient);
711 w.setWebChromeClient(subChromeClient);
712 w.setDownloadListener(mActivity);
713 w.setOnCreateContextMenuListener(mActivity);
714 final BrowserSettings s = BrowserSettings.getInstance();
715 s.addObserver(w.getSettings()).update(s, null);
716 t.mSubView = w;
717 t.mSubViewClient = subClient;
718 t.mSubViewChromeClient = subChromeClient;
719 // FIXME: I really hate having to know the name of the view
720 // containing the webview.
721 t.mSubViewContainer = v.findViewById(R.id.subwindow_container);
722 final ImageButton cancel =
723 (ImageButton) v.findViewById(R.id.subwindow_close);
724 cancel.setOnClickListener(new OnClickListener() {
725 public void onClick(View v) {
726 subChromeClient.onCloseWindow(w);
727 }
728 });
729 }
730 }
731
732 /**
733 * Show the tab that contains the given WebView.
734 * @param view The WebView used to find the tab.
735 */
736 Tab getTabFromView(WebView view) {
737 final int size = getTabCount();
738 for (int i = 0; i < size; i++) {
739 final Tab t = getTab(i);
740 if (t.mSubView == view || t.mMainView == view) {
741 return t;
742 }
743 }
744 return null;
745 }
746
747 /**
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700748 * Return the tab with the matching application id.
749 * @param id The application identifier.
750 */
751 Tab getTabFromId(String id) {
752 if (id == null) {
753 return null;
754 }
755 final int size = getTabCount();
756 for (int i = 0; i < size; i++) {
757 final Tab t = getTab(i);
758 if (id.equals(t.mAppId)) {
759 return t;
760 }
761 }
762 return null;
763 }
764
765 /**
766 * Recreate the main WebView of the given tab. Returns true if the WebView
767 * was deleted.
768 */
769 boolean recreateWebView(Tab t, String url) {
770 final WebView w = t.mMainView;
771 if (w != null) {
772 if (url != null && url.equals(t.mOriginalUrl)) {
773 // The original url matches the current url. Just go back to the
774 // first history item so we can load it faster than if we
775 // rebuilt the WebView.
776 final WebBackForwardList list = w.copyBackForwardList();
777 if (list != null) {
778 w.goBackOrForward(-list.getCurrentIndex());
779 w.clearHistory(); // maintains the current page.
780 return false;
781 }
782 }
783 // Remove the settings object from the global settings and destroy
784 // the WebView.
785 BrowserSettings.getInstance().deleteObserver(
786 t.mMainView.getSettings());
787 t.mMainView.destroy();
788 }
789 // Create a new WebView. If this tab is the current tab, we need to put
790 // back all the clients so force it to be the current tab.
791 t.mMainView = createNewWebView();
792 if (getCurrentTab() == t) {
793 setCurrentTab(t, true);
794 }
795 // Clear the saved state except for the app id and close-on-exit
796 // values.
797 t.mSavedState = null;
798 t.mUrl = null;
799 t.mTitle = null;
800 // Save the new url in order to avoid deleting the WebView.
801 t.mOriginalUrl = url;
802 return true;
803 }
804
805 /**
806 * Creates a new WebView and registers it with the global settings.
807 */
808 private WebView createNewWebView() {
809 // Create a new WebView
810 WebView w = new WebView(mActivity);
811 w.setMapTrackballToArrowKeys(false); // use trackball directly
The Android Open Source Projecta3c0aab2009-03-18 17:39:48 -0700812 // Enable the built-in zoom
813 w.getSettings().setBuiltInZoomControls(true);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700814 // Add this WebView to the settings observer list and update the
815 // settings
816 final BrowserSettings s = BrowserSettings.getInstance();
817 s.addObserver(w.getSettings()).update(s, null);
818 return w;
819 }
820
821 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -0800822 * Put the current tab in the background and set newTab as the current tab.
823 * @param newTab The new tab. If newTab is null, the current tab is not
824 * set.
825 */
826 boolean setCurrentTab(Tab newTab) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700827 return setCurrentTab(newTab, false);
828 }
829
830 /**
831 * If force is true, this method skips the check for newTab == current.
832 */
833 private boolean setCurrentTab(Tab newTab, boolean force) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800834 Tab current = getTab(mCurrentTab);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700835 if (current == newTab && !force) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800836 return true;
837 }
838 if (current != null) {
839 // Remove the current WebView and the container of the subwindow
840 putTabInBackground(current);
841 }
842
843 if (newTab == null) {
844 return false;
845 }
846
847 // Move the newTab to the end of the queue
848 int index = mTabQueue.indexOf(newTab);
849 if (index != -1) {
850 mTabQueue.remove(index);
851 }
852 mTabQueue.add(newTab);
853
854 WebView mainView;
855 WebView subView;
856
857 // Display the new current tab
858 mCurrentTab = mTabs.indexOf(newTab);
859 mainView = newTab.mMainView;
860 boolean needRestore = (mainView == null);
861 if (needRestore) {
862 // Same work as in createNewTab() except don't do new Tab()
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700863 newTab.mMainView = mainView = createNewWebView();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800864 }
865 mainView.setWebViewClient(mActivity.getWebViewClient());
866 mainView.setWebChromeClient(mActivity.getWebChromeClient());
867 mainView.setOnCreateContextMenuListener(mActivity);
868 mainView.setDownloadListener(mActivity);
869 // Add the subwindow if it exists
870 if (newTab.mSubViewContainer != null) {
871 subView = newTab.mSubView;
872 subView.setWebViewClient(newTab.mSubViewClient);
873 subView.setWebChromeClient(newTab.mSubViewChromeClient);
874 subView.setOnCreateContextMenuListener(mActivity);
875 subView.setDownloadListener(mActivity);
876 }
877 if (needRestore) {
878 // Have to finish setCurrentTab work before calling restoreState
879 if (!restoreState(newTab.mSavedState, newTab)) {
880 mainView.loadUrl(BrowserSettings.getInstance().getHomePage());
881 }
882 }
883 return true;
884 }
885
886 /*
887 * Put the tab in the background using all the empty/background clients.
888 */
889 private void putTabInBackground(Tab t) {
890 WebView mainView = t.mMainView;
891 // Set an empty callback so that default actions are not triggered.
892 mainView.setWebViewClient(mEmptyClient);
893 mainView.setWebChromeClient(mBackgroundChromeClient);
894 mainView.setOnCreateContextMenuListener(null);
895 // Leave the DownloadManager attached so that downloads can start in
896 // a non-active window. This can happen when going to a site that does
897 // a redirect after a period of time. The user could have switched to
898 // another tab while waiting for the download to start.
899 mainView.setDownloadListener(mActivity);
900 WebView subView = t.mSubView;
901 if (subView != null) {
902 // Set an empty callback so that default actions are not triggered.
903 subView.setWebViewClient(mEmptyClient);
904 subView.setWebChromeClient(mBackgroundChromeClient);
905 subView.setOnCreateContextMenuListener(null);
906 subView.setDownloadListener(mActivity);
907 }
908 }
909
910 /*
911 * Dismiss the subwindow for the given tab.
912 */
913 void dismissSubWindow(Tab t) {
914 if (t != null && t.mSubView != null) {
915 BrowserSettings.getInstance().deleteObserver(
916 t.mSubView.getSettings());
917 t.mSubView.destroy();
918 t.mSubView = null;
919 t.mSubViewContainer = null;
920 }
921 }
922
923 /**
924 * Ensure that Tab t has a title, url, and favicon.
925 * @param t Tab to populate.
926 */
927 /* package */ void populatePickerData(Tab t) {
928 if (t == null || t.mMainView == null) {
929 return;
930 }
931 // FIXME: The only place we cared about subwindow was for
932 // bookmarking (i.e. not when saving state). Was this deliberate?
933 final WebBackForwardList list = t.mMainView.copyBackForwardList();
934 final WebHistoryItem item =
935 list != null ? list.getCurrentItem() : null;
936 populatePickerData(t, item);
937 }
938
939 // Populate the picker data
940 private void populatePickerData(Tab t, WebHistoryItem item) {
941 if (item != null) {
942 t.mUrl = item.getUrl();
943 t.mTitle = item.getTitle();
944 if (t.mTitle == null) {
945 t.mTitle = t.mUrl;
946 }
947 }
948 }
949
950 /**
951 * Clean up the data for all tabs.
952 */
953 /* package */ void wipeAllPickerData() {
954 int size = getTabCount();
955 for (int i = 0; i < size; i++) {
956 final Tab t = getTab(i);
957 if (t != null && t.mSavedState == null) {
958 t.mUrl = null;
959 t.mTitle = null;
960 }
961 }
962 }
963
964 /*
965 * Save the state for an individual tab.
966 */
967 private boolean saveState(Tab t) {
968 if (t != null) {
969 final WebView w = t.mMainView;
970 // If the WebView is null it means we ran low on memory and we
971 // already stored the saved state in mSavedState.
972 if (w == null) {
973 return true;
974 }
975 final Bundle b = new Bundle();
976 final WebBackForwardList list = w.saveState(b);
977 if (list != null) {
978 final File f = new File(mThumbnailDir, w.hashCode()
979 + "_pic.save");
980 if (w.savePicture(b, f)) {
981 b.putString("picture", f.getPath());
982 }
983 }
984
985 // Store some extra info for displaying the tab in the picker.
986 final WebHistoryItem item =
987 list != null ? list.getCurrentItem() : null;
988 populatePickerData(t, item);
989 if (t.mUrl != null) {
990 b.putString(CURRURL, t.mUrl);
991 }
992 if (t.mTitle != null) {
993 b.putString(CURRTITLE, t.mTitle);
994 }
995 b.putBoolean(CLOSEONEXIT, t.mCloseOnExit);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700996 if (t.mAppId != null) {
997 b.putString(APPID, t.mAppId);
998 }
999 if (t.mOriginalUrl != null) {
1000 b.putString(ORIGINALURL, t.mOriginalUrl);
1001 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001002
1003 // Remember the parent tab so the relationship can be restored.
1004 if (t.mParentTab != null) {
1005 b.putInt(PARENTTAB, getTabIndex(t.mParentTab));
1006 }
1007
1008 // Remember the saved state.
1009 t.mSavedState = b;
1010 return true;
1011 }
1012 return false;
1013 }
1014
1015 /*
1016 * Restore the state of the tab.
1017 */
1018 private boolean restoreState(Bundle b, Tab t) {
1019 if (b == null) {
1020 return false;
1021 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001022 // Restore the internal state even if the WebView fails to restore.
1023 // This will maintain the app id, original url and close-on-exit values.
1024 t.mSavedState = null;
1025 t.mUrl = null;
1026 t.mTitle = null;
1027 t.mCloseOnExit = b.getBoolean(CLOSEONEXIT);
1028 t.mAppId = b.getString(APPID);
1029 t.mOriginalUrl = b.getString(ORIGINALURL);
1030
The Android Open Source Project0c908882009-03-03 19:32:16 -08001031 final WebView w = t.mMainView;
1032 final WebBackForwardList list = w.restoreState(b);
1033 if (list == null) {
1034 return false;
1035 }
1036 if (b.containsKey("picture")) {
1037 final File f = new File(b.getString("picture"));
1038 w.restorePicture(b, f);
1039 f.delete();
1040 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001041 return true;
1042 }
1043}