Tab.java revision 4dce3bf588b7eaf4de555a93b67f5ddf9bc1ba77
1/* 2 * Copyright (C) 2009 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 java.io.File; 20import java.util.ArrayList; 21import java.util.LinkedList; 22import java.util.Vector; 23 24import android.app.AlertDialog; 25import android.app.SearchManager; 26import android.content.ContentResolver; 27import android.content.ContentValues; 28import android.content.DialogInterface; 29import android.content.DialogInterface.OnCancelListener; 30import android.content.Intent; 31import android.database.Cursor; 32import android.database.sqlite.SQLiteDatabase; 33import android.database.sqlite.SQLiteException; 34import android.graphics.Bitmap; 35import android.net.Uri; 36import android.net.http.SslError; 37import android.os.AsyncTask; 38import android.os.Bundle; 39import android.os.Message; 40import android.os.SystemClock; 41import android.provider.Browser; 42import android.speech.RecognizerResultsIntent; 43import android.util.Log; 44import android.view.KeyEvent; 45import android.view.LayoutInflater; 46import android.view.View; 47import android.view.ViewGroup; 48import android.view.View.OnClickListener; 49import android.webkit.CookieSyncManager; 50import android.webkit.GeolocationPermissions; 51import android.webkit.HttpAuthHandler; 52import android.webkit.SslErrorHandler; 53import android.webkit.URLUtil; 54import android.webkit.ValueCallback; 55import android.webkit.WebBackForwardList; 56import android.webkit.WebChromeClient; 57import android.webkit.WebHistoryItem; 58import android.webkit.WebIconDatabase; 59import android.webkit.WebStorage; 60import android.webkit.WebView; 61import android.webkit.WebViewClient; 62import android.widget.FrameLayout; 63import android.widget.ImageButton; 64import android.widget.LinearLayout; 65import android.widget.TextView; 66 67/** 68 * Class for maintaining Tabs with a main WebView and a subwindow. 69 */ 70class Tab { 71 // Log Tag 72 private static final String LOGTAG = "Tab"; 73 // The Geolocation permissions prompt 74 private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt; 75 // Main WebView wrapper 76 private View mContainer; 77 // Main WebView 78 private WebView mMainView; 79 // Subwindow container 80 private View mSubViewContainer; 81 // Subwindow WebView 82 private WebView mSubView; 83 // Saved bundle for when we are running low on memory. It contains the 84 // information needed to restore the WebView if the user goes back to the 85 // tab. 86 private Bundle mSavedState; 87 // Data used when displaying the tab in the picker. 88 private PickerData mPickerData; 89 // Parent Tab. This is the Tab that created this Tab, or null if the Tab was 90 // created by the UI 91 private Tab mParentTab; 92 // Tab that constructed by this Tab. This is used when this Tab is 93 // destroyed, it clears all mParentTab values in the children. 94 private Vector<Tab> mChildTabs; 95 // If true, the tab will be removed when back out of the first page. 96 private boolean mCloseOnExit; 97 // If true, the tab is in the foreground of the current activity. 98 private boolean mInForeground; 99 // If true, the tab is in loading state. 100 private boolean mInLoad; 101 // The time the load started, used to find load page time 102 private long mLoadStartTime; 103 // Application identifier used to find tabs that another application wants 104 // to reuse. 105 private String mAppId; 106 // Keep the original url around to avoid killing the old WebView if the url 107 // has not changed. 108 private String mOriginalUrl; 109 // Error console for the tab 110 private ErrorConsoleView mErrorConsole; 111 // the lock icon type and previous lock icon type for the tab 112 private int mLockIconType; 113 private int mPrevLockIconType; 114 // Inflation service for making subwindows. 115 private final LayoutInflater mInflateService; 116 // The BrowserActivity which owners the Tab 117 private final BrowserActivity mActivity; 118 119 // AsyncTask for downloading touch icons 120 DownloadTouchIcon mTouchIconLoader; 121 122 // Extra saved information for displaying the tab in the picker. 123 private static class PickerData { 124 String mUrl; 125 String mTitle; 126 Bitmap mFavicon; 127 } 128 129 // Used for saving and restoring each Tab 130 static final String WEBVIEW = "webview"; 131 static final String NUMTABS = "numTabs"; 132 static final String CURRTAB = "currentTab"; 133 static final String CURRURL = "currentUrl"; 134 static final String CURRTITLE = "currentTitle"; 135 static final String CURRPICTURE = "currentPicture"; 136 static final String CLOSEONEXIT = "closeonexit"; 137 static final String PARENTTAB = "parentTab"; 138 static final String APPID = "appid"; 139 static final String ORIGINALURL = "originalUrl"; 140 141 // ------------------------------------------------------------------------- 142 143 /** 144 * Private information regarding the latest voice search. If the Tab is not 145 * in voice search mode, this will be null. 146 */ 147 private VoiceSearchData mVoiceSearchData; 148 /** 149 * Return whether the tab is in voice search mode. 150 */ 151 public boolean isInVoiceSearchMode() { 152 return mVoiceSearchData != null; 153 } 154 /** 155 * Get the title to display for the current voice search page. If the Tab 156 * is not in voice search mode, return null. 157 */ 158 public String getVoiceDisplayTitle() { 159 if (mVoiceSearchData == null) return null; 160 return mVoiceSearchData.mLastVoiceSearchTitle; 161 } 162 /** 163 * Get the latest array of voice search results, to be passed to the 164 * BrowserProvider. If the Tab is not in voice search mode, return null. 165 */ 166 public ArrayList<String> getVoiceSearchResults() { 167 if (mVoiceSearchData == null) return null; 168 return mVoiceSearchData.mVoiceSearchResults; 169 } 170 /** 171 * Activate voice search mode. 172 * @param intent Intent which has the results to use, or an index into the 173 * results when reusing the old results. 174 */ 175 /* package */ void activateVoiceSearchMode(Intent intent) { 176 ArrayList<String> results = intent.getStringArrayListExtra( 177 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_STRINGS); 178 ArrayList<String> urls = intent.getStringArrayListExtra( 179 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_URLS); 180 if (results != null) { 181 // This tab is now entering voice search mode for the first time, or 182 // a new voice search was done. 183 if (urls == null || results.size() != urls.size()) { 184 throw new AssertionError("improper extras passed in Intent"); 185 } 186 mVoiceSearchData = new VoiceSearchData(results, urls); 187 } else { 188 String extraData = intent.getStringExtra( 189 SearchManager.EXTRA_DATA_KEY); 190 if (extraData != null) { 191 mVoiceSearchData.mLastVoiceSearchIndex 192 = Integer.parseInt(extraData); 193 if (mVoiceSearchData.mLastVoiceSearchIndex 194 >= mVoiceSearchData.mVoiceSearchResults.size()) { 195 throw new AssertionError("index must be less than " 196 + " size of mVoiceSearchResults"); 197 } 198 } 199 } 200 mVoiceSearchData.mLastVoiceSearchTitle 201 = mVoiceSearchData.mVoiceSearchResults.get(mVoiceSearchData. 202 mLastVoiceSearchIndex); 203 if (mInForeground) { 204 mActivity.showVoiceTitleBar(mVoiceSearchData.mLastVoiceSearchTitle); 205 } 206 mVoiceSearchData.mLastVoiceSearchUrl 207 = mVoiceSearchData.mVoiceSearchUrls.get(mVoiceSearchData. 208 mLastVoiceSearchIndex); 209 mMainView.loadUrl(mVoiceSearchData.mLastVoiceSearchUrl); 210 } 211 /* package */ static class VoiceSearchData { 212 public VoiceSearchData(ArrayList<String> results, 213 ArrayList<String> urls) { 214 mVoiceSearchResults = results; 215 mVoiceSearchUrls = urls; 216 mLastVoiceSearchIndex = 0; 217 } 218 /* 219 * ArrayList of suggestions to be displayed when opening the 220 * SearchManager 221 */ 222 public ArrayList<String> mVoiceSearchResults; 223 /* 224 * ArrayList of urls, associated with the suggestions in 225 * mVoiceSearchResults. 226 */ 227 public ArrayList<String> mVoiceSearchUrls; 228 /* 229 * The last url provided by voice search. Used for comparison to see if 230 * we are going to a page by some method besides voice search. Only 231 * meaningful in voice search mode. 232 */ 233 public String mLastVoiceSearchUrl; 234 /** 235 * The last title used for voice search. Needed to update the title bar 236 * when switching tabs. 237 */ 238 public String mLastVoiceSearchTitle; 239 /* 240 * The index into mVoiceSearchResults and mVoiceSearchUrls of the last 241 * voice search performed. Stored so it can be used to index into 242 * mVoiceSearchUrls to determine the url in getUrlDataFromIntent. 243 */ 244 public int mLastVoiceSearchIndex; 245 } 246 247 // Container class for the next error dialog that needs to be displayed 248 private class ErrorDialog { 249 public final int mTitle; 250 public final String mDescription; 251 public final int mError; 252 ErrorDialog(int title, String desc, int error) { 253 mTitle = title; 254 mDescription = desc; 255 mError = error; 256 } 257 }; 258 259 private void processNextError() { 260 if (mQueuedErrors == null) { 261 return; 262 } 263 // The first one is currently displayed so just remove it. 264 mQueuedErrors.removeFirst(); 265 if (mQueuedErrors.size() == 0) { 266 mQueuedErrors = null; 267 return; 268 } 269 showError(mQueuedErrors.getFirst()); 270 } 271 272 private DialogInterface.OnDismissListener mDialogListener = 273 new DialogInterface.OnDismissListener() { 274 public void onDismiss(DialogInterface d) { 275 processNextError(); 276 } 277 }; 278 private LinkedList<ErrorDialog> mQueuedErrors; 279 280 private void queueError(int err, String desc) { 281 if (mQueuedErrors == null) { 282 mQueuedErrors = new LinkedList<ErrorDialog>(); 283 } 284 for (ErrorDialog d : mQueuedErrors) { 285 if (d.mError == err) { 286 // Already saw a similar error, ignore the new one. 287 return; 288 } 289 } 290 ErrorDialog errDialog = new ErrorDialog( 291 err == WebViewClient.ERROR_FILE_NOT_FOUND ? 292 R.string.browserFrameFileErrorLabel : 293 R.string.browserFrameNetworkErrorLabel, 294 desc, err); 295 mQueuedErrors.addLast(errDialog); 296 297 // Show the dialog now if the queue was empty and it is in foreground 298 if (mQueuedErrors.size() == 1 && mInForeground) { 299 showError(errDialog); 300 } 301 } 302 303 private void showError(ErrorDialog errDialog) { 304 if (mInForeground) { 305 AlertDialog d = new AlertDialog.Builder(mActivity) 306 .setTitle(errDialog.mTitle) 307 .setMessage(errDialog.mDescription) 308 .setPositiveButton(R.string.ok, null) 309 .create(); 310 d.setOnDismissListener(mDialogListener); 311 d.show(); 312 } 313 } 314 315 // ------------------------------------------------------------------------- 316 // WebViewClient implementation for the main WebView 317 // ------------------------------------------------------------------------- 318 319 private final WebViewClient mWebViewClient = new WebViewClient() { 320 @Override 321 public void onPageStarted(WebView view, String url, Bitmap favicon) { 322 mInLoad = true; 323 mLoadStartTime = SystemClock.uptimeMillis(); 324 if (mVoiceSearchData != null 325 && !url.equals(mVoiceSearchData.mLastVoiceSearchUrl)) { 326 mVoiceSearchData = null; 327 if (mInForeground) { 328 mActivity.revertVoiceTitleBar(); 329 } 330 } 331 332 // We've started to load a new page. If there was a pending message 333 // to save a screenshot then we will now take the new page and save 334 // an incorrect screenshot. Therefore, remove any pending thumbnail 335 // messages from the queue. 336 mActivity.removeMessages(BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL, 337 view); 338 339 // If we start a touch icon load and then load a new page, we don't 340 // want to cancel the current touch icon loader. But, we do want to 341 // create a new one when the touch icon url is known. 342 if (mTouchIconLoader != null) { 343 mTouchIconLoader.mTab = null; 344 mTouchIconLoader = null; 345 } 346 347 // reset the error console 348 if (mErrorConsole != null) { 349 mErrorConsole.clearErrorMessages(); 350 if (mActivity.shouldShowErrorConsole()) { 351 mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE); 352 } 353 } 354 355 // update the bookmark database for favicon 356 if (favicon != null) { 357 BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity 358 .getContentResolver(), view.getOriginalUrl(), view 359 .getUrl(), favicon); 360 } 361 362 // reset sync timer to avoid sync starts during loading a page 363 CookieSyncManager.getInstance().resetSync(); 364 365 if (!mActivity.isNetworkUp()) { 366 view.setNetworkAvailable(false); 367 } 368 369 // finally update the UI in the activity if it is in the foreground 370 if (mInForeground) { 371 mActivity.onPageStarted(view, url, favicon); 372 } 373 } 374 375 @Override 376 public void onPageFinished(WebView view, String url) { 377 LogTag.logPageFinishedLoading( 378 url, SystemClock.uptimeMillis() - mLoadStartTime); 379 mInLoad = false; 380 381 if (mInForeground && !mActivity.didUserStopLoading() 382 || !mInForeground) { 383 // Only update the bookmark screenshot if the user did not 384 // cancel the load early. 385 mActivity.postMessage( 386 BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL, 0, 0, view, 387 500); 388 } 389 390 // finally update the UI in the activity if it is in the foreground 391 if (mInForeground) { 392 mActivity.onPageFinished(view, url); 393 } 394 } 395 396 // return true if want to hijack the url to let another app to handle it 397 @Override 398 public boolean shouldOverrideUrlLoading(WebView view, String url) { 399 if (mInForeground) { 400 return mActivity.shouldOverrideUrlLoading(view, url); 401 } else { 402 return false; 403 } 404 } 405 406 /** 407 * Updates the lock icon. This method is called when we discover another 408 * resource to be loaded for this page (for example, javascript). While 409 * we update the icon type, we do not update the lock icon itself until 410 * we are done loading, it is slightly more secure this way. 411 */ 412 @Override 413 public void onLoadResource(WebView view, String url) { 414 if (url != null && url.length() > 0) { 415 // It is only if the page claims to be secure that we may have 416 // to update the lock: 417 if (mLockIconType == BrowserActivity.LOCK_ICON_SECURE) { 418 // If NOT a 'safe' url, change the lock to mixed content! 419 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) 420 || URLUtil.isAboutUrl(url))) { 421 mLockIconType = BrowserActivity.LOCK_ICON_MIXED; 422 } 423 } 424 } 425 } 426 427 /** 428 * Show a dialog informing the user of the network error reported by 429 * WebCore if it is in the foreground. 430 */ 431 @Override 432 public void onReceivedError(WebView view, int errorCode, 433 String description, String failingUrl) { 434 if (errorCode != WebViewClient.ERROR_HOST_LOOKUP && 435 errorCode != WebViewClient.ERROR_CONNECT && 436 errorCode != WebViewClient.ERROR_BAD_URL && 437 errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME && 438 errorCode != WebViewClient.ERROR_FILE) { 439 queueError(errorCode, description); 440 } 441 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl 442 + " " + description); 443 444 // We need to reset the title after an error if it is in foreground. 445 if (mInForeground) { 446 mActivity.resetTitleAndRevertLockIcon(); 447 } 448 } 449 450 /** 451 * Check with the user if it is ok to resend POST data as the page they 452 * are trying to navigate to is the result of a POST. 453 */ 454 @Override 455 public void onFormResubmission(WebView view, final Message dontResend, 456 final Message resend) { 457 if (!mInForeground) { 458 dontResend.sendToTarget(); 459 return; 460 } 461 new AlertDialog.Builder(mActivity).setTitle( 462 R.string.browserFrameFormResubmitLabel).setMessage( 463 R.string.browserFrameFormResubmitMessage) 464 .setPositiveButton(R.string.ok, 465 new DialogInterface.OnClickListener() { 466 public void onClick(DialogInterface dialog, 467 int which) { 468 resend.sendToTarget(); 469 } 470 }).setNegativeButton(R.string.cancel, 471 new DialogInterface.OnClickListener() { 472 public void onClick(DialogInterface dialog, 473 int which) { 474 dontResend.sendToTarget(); 475 } 476 }).setOnCancelListener(new OnCancelListener() { 477 public void onCancel(DialogInterface dialog) { 478 dontResend.sendToTarget(); 479 } 480 }).show(); 481 } 482 483 /** 484 * Insert the url into the visited history database. 485 * @param url The url to be inserted. 486 * @param isReload True if this url is being reloaded. 487 * FIXME: Not sure what to do when reloading the page. 488 */ 489 @Override 490 public void doUpdateVisitedHistory(WebView view, String url, 491 boolean isReload) { 492 if (url.regionMatches(true, 0, "about:", 0, 6)) { 493 return; 494 } 495 // remove "client" before updating it to the history so that it wont 496 // show up in the auto-complete list. 497 int index = url.indexOf("client=ms-"); 498 if (index > 0 && url.contains(".google.")) { 499 int end = url.indexOf('&', index); 500 if (end > 0) { 501 url = url.substring(0, index) 502 .concat(url.substring(end + 1)); 503 } else { 504 // the url.charAt(index-1) should be either '?' or '&' 505 url = url.substring(0, index-1); 506 } 507 } 508 Browser.updateVisitedHistory(mActivity.getContentResolver(), url, 509 true); 510 WebIconDatabase.getInstance().retainIconForPageUrl(url); 511 } 512 513 /** 514 * Displays SSL error(s) dialog to the user. 515 */ 516 @Override 517 public void onReceivedSslError(final WebView view, 518 final SslErrorHandler handler, final SslError error) { 519 if (!mInForeground) { 520 handler.cancel(); 521 return; 522 } 523 if (BrowserSettings.getInstance().showSecurityWarnings()) { 524 final LayoutInflater factory = 525 LayoutInflater.from(mActivity); 526 final View warningsView = 527 factory.inflate(R.layout.ssl_warnings, null); 528 final LinearLayout placeholder = 529 (LinearLayout)warningsView.findViewById(R.id.placeholder); 530 531 if (error.hasError(SslError.SSL_UNTRUSTED)) { 532 LinearLayout ll = (LinearLayout)factory 533 .inflate(R.layout.ssl_warning, null); 534 ((TextView)ll.findViewById(R.id.warning)) 535 .setText(R.string.ssl_untrusted); 536 placeholder.addView(ll); 537 } 538 539 if (error.hasError(SslError.SSL_IDMISMATCH)) { 540 LinearLayout ll = (LinearLayout)factory 541 .inflate(R.layout.ssl_warning, null); 542 ((TextView)ll.findViewById(R.id.warning)) 543 .setText(R.string.ssl_mismatch); 544 placeholder.addView(ll); 545 } 546 547 if (error.hasError(SslError.SSL_EXPIRED)) { 548 LinearLayout ll = (LinearLayout)factory 549 .inflate(R.layout.ssl_warning, null); 550 ((TextView)ll.findViewById(R.id.warning)) 551 .setText(R.string.ssl_expired); 552 placeholder.addView(ll); 553 } 554 555 if (error.hasError(SslError.SSL_NOTYETVALID)) { 556 LinearLayout ll = (LinearLayout)factory 557 .inflate(R.layout.ssl_warning, null); 558 ((TextView)ll.findViewById(R.id.warning)) 559 .setText(R.string.ssl_not_yet_valid); 560 placeholder.addView(ll); 561 } 562 563 new AlertDialog.Builder(mActivity).setTitle( 564 R.string.security_warning).setIcon( 565 android.R.drawable.ic_dialog_alert).setView( 566 warningsView).setPositiveButton(R.string.ssl_continue, 567 new DialogInterface.OnClickListener() { 568 public void onClick(DialogInterface dialog, 569 int whichButton) { 570 handler.proceed(); 571 } 572 }).setNeutralButton(R.string.view_certificate, 573 new DialogInterface.OnClickListener() { 574 public void onClick(DialogInterface dialog, 575 int whichButton) { 576 mActivity.showSSLCertificateOnError(view, 577 handler, error); 578 } 579 }).setNegativeButton(R.string.cancel, 580 new DialogInterface.OnClickListener() { 581 public void onClick(DialogInterface dialog, 582 int whichButton) { 583 handler.cancel(); 584 mActivity.resetTitleAndRevertLockIcon(); 585 } 586 }).setOnCancelListener( 587 new DialogInterface.OnCancelListener() { 588 public void onCancel(DialogInterface dialog) { 589 handler.cancel(); 590 mActivity.resetTitleAndRevertLockIcon(); 591 } 592 }).show(); 593 } else { 594 handler.proceed(); 595 } 596 } 597 598 /** 599 * Handles an HTTP authentication request. 600 * 601 * @param handler The authentication handler 602 * @param host The host 603 * @param realm The realm 604 */ 605 @Override 606 public void onReceivedHttpAuthRequest(WebView view, 607 final HttpAuthHandler handler, final String host, 608 final String realm) { 609 String username = null; 610 String password = null; 611 612 boolean reuseHttpAuthUsernamePassword = handler 613 .useHttpAuthUsernamePassword(); 614 615 if (reuseHttpAuthUsernamePassword && mMainView != null) { 616 String[] credentials = mMainView.getHttpAuthUsernamePassword( 617 host, realm); 618 if (credentials != null && credentials.length == 2) { 619 username = credentials[0]; 620 password = credentials[1]; 621 } 622 } 623 624 if (username != null && password != null) { 625 handler.proceed(username, password); 626 } else { 627 if (mInForeground) { 628 mActivity.showHttpAuthentication(handler, host, realm, 629 null, null, null, 0); 630 } else { 631 handler.cancel(); 632 } 633 } 634 } 635 636 @Override 637 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { 638 if (!mInForeground) { 639 return false; 640 } 641 if (mActivity.isMenuDown()) { 642 // only check shortcut key when MENU is held 643 return mActivity.getWindow().isShortcutKey(event.getKeyCode(), 644 event); 645 } else { 646 return false; 647 } 648 } 649 650 @Override 651 public void onUnhandledKeyEvent(WebView view, KeyEvent event) { 652 if (!mInForeground) { 653 return; 654 } 655 if (event.isDown()) { 656 mActivity.onKeyDown(event.getKeyCode(), event); 657 } else { 658 mActivity.onKeyUp(event.getKeyCode(), event); 659 } 660 } 661 }; 662 663 // ------------------------------------------------------------------------- 664 // WebChromeClient implementation for the main WebView 665 // ------------------------------------------------------------------------- 666 667 private final WebChromeClient mWebChromeClient = new WebChromeClient() { 668 // Helper method to create a new tab or sub window. 669 private void createWindow(final boolean dialog, final Message msg) { 670 WebView.WebViewTransport transport = 671 (WebView.WebViewTransport) msg.obj; 672 if (dialog) { 673 createSubWindow(); 674 mActivity.attachSubWindow(Tab.this); 675 transport.setWebView(mSubView); 676 } else { 677 final Tab newTab = mActivity.openTabAndShow( 678 BrowserActivity.EMPTY_URL_DATA, false, null); 679 if (newTab != Tab.this) { 680 Tab.this.addChildTab(newTab); 681 } 682 transport.setWebView(newTab.getWebView()); 683 } 684 msg.sendToTarget(); 685 } 686 687 @Override 688 public boolean onCreateWindow(WebView view, final boolean dialog, 689 final boolean userGesture, final Message resultMsg) { 690 // only allow new window or sub window for the foreground case 691 if (!mInForeground) { 692 return false; 693 } 694 // Short-circuit if we can't create any more tabs or sub windows. 695 if (dialog && mSubView != null) { 696 new AlertDialog.Builder(mActivity) 697 .setTitle(R.string.too_many_subwindows_dialog_title) 698 .setIcon(android.R.drawable.ic_dialog_alert) 699 .setMessage(R.string.too_many_subwindows_dialog_message) 700 .setPositiveButton(R.string.ok, null) 701 .show(); 702 return false; 703 } else if (!mActivity.getTabControl().canCreateNewTab()) { 704 new AlertDialog.Builder(mActivity) 705 .setTitle(R.string.too_many_windows_dialog_title) 706 .setIcon(android.R.drawable.ic_dialog_alert) 707 .setMessage(R.string.too_many_windows_dialog_message) 708 .setPositiveButton(R.string.ok, null) 709 .show(); 710 return false; 711 } 712 713 // Short-circuit if this was a user gesture. 714 if (userGesture) { 715 createWindow(dialog, resultMsg); 716 return true; 717 } 718 719 // Allow the popup and create the appropriate window. 720 final AlertDialog.OnClickListener allowListener = 721 new AlertDialog.OnClickListener() { 722 public void onClick(DialogInterface d, 723 int which) { 724 createWindow(dialog, resultMsg); 725 } 726 }; 727 728 // Block the popup by returning a null WebView. 729 final AlertDialog.OnClickListener blockListener = 730 new AlertDialog.OnClickListener() { 731 public void onClick(DialogInterface d, int which) { 732 resultMsg.sendToTarget(); 733 } 734 }; 735 736 // Build a confirmation dialog to display to the user. 737 final AlertDialog d = 738 new AlertDialog.Builder(mActivity) 739 .setTitle(R.string.attention) 740 .setIcon(android.R.drawable.ic_dialog_alert) 741 .setMessage(R.string.popup_window_attempt) 742 .setPositiveButton(R.string.allow, allowListener) 743 .setNegativeButton(R.string.block, blockListener) 744 .setCancelable(false) 745 .create(); 746 747 // Show the confirmation dialog. 748 d.show(); 749 return true; 750 } 751 752 @Override 753 public void onRequestFocus(WebView view) { 754 if (!mInForeground) { 755 mActivity.switchToTab(mActivity.getTabControl().getTabIndex( 756 Tab.this)); 757 } 758 } 759 760 @Override 761 public void onCloseWindow(WebView window) { 762 if (mParentTab != null) { 763 // JavaScript can only close popup window. 764 if (mInForeground) { 765 mActivity.switchToTab(mActivity.getTabControl() 766 .getTabIndex(mParentTab)); 767 } 768 mActivity.closeTab(Tab.this); 769 } 770 } 771 772 @Override 773 public void onProgressChanged(WebView view, int newProgress) { 774 if (newProgress == 100) { 775 // sync cookies and cache promptly here. 776 CookieSyncManager.getInstance().sync(); 777 } 778 if (mInForeground) { 779 mActivity.onProgressChanged(view, newProgress); 780 } 781 } 782 783 @Override 784 public void onReceivedTitle(WebView view, String title) { 785 String url = view.getUrl(); 786 if (mInForeground) { 787 // here, if url is null, we want to reset the title 788 mActivity.setUrlTitle(url, title); 789 } 790 if (url == null || 791 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) { 792 return; 793 } 794 // See if we can find the current url in our history database and 795 // add the new title to it. 796 if (url.startsWith("http://www.")) { 797 url = url.substring(11); 798 } else if (url.startsWith("http://")) { 799 url = url.substring(4); 800 } 801 try { 802 final ContentResolver cr = mActivity.getContentResolver(); 803 url = "%" + url; 804 String [] selArgs = new String[] { url }; 805 String where = Browser.BookmarkColumns.URL + " LIKE ? AND " 806 + Browser.BookmarkColumns.BOOKMARK + " = 0"; 807 Cursor c = cr.query(Browser.BOOKMARKS_URI, 808 Browser.HISTORY_PROJECTION, where, selArgs, null); 809 if (c.moveToFirst()) { 810 // Current implementation of database only has one entry per 811 // url. 812 ContentValues map = new ContentValues(); 813 map.put(Browser.BookmarkColumns.TITLE, title); 814 cr.update(Browser.BOOKMARKS_URI, map, "_id = " 815 + c.getInt(0), null); 816 } 817 c.close(); 818 } catch (IllegalStateException e) { 819 Log.e(LOGTAG, "Tab onReceived title", e); 820 } catch (SQLiteException ex) { 821 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex); 822 } 823 } 824 825 @Override 826 public void onReceivedIcon(WebView view, Bitmap icon) { 827 if (icon != null) { 828 BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity 829 .getContentResolver(), view.getOriginalUrl(), view 830 .getUrl(), icon); 831 } 832 if (mInForeground) { 833 mActivity.setFavicon(icon); 834 } 835 } 836 837 @Override 838 public void onReceivedTouchIconUrl(WebView view, String url, 839 boolean precomposed) { 840 final ContentResolver cr = mActivity.getContentResolver(); 841 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(cr, 842 view.getOriginalUrl(), view.getUrl(), true); 843 if (c != null) { 844 if (c.getCount() > 0) { 845 // Let precomposed icons take precedence over non-composed 846 // icons. 847 if (precomposed && mTouchIconLoader != null) { 848 mTouchIconLoader.cancel(false); 849 mTouchIconLoader = null; 850 } 851 // Have only one async task at a time. 852 if (mTouchIconLoader == null) { 853 mTouchIconLoader = new DownloadTouchIcon(Tab.this, cr, 854 c, view); 855 mTouchIconLoader.execute(url); 856 } 857 } else { 858 c.close(); 859 } 860 } 861 } 862 863 @Override 864 public void onShowCustomView(View view, 865 WebChromeClient.CustomViewCallback callback) { 866 if (mInForeground) mActivity.onShowCustomView(view, callback); 867 } 868 869 @Override 870 public void onHideCustomView() { 871 if (mInForeground) mActivity.onHideCustomView(); 872 } 873 874 /** 875 * The origin has exceeded its database quota. 876 * @param url the URL that exceeded the quota 877 * @param databaseIdentifier the identifier of the database on which the 878 * transaction that caused the quota overflow was run 879 * @param currentQuota the current quota for the origin. 880 * @param estimatedSize the estimated size of the database. 881 * @param totalUsedQuota is the sum of all origins' quota. 882 * @param quotaUpdater The callback to run when a decision to allow or 883 * deny quota has been made. Don't forget to call this! 884 */ 885 @Override 886 public void onExceededDatabaseQuota(String url, 887 String databaseIdentifier, long currentQuota, long estimatedSize, 888 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { 889 BrowserSettings.getInstance().getWebStorageSizeManager() 890 .onExceededDatabaseQuota(url, databaseIdentifier, 891 currentQuota, estimatedSize, totalUsedQuota, 892 quotaUpdater); 893 } 894 895 /** 896 * The Application Cache has exceeded its max size. 897 * @param spaceNeeded is the amount of disk space that would be needed 898 * in order for the last appcache operation to succeed. 899 * @param totalUsedQuota is the sum of all origins' quota. 900 * @param quotaUpdater A callback to inform the WebCore thread that a 901 * new app cache size is available. This callback must always 902 * be executed at some point to ensure that the sleeping 903 * WebCore thread is woken up. 904 */ 905 @Override 906 public void onReachedMaxAppCacheSize(long spaceNeeded, 907 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { 908 BrowserSettings.getInstance().getWebStorageSizeManager() 909 .onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota, 910 quotaUpdater); 911 } 912 913 /** 914 * Instructs the browser to show a prompt to ask the user to set the 915 * Geolocation permission state for the specified origin. 916 * @param origin The origin for which Geolocation permissions are 917 * requested. 918 * @param callback The callback to call once the user has set the 919 * Geolocation permission state. 920 */ 921 @Override 922 public void onGeolocationPermissionsShowPrompt(String origin, 923 GeolocationPermissions.Callback callback) { 924 if (mInForeground) { 925 mGeolocationPermissionsPrompt.show(origin, callback); 926 } 927 } 928 929 /** 930 * Instructs the browser to hide the Geolocation permissions prompt. 931 */ 932 @Override 933 public void onGeolocationPermissionsHidePrompt() { 934 if (mInForeground) { 935 mGeolocationPermissionsPrompt.hide(); 936 } 937 } 938 939 /* Adds a JavaScript error message to the system log and if the JS 940 * console is enabled in the about:debug options, to that console 941 * also. 942 * @param message The error message to report. 943 * @param lineNumber The line number of the error. 944 * @param sourceID The name of the source file that caused the error. 945 */ 946 @Override 947 public void onConsoleMessage(String message, int lineNumber, 948 String sourceID) { 949 if (mInForeground) { 950 // call getErrorConsole(true) so it will create one if needed 951 ErrorConsoleView errorConsole = getErrorConsole(true); 952 errorConsole.addErrorMessage(message, sourceID, lineNumber); 953 if (mActivity.shouldShowErrorConsole() 954 && errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) { 955 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); 956 } 957 } 958 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" 959 + lineNumber); 960 } 961 962 /** 963 * Ask the browser for an icon to represent a <video> element. 964 * This icon will be used if the Web page did not specify a poster attribute. 965 * @return Bitmap The icon or null if no such icon is available. 966 */ 967 @Override 968 public Bitmap getDefaultVideoPoster() { 969 if (mInForeground) { 970 return mActivity.getDefaultVideoPoster(); 971 } 972 return null; 973 } 974 975 /** 976 * Ask the host application for a custom progress view to show while 977 * a <video> is loading. 978 * @return View The progress view. 979 */ 980 @Override 981 public View getVideoLoadingProgressView() { 982 if (mInForeground) { 983 return mActivity.getVideoLoadingProgressView(); 984 } 985 return null; 986 } 987 988 @Override 989 public void openFileChooser(ValueCallback<Uri> uploadMsg) { 990 if (mInForeground) { 991 mActivity.openFileChooser(uploadMsg); 992 } else { 993 uploadMsg.onReceiveValue(null); 994 } 995 } 996 997 /** 998 * Deliver a list of already-visited URLs 999 */ 1000 @Override 1001 public void getVisitedHistory(final ValueCallback<String[]> callback) { 1002 AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() { 1003 public String[] doInBackground(Void... unused) { 1004 return Browser.getVisitedHistory(mActivity 1005 .getContentResolver()); 1006 } 1007 public void onPostExecute(String[] result) { 1008 callback.onReceiveValue(result); 1009 }; 1010 }; 1011 task.execute(); 1012 }; 1013 }; 1014 1015 // ------------------------------------------------------------------------- 1016 // WebViewClient implementation for the sub window 1017 // ------------------------------------------------------------------------- 1018 1019 // Subclass of WebViewClient used in subwindows to notify the main 1020 // WebViewClient of certain WebView activities. 1021 private static class SubWindowClient extends WebViewClient { 1022 // The main WebViewClient. 1023 private final WebViewClient mClient; 1024 1025 SubWindowClient(WebViewClient client) { 1026 mClient = client; 1027 } 1028 @Override 1029 public void doUpdateVisitedHistory(WebView view, String url, 1030 boolean isReload) { 1031 mClient.doUpdateVisitedHistory(view, url, isReload); 1032 } 1033 @Override 1034 public boolean shouldOverrideUrlLoading(WebView view, String url) { 1035 return mClient.shouldOverrideUrlLoading(view, url); 1036 } 1037 @Override 1038 public void onReceivedSslError(WebView view, SslErrorHandler handler, 1039 SslError error) { 1040 mClient.onReceivedSslError(view, handler, error); 1041 } 1042 @Override 1043 public void onReceivedHttpAuthRequest(WebView view, 1044 HttpAuthHandler handler, String host, String realm) { 1045 mClient.onReceivedHttpAuthRequest(view, handler, host, realm); 1046 } 1047 @Override 1048 public void onFormResubmission(WebView view, Message dontResend, 1049 Message resend) { 1050 mClient.onFormResubmission(view, dontResend, resend); 1051 } 1052 @Override 1053 public void onReceivedError(WebView view, int errorCode, 1054 String description, String failingUrl) { 1055 mClient.onReceivedError(view, errorCode, description, failingUrl); 1056 } 1057 @Override 1058 public boolean shouldOverrideKeyEvent(WebView view, 1059 android.view.KeyEvent event) { 1060 return mClient.shouldOverrideKeyEvent(view, event); 1061 } 1062 @Override 1063 public void onUnhandledKeyEvent(WebView view, 1064 android.view.KeyEvent event) { 1065 mClient.onUnhandledKeyEvent(view, event); 1066 } 1067 } 1068 1069 // ------------------------------------------------------------------------- 1070 // WebChromeClient implementation for the sub window 1071 // ------------------------------------------------------------------------- 1072 1073 private class SubWindowChromeClient extends WebChromeClient { 1074 // The main WebChromeClient. 1075 private final WebChromeClient mClient; 1076 1077 SubWindowChromeClient(WebChromeClient client) { 1078 mClient = client; 1079 } 1080 @Override 1081 public void onProgressChanged(WebView view, int newProgress) { 1082 mClient.onProgressChanged(view, newProgress); 1083 } 1084 @Override 1085 public boolean onCreateWindow(WebView view, boolean dialog, 1086 boolean userGesture, android.os.Message resultMsg) { 1087 return mClient.onCreateWindow(view, dialog, userGesture, resultMsg); 1088 } 1089 @Override 1090 public void onCloseWindow(WebView window) { 1091 if (window != mSubView) { 1092 Log.e(LOGTAG, "Can't close the window"); 1093 } 1094 mActivity.dismissSubWindow(Tab.this); 1095 } 1096 } 1097 1098 // ------------------------------------------------------------------------- 1099 1100 // Construct a new tab 1101 Tab(BrowserActivity activity, WebView w, boolean closeOnExit, String appId, 1102 String url) { 1103 mActivity = activity; 1104 mCloseOnExit = closeOnExit; 1105 mAppId = appId; 1106 mOriginalUrl = url; 1107 mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE; 1108 mPrevLockIconType = BrowserActivity.LOCK_ICON_UNSECURE; 1109 mInLoad = false; 1110 mInForeground = false; 1111 1112 mInflateService = LayoutInflater.from(activity); 1113 1114 // The tab consists of a container view, which contains the main 1115 // WebView, as well as any other UI elements associated with the tab. 1116 mContainer = mInflateService.inflate(R.layout.tab, null); 1117 1118 mGeolocationPermissionsPrompt = 1119 (GeolocationPermissionsPrompt) mContainer.findViewById( 1120 R.id.geolocation_permissions_prompt); 1121 1122 setWebView(w); 1123 } 1124 1125 /** 1126 * Sets the WebView for this tab, correctly removing the old WebView from 1127 * the container view. 1128 */ 1129 void setWebView(WebView w) { 1130 if (mMainView == w) { 1131 return; 1132 } 1133 // If the WebView is changing, the page will be reloaded, so any ongoing 1134 // Geolocation permission requests are void. 1135 mGeolocationPermissionsPrompt.hide(); 1136 1137 // Just remove the old one. 1138 FrameLayout wrapper = 1139 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper); 1140 wrapper.removeView(mMainView); 1141 1142 // set the new one 1143 mMainView = w; 1144 // attached the WebViewClient and WebChromeClient 1145 if (mMainView != null) { 1146 mMainView.setWebViewClient(mWebViewClient); 1147 mMainView.setWebChromeClient(mWebChromeClient); 1148 } 1149 } 1150 1151 /** 1152 * Destroy the tab's main WebView and subWindow if any 1153 */ 1154 void destroy() { 1155 if (mMainView != null) { 1156 dismissSubWindow(); 1157 BrowserSettings.getInstance().deleteObserver(mMainView.getSettings()); 1158 // save the WebView to call destroy() after detach it from the tab 1159 WebView webView = mMainView; 1160 setWebView(null); 1161 webView.destroy(); 1162 } 1163 } 1164 1165 /** 1166 * Remove the tab from the parent 1167 */ 1168 void removeFromTree() { 1169 // detach the children 1170 if (mChildTabs != null) { 1171 for(Tab t : mChildTabs) { 1172 t.setParentTab(null); 1173 } 1174 } 1175 // remove itself from the parent list 1176 if (mParentTab != null) { 1177 mParentTab.mChildTabs.remove(this); 1178 } 1179 } 1180 1181 /** 1182 * Create a new subwindow unless a subwindow already exists. 1183 * @return True if a new subwindow was created. False if one already exists. 1184 */ 1185 boolean createSubWindow() { 1186 if (mSubView == null) { 1187 mSubViewContainer = mInflateService.inflate( 1188 R.layout.browser_subwindow, null); 1189 mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview); 1190 // use trackball directly 1191 mSubView.setMapTrackballToArrowKeys(false); 1192 mSubView.setWebViewClient(new SubWindowClient(mWebViewClient)); 1193 mSubView.setWebChromeClient(new SubWindowChromeClient( 1194 mWebChromeClient)); 1195 mSubView.setDownloadListener(mActivity); 1196 mSubView.setOnCreateContextMenuListener(mActivity); 1197 final BrowserSettings s = BrowserSettings.getInstance(); 1198 s.addObserver(mSubView.getSettings()).update(s, null); 1199 final ImageButton cancel = (ImageButton) mSubViewContainer 1200 .findViewById(R.id.subwindow_close); 1201 cancel.setOnClickListener(new OnClickListener() { 1202 public void onClick(View v) { 1203 mSubView.getWebChromeClient().onCloseWindow(mSubView); 1204 } 1205 }); 1206 return true; 1207 } 1208 return false; 1209 } 1210 1211 /** 1212 * Dismiss the subWindow for the tab. 1213 */ 1214 void dismissSubWindow() { 1215 if (mSubView != null) { 1216 BrowserSettings.getInstance().deleteObserver( 1217 mSubView.getSettings()); 1218 mSubView.destroy(); 1219 mSubView = null; 1220 mSubViewContainer = null; 1221 } 1222 } 1223 1224 /** 1225 * Attach the sub window to the content view. 1226 */ 1227 void attachSubWindow(ViewGroup content) { 1228 if (mSubView != null) { 1229 content.addView(mSubViewContainer, 1230 BrowserActivity.COVER_SCREEN_PARAMS); 1231 } 1232 } 1233 1234 /** 1235 * Remove the sub window from the content view. 1236 */ 1237 void removeSubWindow(ViewGroup content) { 1238 if (mSubView != null) { 1239 content.removeView(mSubViewContainer); 1240 } 1241 } 1242 1243 /** 1244 * This method attaches both the WebView and any sub window to the 1245 * given content view. 1246 */ 1247 void attachTabToContentView(ViewGroup content) { 1248 if (mMainView == null) { 1249 return; 1250 } 1251 1252 // Attach the WebView to the container and then attach the 1253 // container to the content view. 1254 FrameLayout wrapper = 1255 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper); 1256 wrapper.addView(mMainView); 1257 content.addView(mContainer, BrowserActivity.COVER_SCREEN_PARAMS); 1258 attachSubWindow(content); 1259 } 1260 1261 /** 1262 * Remove the WebView and any sub window from the given content view. 1263 */ 1264 void removeTabFromContentView(ViewGroup content) { 1265 if (mMainView == null) { 1266 return; 1267 } 1268 1269 // Remove the container from the content and then remove the 1270 // WebView from the container. This will trigger a focus change 1271 // needed by WebView. 1272 FrameLayout wrapper = 1273 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper); 1274 wrapper.removeView(mMainView); 1275 content.removeView(mContainer); 1276 removeSubWindow(content); 1277 } 1278 1279 /** 1280 * Set the parent tab of this tab. 1281 */ 1282 void setParentTab(Tab parent) { 1283 mParentTab = parent; 1284 // This tab may have been freed due to low memory. If that is the case, 1285 // the parent tab index is already saved. If we are changing that index 1286 // (most likely due to removing the parent tab) we must update the 1287 // parent tab index in the saved Bundle. 1288 if (mSavedState != null) { 1289 if (parent == null) { 1290 mSavedState.remove(PARENTTAB); 1291 } else { 1292 mSavedState.putInt(PARENTTAB, mActivity.getTabControl() 1293 .getTabIndex(parent)); 1294 } 1295 } 1296 } 1297 1298 /** 1299 * When a Tab is created through the content of another Tab, then we 1300 * associate the Tabs. 1301 * @param child the Tab that was created from this Tab 1302 */ 1303 void addChildTab(Tab child) { 1304 if (mChildTabs == null) { 1305 mChildTabs = new Vector<Tab>(); 1306 } 1307 mChildTabs.add(child); 1308 child.setParentTab(this); 1309 } 1310 1311 Vector<Tab> getChildTabs() { 1312 return mChildTabs; 1313 } 1314 1315 void resume() { 1316 if (mMainView != null) { 1317 mMainView.onResume(); 1318 if (mSubView != null) { 1319 mSubView.onResume(); 1320 } 1321 } 1322 } 1323 1324 void pause() { 1325 if (mMainView != null) { 1326 mMainView.onPause(); 1327 if (mSubView != null) { 1328 mSubView.onPause(); 1329 } 1330 } 1331 } 1332 1333 void putInForeground() { 1334 mInForeground = true; 1335 resume(); 1336 mMainView.setOnCreateContextMenuListener(mActivity); 1337 if (mSubView != null) { 1338 mSubView.setOnCreateContextMenuListener(mActivity); 1339 } 1340 // Show the pending error dialog if the queue is not empty 1341 if (mQueuedErrors != null && mQueuedErrors.size() > 0) { 1342 showError(mQueuedErrors.getFirst()); 1343 } 1344 } 1345 1346 void putInBackground() { 1347 mInForeground = false; 1348 pause(); 1349 mMainView.setOnCreateContextMenuListener(null); 1350 if (mSubView != null) { 1351 mSubView.setOnCreateContextMenuListener(null); 1352 } 1353 } 1354 1355 /** 1356 * Return the top window of this tab; either the subwindow if it is not 1357 * null or the main window. 1358 * @return The top window of this tab. 1359 */ 1360 WebView getTopWindow() { 1361 if (mSubView != null) { 1362 return mSubView; 1363 } 1364 return mMainView; 1365 } 1366 1367 /** 1368 * Return the main window of this tab. Note: if a tab is freed in the 1369 * background, this can return null. It is only guaranteed to be 1370 * non-null for the current tab. 1371 * @return The main WebView of this tab. 1372 */ 1373 WebView getWebView() { 1374 return mMainView; 1375 } 1376 1377 /** 1378 * Return the subwindow of this tab or null if there is no subwindow. 1379 * @return The subwindow of this tab or null. 1380 */ 1381 WebView getSubWebView() { 1382 return mSubView; 1383 } 1384 1385 /** 1386 * @return The geolocation permissions prompt for this tab. 1387 */ 1388 GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() { 1389 return mGeolocationPermissionsPrompt; 1390 } 1391 1392 /** 1393 * @return The application id string 1394 */ 1395 String getAppId() { 1396 return mAppId; 1397 } 1398 1399 /** 1400 * Set the application id string 1401 * @param id 1402 */ 1403 void setAppId(String id) { 1404 mAppId = id; 1405 } 1406 1407 /** 1408 * @return The original url associated with this Tab 1409 */ 1410 String getOriginalUrl() { 1411 return mOriginalUrl; 1412 } 1413 1414 /** 1415 * Set the original url associated with this tab 1416 */ 1417 void setOriginalUrl(String url) { 1418 mOriginalUrl = url; 1419 } 1420 1421 /** 1422 * Get the url of this tab. Valid after calling populatePickerData, but 1423 * before calling wipePickerData, or if the webview has been destroyed. 1424 * @return The WebView's url or null. 1425 */ 1426 String getUrl() { 1427 if (mPickerData != null) { 1428 return mPickerData.mUrl; 1429 } 1430 return null; 1431 } 1432 1433 /** 1434 * Get the title of this tab. Valid after calling populatePickerData, but 1435 * before calling wipePickerData, or if the webview has been destroyed. If 1436 * the url has no title, use the url instead. 1437 * @return The WebView's title (or url) or null. 1438 */ 1439 String getTitle() { 1440 if (mPickerData != null) { 1441 return mPickerData.mTitle; 1442 } 1443 return null; 1444 } 1445 1446 /** 1447 * Get the favicon of this tab. Valid after calling populatePickerData, but 1448 * before calling wipePickerData, or if the webview has been destroyed. 1449 * @return The WebView's favicon or null. 1450 */ 1451 Bitmap getFavicon() { 1452 if (mPickerData != null) { 1453 return mPickerData.mFavicon; 1454 } 1455 return null; 1456 } 1457 1458 /** 1459 * Return the tab's error console. Creates the console if createIfNEcessary 1460 * is true and we haven't already created the console. 1461 * @param createIfNecessary Flag to indicate if the console should be 1462 * created if it has not been already. 1463 * @return The tab's error console, or null if one has not been created and 1464 * createIfNecessary is false. 1465 */ 1466 ErrorConsoleView getErrorConsole(boolean createIfNecessary) { 1467 if (createIfNecessary && mErrorConsole == null) { 1468 mErrorConsole = new ErrorConsoleView(mActivity); 1469 mErrorConsole.setWebView(mMainView); 1470 } 1471 return mErrorConsole; 1472 } 1473 1474 /** 1475 * If this Tab was created through another Tab, then this method returns 1476 * that Tab. 1477 * @return the Tab parent or null 1478 */ 1479 public Tab getParentTab() { 1480 return mParentTab; 1481 } 1482 1483 /** 1484 * Return whether this tab should be closed when it is backing out of the 1485 * first page. 1486 * @return TRUE if this tab should be closed when exit. 1487 */ 1488 boolean closeOnExit() { 1489 return mCloseOnExit; 1490 } 1491 1492 /** 1493 * Saves the current lock-icon state before resetting the lock icon. If we 1494 * have an error, we may need to roll back to the previous state. 1495 */ 1496 void resetLockIcon(String url) { 1497 mPrevLockIconType = mLockIconType; 1498 mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE; 1499 if (URLUtil.isHttpsUrl(url)) { 1500 mLockIconType = BrowserActivity.LOCK_ICON_SECURE; 1501 } 1502 } 1503 1504 /** 1505 * Reverts the lock-icon state to the last saved state, for example, if we 1506 * had an error, and need to cancel the load. 1507 */ 1508 void revertLockIcon() { 1509 mLockIconType = mPrevLockIconType; 1510 } 1511 1512 /** 1513 * @return The tab's lock icon type. 1514 */ 1515 int getLockIconType() { 1516 return mLockIconType; 1517 } 1518 1519 /** 1520 * @return TRUE if onPageStarted is called while onPageFinished is not 1521 * called yet. 1522 */ 1523 boolean inLoad() { 1524 return mInLoad; 1525 } 1526 1527 // force mInLoad to be false. This should only be called before closing the 1528 // tab to ensure BrowserActivity's pauseWebViewTimers() is called correctly. 1529 void clearInLoad() { 1530 mInLoad = false; 1531 } 1532 1533 void populatePickerData() { 1534 if (mMainView == null) { 1535 populatePickerDataFromSavedState(); 1536 return; 1537 } 1538 1539 // FIXME: The only place we cared about subwindow was for 1540 // bookmarking (i.e. not when saving state). Was this deliberate? 1541 final WebBackForwardList list = mMainView.copyBackForwardList(); 1542 final WebHistoryItem item = list != null ? list.getCurrentItem() : null; 1543 populatePickerData(item); 1544 } 1545 1546 // Populate the picker data using the given history item and the current top 1547 // WebView. 1548 private void populatePickerData(WebHistoryItem item) { 1549 mPickerData = new PickerData(); 1550 if (item != null) { 1551 mPickerData.mUrl = item.getUrl(); 1552 mPickerData.mTitle = item.getTitle(); 1553 mPickerData.mFavicon = item.getFavicon(); 1554 if (mPickerData.mTitle == null) { 1555 mPickerData.mTitle = mPickerData.mUrl; 1556 } 1557 } 1558 } 1559 1560 // Create the PickerData and populate it using the saved state of the tab. 1561 void populatePickerDataFromSavedState() { 1562 if (mSavedState == null) { 1563 return; 1564 } 1565 mPickerData = new PickerData(); 1566 mPickerData.mUrl = mSavedState.getString(CURRURL); 1567 mPickerData.mTitle = mSavedState.getString(CURRTITLE); 1568 } 1569 1570 void clearPickerData() { 1571 mPickerData = null; 1572 } 1573 1574 /** 1575 * Get the saved state bundle. 1576 * @return 1577 */ 1578 Bundle getSavedState() { 1579 return mSavedState; 1580 } 1581 1582 /** 1583 * Set the saved state. 1584 */ 1585 void setSavedState(Bundle state) { 1586 mSavedState = state; 1587 } 1588 1589 /** 1590 * @return TRUE if succeed in saving the state. 1591 */ 1592 boolean saveState() { 1593 // If the WebView is null it means we ran low on memory and we already 1594 // stored the saved state in mSavedState. 1595 if (mMainView == null) { 1596 return mSavedState != null; 1597 } 1598 1599 mSavedState = new Bundle(); 1600 final WebBackForwardList list = mMainView.saveState(mSavedState); 1601 if (list != null) { 1602 final File f = new File(mActivity.getTabControl().getThumbnailDir(), 1603 mMainView.hashCode() + "_pic.save"); 1604 if (mMainView.savePicture(mSavedState, f)) { 1605 mSavedState.putString(CURRPICTURE, f.getPath()); 1606 } 1607 } 1608 1609 // Store some extra info for displaying the tab in the picker. 1610 final WebHistoryItem item = list != null ? list.getCurrentItem() : null; 1611 populatePickerData(item); 1612 1613 if (mPickerData.mUrl != null) { 1614 mSavedState.putString(CURRURL, mPickerData.mUrl); 1615 } 1616 if (mPickerData.mTitle != null) { 1617 mSavedState.putString(CURRTITLE, mPickerData.mTitle); 1618 } 1619 mSavedState.putBoolean(CLOSEONEXIT, mCloseOnExit); 1620 if (mAppId != null) { 1621 mSavedState.putString(APPID, mAppId); 1622 } 1623 if (mOriginalUrl != null) { 1624 mSavedState.putString(ORIGINALURL, mOriginalUrl); 1625 } 1626 // Remember the parent tab so the relationship can be restored. 1627 if (mParentTab != null) { 1628 mSavedState.putInt(PARENTTAB, mActivity.getTabControl().getTabIndex( 1629 mParentTab)); 1630 } 1631 return true; 1632 } 1633 1634 /* 1635 * Restore the state of the tab. 1636 */ 1637 boolean restoreState(Bundle b) { 1638 if (b == null) { 1639 return false; 1640 } 1641 // Restore the internal state even if the WebView fails to restore. 1642 // This will maintain the app id, original url and close-on-exit values. 1643 mSavedState = null; 1644 mPickerData = null; 1645 mCloseOnExit = b.getBoolean(CLOSEONEXIT); 1646 mAppId = b.getString(APPID); 1647 mOriginalUrl = b.getString(ORIGINALURL); 1648 1649 final WebBackForwardList list = mMainView.restoreState(b); 1650 if (list == null) { 1651 return false; 1652 } 1653 if (b.containsKey(CURRPICTURE)) { 1654 final File f = new File(b.getString(CURRPICTURE)); 1655 mMainView.restorePicture(b, f); 1656 f.delete(); 1657 } 1658 return true; 1659 } 1660} 1661