TabControl.java revision f59ec877363eaf43118677f249008eddc7a9ce11
1/* 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; 177 // 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; 183 184 // Construct a new tab 185 private Tab(WebView w, boolean closeOnExit, String appId, String url) { 186 mMainView = w; 187 mCloseOnExit = closeOnExit; 188 mAppId = appId; 189 mOriginalUrl = url; 190 } 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 /** 414 * Create a new tab. 415 * @return The newly createTab or null if we have reached the maximum 416 * number of open tabs. 417 */ 418 Tab createNewTab(boolean closeOnExit, String appId, String url) { 419 int size = mTabs.size(); 420 // Return false if we have maxed out on tabs 421 if (MAX_TABS == size) { 422 return null; 423 } 424 final WebView w = createNewWebView(); 425 // Create a new tab and add it to the tab list 426 Tab t = new Tab(w, closeOnExit, appId, url); 427 mTabs.add(t); 428 // Initially put the tab in the background. 429 putTabInBackground(t); 430 return t; 431 } 432 433 /** 434 * 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 /** 442 * 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"; 549 private static final String APPID = "appid"; 550 private static final String ORIGINALURL = "originalUrl"; 551 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) { 584 Tab t = createNewTab(); 585 // 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 596 Tab t = new Tab(null, false, null, null); 597 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); 601 // 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); 605 } 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 /** 746 * 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 /** 818 * 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) { 823 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) { 830 Tab current = getTab(mCurrentTab); 831 if (current == newTab && !force) { 832 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() 859 newTab.mMainView = mainView = createNewWebView(); 860 } 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); 992 if (t.mAppId != null) { 993 b.putString(APPID, t.mAppId); 994 } 995 if (t.mOriginalUrl != null) { 996 b.putString(ORIGINALURL, t.mOriginalUrl); 997 } 998 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 } 1018 // 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 1027 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 } 1037 return true; 1038 } 1039} 1040