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