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 17 package com.android.browser; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.content.ContentResolver; 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.DialogInterface.OnCancelListener; 26 import android.graphics.Bitmap; 27 import android.graphics.Bitmap.CompressFormat; 28 import android.graphics.BitmapFactory; 29 import android.graphics.Canvas; 30 import android.graphics.Color; 31 import android.graphics.Paint; 32 import android.graphics.Picture; 33 import android.graphics.PorterDuff; 34 import android.graphics.PorterDuffXfermode; 35 import android.net.Uri; 36 import android.net.http.SslError; 37 import android.os.Bundle; 38 import android.os.Handler; 39 import android.os.Message; 40 import android.os.SystemClock; 41 import android.security.KeyChain; 42 import android.security.KeyChainAliasCallback; 43 import android.text.TextUtils; 44 import android.util.Log; 45 import android.view.KeyEvent; 46 import android.view.LayoutInflater; 47 import android.view.View; 48 import android.view.ViewStub; 49 import android.webkit.ClientCertRequest; 50 import android.webkit.ConsoleMessage; 51 import android.webkit.CookieManager; 52 import android.webkit.GeolocationPermissions; 53 import android.webkit.GeolocationPermissions.Callback; 54 import android.webkit.HttpAuthHandler; 55 import android.webkit.PermissionRequest; 56 import android.webkit.SslErrorHandler; 57 import android.webkit.URLUtil; 58 import android.webkit.ValueCallback; 59 import android.webkit.WebBackForwardList; 60 import android.webkit.WebChromeClient; 61 import android.webkit.WebChromeClient.FileChooserParams; 62 import android.webkit.WebHistoryItem; 63 import android.webkit.WebResourceResponse; 64 import android.webkit.WebStorage; 65 import android.webkit.WebView; 66 import android.webkit.WebView.PictureListener; 67 import android.webkit.WebViewClient; 68 import android.widget.CheckBox; 69 import android.widget.Toast; 70 71 import com.android.browser.TabControl.OnThumbnailUpdatedListener; 72 import com.android.browser.homepages.HomeProvider; 73 import com.android.browser.provider.SnapshotProvider.Snapshots; 74 75 import java.io.ByteArrayInputStream; 76 import java.io.ByteArrayOutputStream; 77 import java.io.File; 78 import java.io.IOException; 79 import java.io.OutputStream; 80 import java.nio.ByteBuffer; 81 import java.security.Principal; 82 import java.util.LinkedList; 83 import java.util.Map; 84 import java.util.UUID; 85 import java.util.Vector; 86 import java.util.regex.Pattern; 87 import java.util.zip.GZIPOutputStream; 88 89 /** 90 * Class for maintaining Tabs with a main WebView and a subwindow. 91 */ 92 class Tab implements PictureListener { 93 94 // Log Tag 95 private static final String LOGTAG = "Tab"; 96 private static final boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED; 97 // Special case the logtag for messages for the Console to make it easier to 98 // filter them and match the logtag used for these messages in older versions 99 // of the browser. 100 private static final String CONSOLE_LOGTAG = "browser"; 101 102 private static final int MSG_CAPTURE = 42; 103 private static final int CAPTURE_DELAY = 100; 104 private static final int INITIAL_PROGRESS = 5; 105 106 private static final String RESTRICTED = "<html><body>not allowed</body></html>"; 107 108 private static Bitmap sDefaultFavicon; 109 110 private static Paint sAlphaPaint = new Paint(); 111 static { sAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR))112 sAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 113 sAlphaPaint.setColor(Color.TRANSPARENT); 114 } 115 116 public enum SecurityState { 117 // The page's main resource does not use SSL. Note that we use this 118 // state irrespective of the SSL authentication state of sub-resources. 119 SECURITY_STATE_NOT_SECURE, 120 // The page's main resource uses SSL and the certificate is good. The 121 // same is true of all sub-resources. 122 SECURITY_STATE_SECURE, 123 // The page's main resource uses SSL and the certificate is good, but 124 // some sub-resources either do not use SSL or have problems with their 125 // certificates. 126 SECURITY_STATE_MIXED, 127 // The page's main resource uses SSL but there is a problem with its 128 // certificate. 129 SECURITY_STATE_BAD_CERTIFICATE, 130 } 131 132 Context mContext; 133 protected WebViewController mWebViewController; 134 135 // The tab ID 136 private long mId = -1; 137 138 // The Geolocation permissions prompt 139 private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt; 140 // The permissions prompt 141 private PermissionsPrompt mPermissionsPrompt; 142 // Main WebView wrapper 143 private View mContainer; 144 // Main WebView 145 private WebView mMainView; 146 // Subwindow container 147 private View mSubViewContainer; 148 // Subwindow WebView 149 private WebView mSubView; 150 // Saved bundle for when we are running low on memory. It contains the 151 // information needed to restore the WebView if the user goes back to the 152 // tab. 153 private Bundle mSavedState; 154 // Parent Tab. This is the Tab that created this Tab, or null if the Tab was 155 // created by the UI 156 private Tab mParent; 157 // Tab that constructed by this Tab. This is used when this Tab is 158 // destroyed, it clears all mParentTab values in the children. 159 private Vector<Tab> mChildren; 160 // If true, the tab is in the foreground of the current activity. 161 private boolean mInForeground; 162 // If true, the tab is in page loading state (after onPageStarted, 163 // before onPageFinsihed) 164 private boolean mInPageLoad; 165 private boolean mDisableOverrideUrlLoading; 166 // The last reported progress of the current page 167 private int mPageLoadProgress; 168 // The time the load started, used to find load page time 169 private long mLoadStartTime; 170 // Application identifier used to find tabs that another application wants 171 // to reuse. 172 private String mAppId; 173 // flag to indicate if tab should be closed on back 174 private boolean mCloseOnBack; 175 // Keep the original url around to avoid killing the old WebView if the url 176 // has not changed. 177 // Error console for the tab 178 private ErrorConsoleView mErrorConsole; 179 // The listener that gets invoked when a download is started from the 180 // mMainView 181 private final BrowserDownloadListener mDownloadListener; 182 // Listener used to know when we move forward or back in the history list. 183 private final WebBackForwardListClient mWebBackForwardListClient; 184 private DataController mDataController; 185 // State of the auto-login request. 186 private DeviceAccountLogin mDeviceAccountLogin; 187 188 // AsyncTask for downloading touch icons 189 DownloadTouchIcon mTouchIconLoader; 190 191 private BrowserSettings mSettings; 192 private int mCaptureWidth; 193 private int mCaptureHeight; 194 private Bitmap mCapture; 195 private Handler mHandler; 196 private boolean mUpdateThumbnail; 197 198 /** 199 * See {@link #clearBackStackWhenItemAdded(String)}. 200 */ 201 private Pattern mClearHistoryUrlPattern; 202 getDefaultFavicon(Context context)203 private static synchronized Bitmap getDefaultFavicon(Context context) { 204 if (sDefaultFavicon == null) { 205 sDefaultFavicon = BitmapFactory.decodeResource( 206 context.getResources(), R.drawable.app_web_browser_sm); 207 } 208 return sDefaultFavicon; 209 } 210 211 // All the state needed for a page 212 protected static class PageState { 213 String mUrl; 214 String mOriginalUrl; 215 String mTitle; 216 SecurityState mSecurityState; 217 // This is non-null only when mSecurityState is SECURITY_STATE_BAD_CERTIFICATE. 218 SslError mSslCertificateError; 219 Bitmap mFavicon; 220 boolean mIsBookmarkedSite; 221 boolean mIncognito; 222 PageState(Context c, boolean incognito)223 PageState(Context c, boolean incognito) { 224 mIncognito = incognito; 225 if (mIncognito) { 226 mOriginalUrl = mUrl = "browser:incognito"; 227 mTitle = c.getString(R.string.new_incognito_tab); 228 } else { 229 mOriginalUrl = mUrl = ""; 230 mTitle = c.getString(R.string.new_tab); 231 } 232 mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE; 233 } 234 PageState(Context c, boolean incognito, String url, Bitmap favicon)235 PageState(Context c, boolean incognito, String url, Bitmap favicon) { 236 mIncognito = incognito; 237 mOriginalUrl = mUrl = url; 238 if (URLUtil.isHttpsUrl(url)) { 239 mSecurityState = SecurityState.SECURITY_STATE_SECURE; 240 } else { 241 mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE; 242 } 243 mFavicon = favicon; 244 } 245 246 } 247 248 // The current/loading page's state 249 protected PageState mCurrentState; 250 251 // Used for saving and restoring each Tab 252 static final String ID = "ID"; 253 static final String CURRURL = "currentUrl"; 254 static final String CURRTITLE = "currentTitle"; 255 static final String PARENTTAB = "parentTab"; 256 static final String APPID = "appid"; 257 static final String INCOGNITO = "privateBrowsingEnabled"; 258 static final String USERAGENT = "useragent"; 259 static final String CLOSEFLAG = "closeOnBack"; 260 261 // Container class for the next error dialog that needs to be displayed 262 private class ErrorDialog { 263 public final int mTitle; 264 public final String mDescription; 265 public final int mError; ErrorDialog(int title, String desc, int error)266 ErrorDialog(int title, String desc, int error) { 267 mTitle = title; 268 mDescription = desc; 269 mError = error; 270 } 271 } 272 processNextError()273 private void processNextError() { 274 if (mQueuedErrors == null) { 275 return; 276 } 277 // The first one is currently displayed so just remove it. 278 mQueuedErrors.removeFirst(); 279 if (mQueuedErrors.size() == 0) { 280 mQueuedErrors = null; 281 return; 282 } 283 showError(mQueuedErrors.getFirst()); 284 } 285 286 private DialogInterface.OnDismissListener mDialogListener = 287 new DialogInterface.OnDismissListener() { 288 public void onDismiss(DialogInterface d) { 289 processNextError(); 290 } 291 }; 292 private LinkedList<ErrorDialog> mQueuedErrors; 293 queueError(int err, String desc)294 private void queueError(int err, String desc) { 295 if (mQueuedErrors == null) { 296 mQueuedErrors = new LinkedList<ErrorDialog>(); 297 } 298 for (ErrorDialog d : mQueuedErrors) { 299 if (d.mError == err) { 300 // Already saw a similar error, ignore the new one. 301 return; 302 } 303 } 304 ErrorDialog errDialog = new ErrorDialog( 305 err == WebViewClient.ERROR_FILE_NOT_FOUND ? 306 R.string.browserFrameFileErrorLabel : 307 R.string.browserFrameNetworkErrorLabel, 308 desc, err); 309 mQueuedErrors.addLast(errDialog); 310 311 // Show the dialog now if the queue was empty and it is in foreground 312 if (mQueuedErrors.size() == 1 && mInForeground) { 313 showError(errDialog); 314 } 315 } 316 showError(ErrorDialog errDialog)317 private void showError(ErrorDialog errDialog) { 318 if (mInForeground) { 319 AlertDialog d = new AlertDialog.Builder(mContext) 320 .setTitle(errDialog.mTitle) 321 .setMessage(errDialog.mDescription) 322 .setPositiveButton(R.string.ok, null) 323 .create(); 324 d.setOnDismissListener(mDialogListener); 325 d.show(); 326 } 327 } 328 329 // ------------------------------------------------------------------------- 330 // WebViewClient implementation for the main WebView 331 // ------------------------------------------------------------------------- 332 333 private final WebViewClient mWebViewClient = new WebViewClient() { 334 private Message mDontResend; 335 private Message mResend; 336 337 private boolean providersDiffer(String url, String otherUrl) { 338 Uri uri1 = Uri.parse(url); 339 Uri uri2 = Uri.parse(otherUrl); 340 return !uri1.getEncodedAuthority().equals(uri2.getEncodedAuthority()); 341 } 342 343 @Override 344 public void onPageStarted(WebView view, String url, Bitmap favicon) { 345 mInPageLoad = true; 346 mUpdateThumbnail = true; 347 mPageLoadProgress = INITIAL_PROGRESS; 348 mCurrentState = new PageState(mContext, 349 view.isPrivateBrowsingEnabled(), url, favicon); 350 mLoadStartTime = SystemClock.uptimeMillis(); 351 352 // If we start a touch icon load and then load a new page, we don't 353 // want to cancel the current touch icon loader. But, we do want to 354 // create a new one when the touch icon url is known. 355 if (mTouchIconLoader != null) { 356 mTouchIconLoader.mTab = null; 357 mTouchIconLoader = null; 358 } 359 360 // reset the error console 361 if (mErrorConsole != null) { 362 mErrorConsole.clearErrorMessages(); 363 if (mWebViewController.shouldShowErrorConsole()) { 364 mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE); 365 } 366 } 367 368 // Cancel the auto-login process. 369 if (mDeviceAccountLogin != null) { 370 mDeviceAccountLogin.cancel(); 371 mDeviceAccountLogin = null; 372 mWebViewController.hideAutoLogin(Tab.this); 373 } 374 375 // finally update the UI in the activity if it is in the foreground 376 mWebViewController.onPageStarted(Tab.this, view, favicon); 377 378 updateBookmarkedStatus(); 379 } 380 381 @Override 382 public void onPageFinished(WebView view, String url) { 383 mDisableOverrideUrlLoading = false; 384 if (!isPrivateBrowsingEnabled()) { 385 LogTag.logPageFinishedLoading( 386 url, SystemClock.uptimeMillis() - mLoadStartTime); 387 } 388 syncCurrentState(view, url); 389 mWebViewController.onPageFinished(Tab.this); 390 } 391 392 // return true if want to hijack the url to let another app to handle it 393 @Override 394 public boolean shouldOverrideUrlLoading(WebView view, String url) { 395 if (!mDisableOverrideUrlLoading && mInForeground) { 396 return mWebViewController.shouldOverrideUrlLoading(Tab.this, 397 view, url); 398 } else { 399 return false; 400 } 401 } 402 403 /** 404 * Updates the security state. This method is called when we discover 405 * another resource to be loaded for this page (for example, 406 * javascript). While we update the security state, we do not update 407 * the lock icon until we are done loading, as it is slightly more 408 * secure this way. 409 */ 410 @Override 411 public void onLoadResource(WebView view, String url) { 412 if (url != null && url.length() > 0) { 413 // It is only if the page claims to be secure that we may have 414 // to update the security state: 415 if (mCurrentState.mSecurityState == SecurityState.SECURITY_STATE_SECURE) { 416 // If NOT a 'safe' url, change the state to mixed content! 417 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) 418 || URLUtil.isAboutUrl(url))) { 419 mCurrentState.mSecurityState = SecurityState.SECURITY_STATE_MIXED; 420 } 421 } 422 } 423 } 424 425 /** 426 * Show a dialog informing the user of the network error reported by 427 * WebCore if it is in the foreground. 428 */ 429 @Override 430 public void onReceivedError(WebView view, int errorCode, 431 String description, String failingUrl) { 432 if (errorCode != WebViewClient.ERROR_HOST_LOOKUP && 433 errorCode != WebViewClient.ERROR_CONNECT && 434 errorCode != WebViewClient.ERROR_BAD_URL && 435 errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME && 436 errorCode != WebViewClient.ERROR_FILE) { 437 queueError(errorCode, description); 438 439 // Don't log URLs when in private browsing mode 440 if (!isPrivateBrowsingEnabled()) { 441 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl 442 + " " + description); 443 } 444 } 445 } 446 447 /** 448 * Check with the user if it is ok to resend POST data as the page they 449 * are trying to navigate to is the result of a POST. 450 */ 451 @Override 452 public void onFormResubmission(WebView view, final Message dontResend, 453 final Message resend) { 454 if (!mInForeground) { 455 dontResend.sendToTarget(); 456 return; 457 } 458 if (mDontResend != null) { 459 Log.w(LOGTAG, "onFormResubmission should not be called again " 460 + "while dialog is still up"); 461 dontResend.sendToTarget(); 462 return; 463 } 464 mDontResend = dontResend; 465 mResend = resend; 466 new AlertDialog.Builder(mContext).setTitle( 467 R.string.browserFrameFormResubmitLabel).setMessage( 468 R.string.browserFrameFormResubmitMessage) 469 .setPositiveButton(R.string.ok, 470 new DialogInterface.OnClickListener() { 471 public void onClick(DialogInterface dialog, 472 int which) { 473 if (mResend != null) { 474 mResend.sendToTarget(); 475 mResend = null; 476 mDontResend = null; 477 } 478 } 479 }).setNegativeButton(R.string.cancel, 480 new DialogInterface.OnClickListener() { 481 public void onClick(DialogInterface dialog, 482 int which) { 483 if (mDontResend != null) { 484 mDontResend.sendToTarget(); 485 mResend = null; 486 mDontResend = null; 487 } 488 } 489 }).setOnCancelListener(new OnCancelListener() { 490 public void onCancel(DialogInterface dialog) { 491 if (mDontResend != null) { 492 mDontResend.sendToTarget(); 493 mResend = null; 494 mDontResend = null; 495 } 496 } 497 }).show(); 498 } 499 500 /** 501 * Insert the url into the visited history database. 502 * @param url The url to be inserted. 503 * @param isReload True if this url is being reloaded. 504 * FIXME: Not sure what to do when reloading the page. 505 */ 506 @Override 507 public void doUpdateVisitedHistory(WebView view, String url, 508 boolean isReload) { 509 mWebViewController.doUpdateVisitedHistory(Tab.this, isReload); 510 } 511 512 /** 513 * Displays SSL error(s) dialog to the user. 514 */ 515 @Override 516 public void onReceivedSslError(final WebView view, 517 final SslErrorHandler handler, final SslError error) { 518 if (!mInForeground) { 519 handler.cancel(); 520 setSecurityState(SecurityState.SECURITY_STATE_NOT_SECURE); 521 return; 522 } 523 if (mSettings.showSecurityWarnings()) { 524 new AlertDialog.Builder(mContext) 525 .setTitle(R.string.security_warning) 526 .setMessage(R.string.ssl_warnings_header) 527 .setIconAttribute(android.R.attr.alertDialogIcon) 528 .setPositiveButton(R.string.ssl_continue, 529 new DialogInterface.OnClickListener() { 530 @Override 531 public void onClick(DialogInterface dialog, 532 int whichButton) { 533 handler.proceed(); 534 handleProceededAfterSslError(error); 535 } 536 }) 537 .setNeutralButton(R.string.view_certificate, 538 new DialogInterface.OnClickListener() { 539 @Override 540 public void onClick(DialogInterface dialog, 541 int whichButton) { 542 mWebViewController.showSslCertificateOnError( 543 view, handler, error); 544 } 545 }) 546 .setNegativeButton(R.string.ssl_go_back, 547 new DialogInterface.OnClickListener() { 548 @Override 549 public void onClick(DialogInterface dialog, 550 int whichButton) { 551 dialog.cancel(); 552 } 553 }) 554 .setOnCancelListener( 555 new DialogInterface.OnCancelListener() { 556 @Override 557 public void onCancel(DialogInterface dialog) { 558 handler.cancel(); 559 setSecurityState(SecurityState.SECURITY_STATE_NOT_SECURE); 560 mWebViewController.onUserCanceledSsl(Tab.this); 561 } 562 }) 563 .show(); 564 } else { 565 handler.proceed(); 566 } 567 } 568 569 /** 570 * Displays client certificate request to the user. 571 */ 572 @Override 573 public void onReceivedClientCertRequest(final WebView view, 574 final ClientCertRequest request) { 575 if (!mInForeground) { 576 request.ignore(); 577 return; 578 } 579 KeyChain.choosePrivateKeyAlias( 580 mWebViewController.getActivity(), new KeyChainAliasCallback() { 581 @Override public void alias(String alias) { 582 if (alias == null) { 583 request.cancel(); 584 return; 585 } 586 new KeyChainLookup(mContext, request, alias).execute(); 587 } 588 }, request.getKeyTypes(), request.getPrincipals(), request.getHost(), 589 request.getPort(), null); 590 } 591 592 /** 593 * Handles an HTTP authentication request. 594 * 595 * @param handler The authentication handler 596 * @param host The host 597 * @param realm The realm 598 */ 599 @Override 600 public void onReceivedHttpAuthRequest(WebView view, 601 final HttpAuthHandler handler, final String host, 602 final String realm) { 603 mWebViewController.onReceivedHttpAuthRequest(Tab.this, view, handler, host, realm); 604 } 605 606 @Override 607 public WebResourceResponse shouldInterceptRequest(WebView view, 608 String url) { 609 Uri uri = Uri.parse(url); 610 if (uri.getScheme().toLowerCase().equals("file")) { 611 File file = new File(uri.getPath()); 612 try { 613 if (file.getCanonicalPath().startsWith( 614 mContext.getApplicationContext().getApplicationInfo().dataDir)) { 615 return new WebResourceResponse("text/html","UTF-8", 616 new ByteArrayInputStream(RESTRICTED.getBytes("UTF-8"))); 617 } 618 } catch (Exception ex) { 619 Log.e(LOGTAG, "Bad canonical path" + ex.toString()); 620 try { 621 return new WebResourceResponse("text/html","UTF-8", 622 new ByteArrayInputStream(RESTRICTED.getBytes("UTF-8"))); 623 } catch (java.io.UnsupportedEncodingException e) { 624 } 625 } 626 } 627 WebResourceResponse res = HomeProvider.shouldInterceptRequest( 628 mContext, url); 629 return res; 630 } 631 632 @Override 633 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { 634 if (!mInForeground) { 635 return false; 636 } 637 return mWebViewController.shouldOverrideKeyEvent(event); 638 } 639 640 @Override 641 public void onUnhandledKeyEvent(WebView view, KeyEvent event) { 642 if (!mInForeground) { 643 return; 644 } 645 if (!mWebViewController.onUnhandledKeyEvent(event)) { 646 super.onUnhandledKeyEvent(view, event); 647 } 648 } 649 650 @Override 651 public void onReceivedLoginRequest(WebView view, String realm, 652 String account, String args) { 653 new DeviceAccountLogin(mWebViewController.getActivity(), view, Tab.this, mWebViewController) 654 .handleLogin(realm, account, args); 655 } 656 657 }; 658 syncCurrentState(WebView view, String url)659 private void syncCurrentState(WebView view, String url) { 660 // Sync state (in case of stop/timeout) 661 mCurrentState.mUrl = view.getUrl(); 662 if (mCurrentState.mUrl == null) { 663 mCurrentState.mUrl = ""; 664 } 665 mCurrentState.mOriginalUrl = view.getOriginalUrl(); 666 mCurrentState.mTitle = view.getTitle(); 667 mCurrentState.mFavicon = view.getFavicon(); 668 if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) { 669 // In case we stop when loading an HTTPS page from an HTTP page 670 // but before a provisional load occurred 671 mCurrentState.mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE; 672 mCurrentState.mSslCertificateError = null; 673 } 674 mCurrentState.mIncognito = view.isPrivateBrowsingEnabled(); 675 } 676 677 // Called by DeviceAccountLogin when the Tab needs to have the auto-login UI 678 // displayed. setDeviceAccountLogin(DeviceAccountLogin login)679 void setDeviceAccountLogin(DeviceAccountLogin login) { 680 mDeviceAccountLogin = login; 681 } 682 683 // Returns non-null if the title bar should display the auto-login UI. getDeviceAccountLogin()684 DeviceAccountLogin getDeviceAccountLogin() { 685 return mDeviceAccountLogin; 686 } 687 688 // ------------------------------------------------------------------------- 689 // WebChromeClient implementation for the main WebView 690 // ------------------------------------------------------------------------- 691 692 private final WebChromeClient mWebChromeClient = new WebChromeClient() { 693 // Helper method to create a new tab or sub window. 694 private void createWindow(final boolean dialog, final Message msg) { 695 WebView.WebViewTransport transport = 696 (WebView.WebViewTransport) msg.obj; 697 if (dialog) { 698 createSubWindow(); 699 mWebViewController.attachSubWindow(Tab.this); 700 transport.setWebView(mSubView); 701 } else { 702 final Tab newTab = mWebViewController.openTab(null, 703 Tab.this, true, true); 704 transport.setWebView(newTab.getWebView()); 705 } 706 msg.sendToTarget(); 707 } 708 709 @Override 710 public boolean onCreateWindow(WebView view, final boolean dialog, 711 final boolean userGesture, final Message resultMsg) { 712 // only allow new window or sub window for the foreground case 713 if (!mInForeground) { 714 return false; 715 } 716 // Short-circuit if we can't create any more tabs or sub windows. 717 if (dialog && mSubView != null) { 718 new AlertDialog.Builder(mContext) 719 .setTitle(R.string.too_many_subwindows_dialog_title) 720 .setIconAttribute(android.R.attr.alertDialogIcon) 721 .setMessage(R.string.too_many_subwindows_dialog_message) 722 .setPositiveButton(R.string.ok, null) 723 .show(); 724 return false; 725 } else if (!mWebViewController.getTabControl().canCreateNewTab()) { 726 new AlertDialog.Builder(mContext) 727 .setTitle(R.string.too_many_windows_dialog_title) 728 .setIconAttribute(android.R.attr.alertDialogIcon) 729 .setMessage(R.string.too_many_windows_dialog_message) 730 .setPositiveButton(R.string.ok, null) 731 .show(); 732 return false; 733 } 734 735 // Short-circuit if this was a user gesture. 736 if (userGesture) { 737 createWindow(dialog, resultMsg); 738 return true; 739 } 740 741 // Allow the popup and create the appropriate window. 742 final AlertDialog.OnClickListener allowListener = 743 new AlertDialog.OnClickListener() { 744 public void onClick(DialogInterface d, 745 int which) { 746 createWindow(dialog, resultMsg); 747 } 748 }; 749 750 // Block the popup by returning a null WebView. 751 final AlertDialog.OnClickListener blockListener = 752 new AlertDialog.OnClickListener() { 753 public void onClick(DialogInterface d, int which) { 754 resultMsg.sendToTarget(); 755 } 756 }; 757 758 // Build a confirmation dialog to display to the user. 759 final AlertDialog d = 760 new AlertDialog.Builder(mContext) 761 .setIconAttribute(android.R.attr.alertDialogIcon) 762 .setMessage(R.string.popup_window_attempt) 763 .setPositiveButton(R.string.allow, allowListener) 764 .setNegativeButton(R.string.block, blockListener) 765 .setCancelable(false) 766 .create(); 767 768 // Show the confirmation dialog. 769 d.show(); 770 return true; 771 } 772 773 @Override 774 public void onRequestFocus(WebView view) { 775 if (!mInForeground) { 776 mWebViewController.switchToTab(Tab.this); 777 } 778 } 779 780 @Override 781 public void onCloseWindow(WebView window) { 782 if (mParent != null) { 783 // JavaScript can only close popup window. 784 if (mInForeground) { 785 mWebViewController.switchToTab(mParent); 786 } 787 mWebViewController.closeTab(Tab.this); 788 } 789 } 790 791 @Override 792 public void onProgressChanged(WebView view, int newProgress) { 793 mPageLoadProgress = newProgress; 794 if (newProgress == 100) { 795 mInPageLoad = false; 796 } 797 mWebViewController.onProgressChanged(Tab.this); 798 if (mUpdateThumbnail && newProgress == 100) { 799 mUpdateThumbnail = false; 800 } 801 } 802 803 @Override 804 public void onReceivedTitle(WebView view, final String title) { 805 mCurrentState.mTitle = title; 806 mWebViewController.onReceivedTitle(Tab.this, title); 807 } 808 809 @Override 810 public void onReceivedIcon(WebView view, Bitmap icon) { 811 mCurrentState.mFavicon = icon; 812 mWebViewController.onFavicon(Tab.this, view, icon); 813 } 814 815 @Override 816 public void onReceivedTouchIconUrl(WebView view, String url, 817 boolean precomposed) { 818 final ContentResolver cr = mContext.getContentResolver(); 819 // Let precomposed icons take precedence over non-composed 820 // icons. 821 if (precomposed && mTouchIconLoader != null) { 822 mTouchIconLoader.cancel(false); 823 mTouchIconLoader = null; 824 } 825 // Have only one async task at a time. 826 if (mTouchIconLoader == null) { 827 mTouchIconLoader = new DownloadTouchIcon(Tab.this, 828 mContext, cr, view); 829 mTouchIconLoader.execute(url); 830 } 831 } 832 833 @Override 834 public void onShowCustomView(View view, 835 WebChromeClient.CustomViewCallback callback) { 836 Activity activity = mWebViewController.getActivity(); 837 if (activity != null) { 838 onShowCustomView(view, activity.getRequestedOrientation(), callback); 839 } 840 } 841 842 @Override 843 public void onShowCustomView(View view, int requestedOrientation, 844 WebChromeClient.CustomViewCallback callback) { 845 if (mInForeground) mWebViewController.showCustomView(Tab.this, view, 846 requestedOrientation, callback); 847 } 848 849 @Override 850 public void onHideCustomView() { 851 if (mInForeground) mWebViewController.hideCustomView(); 852 } 853 854 /** 855 * The origin has exceeded its database quota. 856 * @param url the URL that exceeded the quota 857 * @param databaseIdentifier the identifier of the database on which the 858 * transaction that caused the quota overflow was run 859 * @param currentQuota the current quota for the origin. 860 * @param estimatedSize the estimated size of the database. 861 * @param totalUsedQuota is the sum of all origins' quota. 862 * @param quotaUpdater The callback to run when a decision to allow or 863 * deny quota has been made. Don't forget to call this! 864 */ 865 @Override 866 public void onExceededDatabaseQuota(String url, 867 String databaseIdentifier, long currentQuota, long estimatedSize, 868 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { 869 mSettings.getWebStorageSizeManager() 870 .onExceededDatabaseQuota(url, databaseIdentifier, 871 currentQuota, estimatedSize, totalUsedQuota, 872 quotaUpdater); 873 } 874 875 /** 876 * The Application Cache has exceeded its max size. 877 * @param spaceNeeded is the amount of disk space that would be needed 878 * in order for the last appcache operation to succeed. 879 * @param totalUsedQuota is the sum of all origins' quota. 880 * @param quotaUpdater A callback to inform the WebCore thread that a 881 * new app cache size is available. This callback must always 882 * be executed at some point to ensure that the sleeping 883 * WebCore thread is woken up. 884 */ 885 @Override 886 public void onReachedMaxAppCacheSize(long spaceNeeded, 887 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { 888 mSettings.getWebStorageSizeManager() 889 .onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota, 890 quotaUpdater); 891 } 892 893 /** 894 * Instructs the browser to show a prompt to ask the user to set the 895 * Geolocation permission state for the specified origin. 896 * @param origin The origin for which Geolocation permissions are 897 * requested. 898 * @param callback The callback to call once the user has set the 899 * Geolocation permission state. 900 */ 901 @Override 902 public void onGeolocationPermissionsShowPrompt(String origin, 903 GeolocationPermissions.Callback callback) { 904 if (mInForeground) { 905 getGeolocationPermissionsPrompt().show(origin, callback); 906 } 907 } 908 909 /** 910 * Instructs the browser to hide the Geolocation permissions prompt. 911 */ 912 @Override 913 public void onGeolocationPermissionsHidePrompt() { 914 if (mInForeground && mGeolocationPermissionsPrompt != null) { 915 mGeolocationPermissionsPrompt.hide(); 916 } 917 } 918 919 @Override 920 public void onPermissionRequest(PermissionRequest request) { 921 if (!mInForeground) return; 922 getPermissionsPrompt().show(request); 923 } 924 925 @Override 926 public void onPermissionRequestCanceled(PermissionRequest request) { 927 if (mInForeground && mPermissionsPrompt != null) { 928 mPermissionsPrompt.hide(); 929 } 930 } 931 932 /* Adds a JavaScript error message to the system log and if the JS 933 * console is enabled in the about:debug options, to that console 934 * also. 935 * @param consoleMessage the message object. 936 */ 937 @Override 938 public boolean onConsoleMessage(ConsoleMessage consoleMessage) { 939 if (mInForeground) { 940 // call getErrorConsole(true) so it will create one if needed 941 ErrorConsoleView errorConsole = getErrorConsole(true); 942 errorConsole.addErrorMessage(consoleMessage); 943 if (mWebViewController.shouldShowErrorConsole() 944 && errorConsole.getShowState() != 945 ErrorConsoleView.SHOW_MAXIMIZED) { 946 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED); 947 } 948 } 949 950 // Don't log console messages in private browsing mode 951 if (isPrivateBrowsingEnabled()) return true; 952 953 String message = "Console: " + consoleMessage.message() + " " 954 + consoleMessage.sourceId() + ":" 955 + consoleMessage.lineNumber(); 956 957 switch (consoleMessage.messageLevel()) { 958 case TIP: 959 Log.v(CONSOLE_LOGTAG, message); 960 break; 961 case LOG: 962 Log.i(CONSOLE_LOGTAG, message); 963 break; 964 case WARNING: 965 Log.w(CONSOLE_LOGTAG, message); 966 break; 967 case ERROR: 968 Log.e(CONSOLE_LOGTAG, message); 969 break; 970 case DEBUG: 971 Log.d(CONSOLE_LOGTAG, message); 972 break; 973 } 974 975 return true; 976 } 977 978 /** 979 * Ask the browser for an icon to represent a <video> element. 980 * This icon will be used if the Web page did not specify a poster attribute. 981 * @return Bitmap The icon or null if no such icon is available. 982 */ 983 @Override 984 public Bitmap getDefaultVideoPoster() { 985 if (mInForeground) { 986 return mWebViewController.getDefaultVideoPoster(); 987 } 988 return null; 989 } 990 991 /** 992 * Ask the host application for a custom progress view to show while 993 * a <video> is loading. 994 * @return View The progress view. 995 */ 996 @Override 997 public View getVideoLoadingProgressView() { 998 if (mInForeground) { 999 return mWebViewController.getVideoLoadingProgressView(); 1000 } 1001 return null; 1002 } 1003 1004 @Override 1005 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> callback, 1006 FileChooserParams params) { 1007 if (mInForeground) { 1008 mWebViewController.showFileChooser(callback, params); 1009 return true; 1010 } else { 1011 return false; 1012 } 1013 } 1014 1015 /** 1016 * Deliver a list of already-visited URLs 1017 */ 1018 @Override 1019 public void getVisitedHistory(final ValueCallback<String[]> callback) { 1020 mWebViewController.getVisitedHistory(callback); 1021 } 1022 1023 }; 1024 1025 // ------------------------------------------------------------------------- 1026 // WebViewClient implementation for the sub window 1027 // ------------------------------------------------------------------------- 1028 1029 // Subclass of WebViewClient used in subwindows to notify the main 1030 // WebViewClient of certain WebView activities. 1031 private static class SubWindowClient extends WebViewClient { 1032 // The main WebViewClient. 1033 private final WebViewClient mClient; 1034 private final WebViewController mController; 1035 SubWindowClient(WebViewClient client, WebViewController controller)1036 SubWindowClient(WebViewClient client, WebViewController controller) { 1037 mClient = client; 1038 mController = controller; 1039 } 1040 @Override onPageStarted(WebView view, String url, Bitmap favicon)1041 public void onPageStarted(WebView view, String url, Bitmap favicon) { 1042 // Unlike the others, do not call mClient's version, which would 1043 // change the progress bar. However, we do want to remove the 1044 // find or select dialog. 1045 mController.endActionMode(); 1046 } 1047 @Override doUpdateVisitedHistory(WebView view, String url, boolean isReload)1048 public void doUpdateVisitedHistory(WebView view, String url, 1049 boolean isReload) { 1050 mClient.doUpdateVisitedHistory(view, url, isReload); 1051 } 1052 @Override shouldOverrideUrlLoading(WebView view, String url)1053 public boolean shouldOverrideUrlLoading(WebView view, String url) { 1054 return mClient.shouldOverrideUrlLoading(view, url); 1055 } 1056 @Override onReceivedSslError(WebView view, SslErrorHandler handler, SslError error)1057 public void onReceivedSslError(WebView view, SslErrorHandler handler, 1058 SslError error) { 1059 mClient.onReceivedSslError(view, handler, error); 1060 } 1061 @Override onReceivedClientCertRequest(WebView view, ClientCertRequest request)1062 public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { 1063 mClient.onReceivedClientCertRequest(view, request); 1064 } 1065 @Override onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm)1066 public void onReceivedHttpAuthRequest(WebView view, 1067 HttpAuthHandler handler, String host, String realm) { 1068 mClient.onReceivedHttpAuthRequest(view, handler, host, realm); 1069 } 1070 @Override onFormResubmission(WebView view, Message dontResend, Message resend)1071 public void onFormResubmission(WebView view, Message dontResend, 1072 Message resend) { 1073 mClient.onFormResubmission(view, dontResend, resend); 1074 } 1075 @Override onReceivedError(WebView view, int errorCode, String description, String failingUrl)1076 public void onReceivedError(WebView view, int errorCode, 1077 String description, String failingUrl) { 1078 mClient.onReceivedError(view, errorCode, description, failingUrl); 1079 } 1080 @Override shouldOverrideKeyEvent(WebView view, android.view.KeyEvent event)1081 public boolean shouldOverrideKeyEvent(WebView view, 1082 android.view.KeyEvent event) { 1083 return mClient.shouldOverrideKeyEvent(view, event); 1084 } 1085 @Override onUnhandledKeyEvent(WebView view, android.view.KeyEvent event)1086 public void onUnhandledKeyEvent(WebView view, 1087 android.view.KeyEvent event) { 1088 mClient.onUnhandledKeyEvent(view, event); 1089 } 1090 } 1091 1092 // ------------------------------------------------------------------------- 1093 // WebChromeClient implementation for the sub window 1094 // ------------------------------------------------------------------------- 1095 1096 private class SubWindowChromeClient extends WebChromeClient { 1097 // The main WebChromeClient. 1098 private final WebChromeClient mClient; 1099 SubWindowChromeClient(WebChromeClient client)1100 SubWindowChromeClient(WebChromeClient client) { 1101 mClient = client; 1102 } 1103 @Override onProgressChanged(WebView view, int newProgress)1104 public void onProgressChanged(WebView view, int newProgress) { 1105 mClient.onProgressChanged(view, newProgress); 1106 } 1107 @Override onCreateWindow(WebView view, boolean dialog, boolean userGesture, android.os.Message resultMsg)1108 public boolean onCreateWindow(WebView view, boolean dialog, 1109 boolean userGesture, android.os.Message resultMsg) { 1110 return mClient.onCreateWindow(view, dialog, userGesture, resultMsg); 1111 } 1112 @Override onCloseWindow(WebView window)1113 public void onCloseWindow(WebView window) { 1114 if (window != mSubView) { 1115 Log.e(LOGTAG, "Can't close the window"); 1116 } 1117 mWebViewController.dismissSubWindow(Tab.this); 1118 } 1119 } 1120 1121 // ------------------------------------------------------------------------- 1122 1123 // Construct a new tab Tab(WebViewController wvcontroller, WebView w)1124 Tab(WebViewController wvcontroller, WebView w) { 1125 this(wvcontroller, w, null); 1126 } 1127 Tab(WebViewController wvcontroller, Bundle state)1128 Tab(WebViewController wvcontroller, Bundle state) { 1129 this(wvcontroller, null, state); 1130 } 1131 Tab(WebViewController wvcontroller, WebView w, Bundle state)1132 Tab(WebViewController wvcontroller, WebView w, Bundle state) { 1133 mWebViewController = wvcontroller; 1134 mContext = mWebViewController.getContext(); 1135 mSettings = BrowserSettings.getInstance(); 1136 mDataController = DataController.getInstance(mContext); 1137 mCurrentState = new PageState(mContext, w != null 1138 ? w.isPrivateBrowsingEnabled() : false); 1139 mInPageLoad = false; 1140 mInForeground = false; 1141 1142 mDownloadListener = new BrowserDownloadListener() { 1143 public void onDownloadStart(String url, String userAgent, 1144 String contentDisposition, String mimetype, String referer, 1145 long contentLength) { 1146 mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition, 1147 mimetype, referer, contentLength); 1148 } 1149 }; 1150 mWebBackForwardListClient = new WebBackForwardListClient() { 1151 @Override 1152 public void onNewHistoryItem(WebHistoryItem item) { 1153 if (mClearHistoryUrlPattern != null) { 1154 boolean match = 1155 mClearHistoryUrlPattern.matcher(item.getOriginalUrl()).matches(); 1156 if (LOGD_ENABLED) { 1157 Log.d(LOGTAG, "onNewHistoryItem: match=" + match + "\n\t" 1158 + item.getUrl() + "\n\t" 1159 + mClearHistoryUrlPattern); 1160 } 1161 if (match) { 1162 if (mMainView != null) { 1163 mMainView.clearHistory(); 1164 } 1165 } 1166 mClearHistoryUrlPattern = null; 1167 } 1168 } 1169 }; 1170 1171 mCaptureWidth = mContext.getResources().getDimensionPixelSize( 1172 R.dimen.tab_thumbnail_width); 1173 mCaptureHeight = mContext.getResources().getDimensionPixelSize( 1174 R.dimen.tab_thumbnail_height); 1175 updateShouldCaptureThumbnails(); 1176 restoreState(state); 1177 if (getId() == -1) { 1178 mId = TabControl.getNextId(); 1179 } 1180 setWebView(w); 1181 mHandler = new Handler() { 1182 @Override 1183 public void handleMessage(Message m) { 1184 switch (m.what) { 1185 case MSG_CAPTURE: 1186 capture(); 1187 break; 1188 } 1189 } 1190 }; 1191 } 1192 shouldUpdateThumbnail()1193 public boolean shouldUpdateThumbnail() { 1194 return mUpdateThumbnail; 1195 } 1196 1197 /** 1198 * This is used to get a new ID when the tab has been preloaded, before it is displayed and 1199 * added to TabControl. Preloaded tabs can be created before restoreInstanceState, leading 1200 * to overlapping IDs between the preloaded and restored tabs. 1201 */ refreshIdAfterPreload()1202 public void refreshIdAfterPreload() { 1203 mId = TabControl.getNextId(); 1204 } 1205 updateShouldCaptureThumbnails()1206 public void updateShouldCaptureThumbnails() { 1207 if (mWebViewController.shouldCaptureThumbnails()) { 1208 synchronized (Tab.this) { 1209 if (mCapture == null) { 1210 mCapture = Bitmap.createBitmap(mCaptureWidth, mCaptureHeight, 1211 Bitmap.Config.RGB_565); 1212 mCapture.eraseColor(Color.WHITE); 1213 if (mInForeground) { 1214 postCapture(); 1215 } 1216 } 1217 } 1218 } else { 1219 synchronized (Tab.this) { 1220 mCapture = null; 1221 deleteThumbnail(); 1222 } 1223 } 1224 } 1225 setController(WebViewController ctl)1226 public void setController(WebViewController ctl) { 1227 mWebViewController = ctl; 1228 updateShouldCaptureThumbnails(); 1229 } 1230 getId()1231 public long getId() { 1232 return mId; 1233 } 1234 setWebView(WebView w)1235 void setWebView(WebView w) { 1236 setWebView(w, true); 1237 } 1238 1239 /** 1240 * Sets the WebView for this tab, correctly removing the old WebView from 1241 * the container view. 1242 */ setWebView(WebView w, boolean restore)1243 void setWebView(WebView w, boolean restore) { 1244 if (mMainView == w) { 1245 return; 1246 } 1247 1248 // If the WebView is changing, the page will be reloaded, so any ongoing 1249 // Geolocation permission requests are void. 1250 if (mGeolocationPermissionsPrompt != null) { 1251 mGeolocationPermissionsPrompt.hide(); 1252 } 1253 1254 if (mPermissionsPrompt != null) { 1255 mPermissionsPrompt.hide(); 1256 } 1257 1258 mWebViewController.onSetWebView(this, w); 1259 1260 if (mMainView != null) { 1261 mMainView.setPictureListener(null); 1262 if (w != null) { 1263 syncCurrentState(w, null); 1264 } else { 1265 mCurrentState = new PageState(mContext, false); 1266 } 1267 } 1268 // set the new one 1269 mMainView = w; 1270 // attach the WebViewClient, WebChromeClient and DownloadListener 1271 if (mMainView != null) { 1272 mMainView.setWebViewClient(mWebViewClient); 1273 mMainView.setWebChromeClient(mWebChromeClient); 1274 // Attach DownloadManager so that downloads can start in an active 1275 // or a non-active window. This can happen when going to a site that 1276 // does a redirect after a period of time. The user could have 1277 // switched to another tab while waiting for the download to start. 1278 mMainView.setDownloadListener(mDownloadListener); 1279 TabControl tc = mWebViewController.getTabControl(); 1280 if (tc != null && tc.getOnThumbnailUpdatedListener() != null) { 1281 mMainView.setPictureListener(this); 1282 } 1283 if (restore && (mSavedState != null)) { 1284 restoreUserAgent(); 1285 WebBackForwardList restoredState 1286 = mMainView.restoreState(mSavedState); 1287 if (restoredState == null || restoredState.getSize() == 0) { 1288 Log.w(LOGTAG, "Failed to restore WebView state!"); 1289 loadUrl(mCurrentState.mOriginalUrl, null); 1290 } 1291 mSavedState = null; 1292 } 1293 } 1294 } 1295 1296 /** 1297 * Destroy the tab's main WebView and subWindow if any 1298 */ destroy()1299 void destroy() { 1300 if (mMainView != null) { 1301 dismissSubWindow(); 1302 // save the WebView to call destroy() after detach it from the tab 1303 WebView webView = mMainView; 1304 setWebView(null); 1305 webView.destroy(); 1306 } 1307 } 1308 1309 /** 1310 * Remove the tab from the parent 1311 */ removeFromTree()1312 void removeFromTree() { 1313 // detach the children 1314 if (mChildren != null) { 1315 for(Tab t : mChildren) { 1316 t.setParent(null); 1317 } 1318 } 1319 // remove itself from the parent list 1320 if (mParent != null) { 1321 mParent.mChildren.remove(this); 1322 } 1323 deleteThumbnail(); 1324 } 1325 1326 /** 1327 * Create a new subwindow unless a subwindow already exists. 1328 * @return True if a new subwindow was created. False if one already exists. 1329 */ createSubWindow()1330 boolean createSubWindow() { 1331 if (mSubView == null) { 1332 mWebViewController.createSubWindow(this); 1333 mSubView.setWebViewClient(new SubWindowClient(mWebViewClient, 1334 mWebViewController)); 1335 mSubView.setWebChromeClient(new SubWindowChromeClient( 1336 mWebChromeClient)); 1337 // Set a different DownloadListener for the mSubView, since it will 1338 // just need to dismiss the mSubView, rather than close the Tab 1339 mSubView.setDownloadListener(new BrowserDownloadListener() { 1340 public void onDownloadStart(String url, String userAgent, 1341 String contentDisposition, String mimetype, String referer, 1342 long contentLength) { 1343 mWebViewController.onDownloadStart(Tab.this, url, userAgent, 1344 contentDisposition, mimetype, referer, contentLength); 1345 if (mSubView.copyBackForwardList().getSize() == 0) { 1346 // This subwindow was opened for the sole purpose of 1347 // downloading a file. Remove it. 1348 mWebViewController.dismissSubWindow(Tab.this); 1349 } 1350 } 1351 }); 1352 mSubView.setOnCreateContextMenuListener(mWebViewController.getActivity()); 1353 return true; 1354 } 1355 return false; 1356 } 1357 1358 /** 1359 * Dismiss the subWindow for the tab. 1360 */ dismissSubWindow()1361 void dismissSubWindow() { 1362 if (mSubView != null) { 1363 mWebViewController.endActionMode(); 1364 mSubView.destroy(); 1365 mSubView = null; 1366 mSubViewContainer = null; 1367 } 1368 } 1369 1370 1371 /** 1372 * Set the parent tab of this tab. 1373 */ setParent(Tab parent)1374 void setParent(Tab parent) { 1375 if (parent == this) { 1376 throw new IllegalStateException("Cannot set parent to self!"); 1377 } 1378 mParent = parent; 1379 // This tab may have been freed due to low memory. If that is the case, 1380 // the parent tab id is already saved. If we are changing that id 1381 // (most likely due to removing the parent tab) we must update the 1382 // parent tab id in the saved Bundle. 1383 if (mSavedState != null) { 1384 if (parent == null) { 1385 mSavedState.remove(PARENTTAB); 1386 } else { 1387 mSavedState.putLong(PARENTTAB, parent.getId()); 1388 } 1389 } 1390 1391 // Sync the WebView useragent with the parent 1392 if (parent != null && mSettings.hasDesktopUseragent(parent.getWebView()) 1393 != mSettings.hasDesktopUseragent(getWebView())) { 1394 mSettings.toggleDesktopUseragent(getWebView()); 1395 } 1396 1397 if (parent != null && parent.getId() == getId()) { 1398 throw new IllegalStateException("Parent has same ID as child!"); 1399 } 1400 } 1401 1402 /** 1403 * If this Tab was created through another Tab, then this method returns 1404 * that Tab. 1405 * @return the Tab parent or null 1406 */ getParent()1407 public Tab getParent() { 1408 return mParent; 1409 } 1410 1411 /** 1412 * When a Tab is created through the content of another Tab, then we 1413 * associate the Tabs. 1414 * @param child the Tab that was created from this Tab 1415 */ addChildTab(Tab child)1416 void addChildTab(Tab child) { 1417 if (mChildren == null) { 1418 mChildren = new Vector<Tab>(); 1419 } 1420 mChildren.add(child); 1421 child.setParent(this); 1422 } 1423 getChildren()1424 Vector<Tab> getChildren() { 1425 return mChildren; 1426 } 1427 resume()1428 void resume() { 1429 if (mMainView != null) { 1430 setupHwAcceleration(mMainView); 1431 mMainView.onResume(); 1432 if (mSubView != null) { 1433 mSubView.onResume(); 1434 } 1435 } 1436 } 1437 setupHwAcceleration(View web)1438 private void setupHwAcceleration(View web) { 1439 if (web == null) return; 1440 BrowserSettings settings = BrowserSettings.getInstance(); 1441 if (settings.isHardwareAccelerated()) { 1442 web.setLayerType(View.LAYER_TYPE_NONE, null); 1443 } else { 1444 web.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 1445 } 1446 } 1447 pause()1448 void pause() { 1449 if (mMainView != null) { 1450 mMainView.onPause(); 1451 if (mSubView != null) { 1452 mSubView.onPause(); 1453 } 1454 } 1455 } 1456 putInForeground()1457 void putInForeground() { 1458 if (mInForeground) { 1459 return; 1460 } 1461 mInForeground = true; 1462 resume(); 1463 Activity activity = mWebViewController.getActivity(); 1464 mMainView.setOnCreateContextMenuListener(activity); 1465 if (mSubView != null) { 1466 mSubView.setOnCreateContextMenuListener(activity); 1467 } 1468 // Show the pending error dialog if the queue is not empty 1469 if (mQueuedErrors != null && mQueuedErrors.size() > 0) { 1470 showError(mQueuedErrors.getFirst()); 1471 } 1472 mWebViewController.bookmarkedStatusHasChanged(this); 1473 } 1474 putInBackground()1475 void putInBackground() { 1476 if (!mInForeground) { 1477 return; 1478 } 1479 capture(); 1480 mInForeground = false; 1481 pause(); 1482 mMainView.setOnCreateContextMenuListener(null); 1483 if (mSubView != null) { 1484 mSubView.setOnCreateContextMenuListener(null); 1485 } 1486 } 1487 inForeground()1488 boolean inForeground() { 1489 return mInForeground; 1490 } 1491 1492 /** 1493 * Return the top window of this tab; either the subwindow if it is not 1494 * null or the main window. 1495 * @return The top window of this tab. 1496 */ getTopWindow()1497 WebView getTopWindow() { 1498 if (mSubView != null) { 1499 return mSubView; 1500 } 1501 return mMainView; 1502 } 1503 1504 /** 1505 * Return the main window of this tab. Note: if a tab is freed in the 1506 * background, this can return null. It is only guaranteed to be 1507 * non-null for the current tab. 1508 * @return The main WebView of this tab. 1509 */ getWebView()1510 WebView getWebView() { 1511 return mMainView; 1512 } 1513 setViewContainer(View container)1514 void setViewContainer(View container) { 1515 mContainer = container; 1516 } 1517 getViewContainer()1518 View getViewContainer() { 1519 return mContainer; 1520 } 1521 1522 /** 1523 * Return whether private browsing is enabled for the main window of 1524 * this tab. 1525 * @return True if private browsing is enabled. 1526 */ isPrivateBrowsingEnabled()1527 boolean isPrivateBrowsingEnabled() { 1528 return mCurrentState.mIncognito; 1529 } 1530 1531 /** 1532 * Return the subwindow of this tab or null if there is no subwindow. 1533 * @return The subwindow of this tab or null. 1534 */ getSubWebView()1535 WebView getSubWebView() { 1536 return mSubView; 1537 } 1538 setSubWebView(WebView subView)1539 void setSubWebView(WebView subView) { 1540 mSubView = subView; 1541 } 1542 getSubViewContainer()1543 View getSubViewContainer() { 1544 return mSubViewContainer; 1545 } 1546 setSubViewContainer(View subViewContainer)1547 void setSubViewContainer(View subViewContainer) { 1548 mSubViewContainer = subViewContainer; 1549 } 1550 1551 /** 1552 * @return The geolocation permissions prompt for this tab. 1553 */ getGeolocationPermissionsPrompt()1554 GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() { 1555 if (mGeolocationPermissionsPrompt == null) { 1556 ViewStub stub = (ViewStub) mContainer 1557 .findViewById(R.id.geolocation_permissions_prompt); 1558 mGeolocationPermissionsPrompt = (GeolocationPermissionsPrompt) stub 1559 .inflate(); 1560 } 1561 return mGeolocationPermissionsPrompt; 1562 } 1563 1564 /** 1565 * @return The permissions prompt for this tab. 1566 */ getPermissionsPrompt()1567 PermissionsPrompt getPermissionsPrompt() { 1568 if (mPermissionsPrompt == null) { 1569 ViewStub stub = (ViewStub) mContainer 1570 .findViewById(R.id.permissions_prompt); 1571 mPermissionsPrompt = (PermissionsPrompt) stub.inflate(); 1572 } 1573 return mPermissionsPrompt; 1574 } 1575 1576 /** 1577 * @return The application id string 1578 */ getAppId()1579 String getAppId() { 1580 return mAppId; 1581 } 1582 1583 /** 1584 * Set the application id string 1585 * @param id 1586 */ setAppId(String id)1587 void setAppId(String id) { 1588 mAppId = id; 1589 } 1590 closeOnBack()1591 boolean closeOnBack() { 1592 return mCloseOnBack; 1593 } 1594 setCloseOnBack(boolean close)1595 void setCloseOnBack(boolean close) { 1596 mCloseOnBack = close; 1597 } 1598 getUrl()1599 String getUrl() { 1600 return UrlUtils.filteredUrl(mCurrentState.mUrl); 1601 } 1602 getOriginalUrl()1603 String getOriginalUrl() { 1604 if (mCurrentState.mOriginalUrl == null) { 1605 return getUrl(); 1606 } 1607 return UrlUtils.filteredUrl(mCurrentState.mOriginalUrl); 1608 } 1609 1610 /** 1611 * Get the title of this tab. 1612 */ getTitle()1613 String getTitle() { 1614 if (mCurrentState.mTitle == null && mInPageLoad) { 1615 return mContext.getString(R.string.title_bar_loading); 1616 } 1617 return mCurrentState.mTitle; 1618 } 1619 1620 /** 1621 * Get the favicon of this tab. 1622 */ getFavicon()1623 Bitmap getFavicon() { 1624 if (mCurrentState.mFavicon != null) { 1625 return mCurrentState.mFavicon; 1626 } 1627 return getDefaultFavicon(mContext); 1628 } 1629 isBookmarkedSite()1630 public boolean isBookmarkedSite() { 1631 return mCurrentState.mIsBookmarkedSite; 1632 } 1633 1634 /** 1635 * Return the tab's error console. Creates the console if createIfNEcessary 1636 * is true and we haven't already created the console. 1637 * @param createIfNecessary Flag to indicate if the console should be 1638 * created if it has not been already. 1639 * @return The tab's error console, or null if one has not been created and 1640 * createIfNecessary is false. 1641 */ getErrorConsole(boolean createIfNecessary)1642 ErrorConsoleView getErrorConsole(boolean createIfNecessary) { 1643 if (createIfNecessary && mErrorConsole == null) { 1644 mErrorConsole = new ErrorConsoleView(mContext); 1645 mErrorConsole.setWebView(mMainView); 1646 } 1647 return mErrorConsole; 1648 } 1649 1650 /** 1651 * Sets the security state, clears the SSL certificate error and informs 1652 * the controller. 1653 */ setSecurityState(SecurityState securityState)1654 private void setSecurityState(SecurityState securityState) { 1655 mCurrentState.mSecurityState = securityState; 1656 mCurrentState.mSslCertificateError = null; 1657 mWebViewController.onUpdatedSecurityState(this); 1658 } 1659 1660 /** 1661 * @return The tab's security state. 1662 */ getSecurityState()1663 SecurityState getSecurityState() { 1664 return mCurrentState.mSecurityState; 1665 } 1666 1667 /** 1668 * Gets the SSL certificate error, if any, for the page's main resource. 1669 * This is only non-null when the security state is 1670 * SECURITY_STATE_BAD_CERTIFICATE. 1671 */ getSslCertificateError()1672 SslError getSslCertificateError() { 1673 return mCurrentState.mSslCertificateError; 1674 } 1675 getLoadProgress()1676 int getLoadProgress() { 1677 if (mInPageLoad) { 1678 return mPageLoadProgress; 1679 } 1680 return 100; 1681 } 1682 1683 /** 1684 * @return TRUE if onPageStarted is called while onPageFinished is not 1685 * called yet. 1686 */ inPageLoad()1687 boolean inPageLoad() { 1688 return mInPageLoad; 1689 } 1690 1691 /** 1692 * @return The Bundle with the tab's state if it can be saved, otherwise null 1693 */ saveState()1694 public Bundle saveState() { 1695 // If the WebView is null it means we ran low on memory and we already 1696 // stored the saved state in mSavedState. 1697 if (mMainView == null) { 1698 return mSavedState; 1699 } 1700 1701 if (TextUtils.isEmpty(mCurrentState.mUrl)) { 1702 return null; 1703 } 1704 1705 mSavedState = new Bundle(); 1706 WebBackForwardList savedList = mMainView.saveState(mSavedState); 1707 if (savedList == null || savedList.getSize() == 0) { 1708 Log.w(LOGTAG, "Failed to save back/forward list for " 1709 + mCurrentState.mUrl); 1710 } 1711 1712 mSavedState.putLong(ID, mId); 1713 mSavedState.putString(CURRURL, mCurrentState.mUrl); 1714 mSavedState.putString(CURRTITLE, mCurrentState.mTitle); 1715 mSavedState.putBoolean(INCOGNITO, mMainView.isPrivateBrowsingEnabled()); 1716 if (mAppId != null) { 1717 mSavedState.putString(APPID, mAppId); 1718 } 1719 mSavedState.putBoolean(CLOSEFLAG, mCloseOnBack); 1720 // Remember the parent tab so the relationship can be restored. 1721 if (mParent != null) { 1722 mSavedState.putLong(PARENTTAB, mParent.mId); 1723 } 1724 mSavedState.putBoolean(USERAGENT, 1725 mSettings.hasDesktopUseragent(getWebView())); 1726 return mSavedState; 1727 } 1728 1729 /* 1730 * Restore the state of the tab. 1731 */ restoreState(Bundle b)1732 private void restoreState(Bundle b) { 1733 mSavedState = b; 1734 if (mSavedState == null) { 1735 return; 1736 } 1737 // Restore the internal state even if the WebView fails to restore. 1738 // This will maintain the app id, original url and close-on-exit values. 1739 mId = b.getLong(ID); 1740 mAppId = b.getString(APPID); 1741 mCloseOnBack = b.getBoolean(CLOSEFLAG); 1742 restoreUserAgent(); 1743 String url = b.getString(CURRURL); 1744 String title = b.getString(CURRTITLE); 1745 boolean incognito = b.getBoolean(INCOGNITO); 1746 mCurrentState = new PageState(mContext, incognito, url, null); 1747 mCurrentState.mTitle = title; 1748 synchronized (Tab.this) { 1749 if (mCapture != null) { 1750 DataController.getInstance(mContext).loadThumbnail(this); 1751 } 1752 } 1753 } 1754 restoreUserAgent()1755 private void restoreUserAgent() { 1756 if (mMainView == null || mSavedState == null) { 1757 return; 1758 } 1759 if (mSavedState.getBoolean(USERAGENT) 1760 != mSettings.hasDesktopUseragent(mMainView)) { 1761 mSettings.toggleDesktopUseragent(mMainView); 1762 } 1763 } 1764 updateBookmarkedStatus()1765 public void updateBookmarkedStatus() { 1766 mDataController.queryBookmarkStatus(getUrl(), mIsBookmarkCallback); 1767 } 1768 1769 private DataController.OnQueryUrlIsBookmark mIsBookmarkCallback 1770 = new DataController.OnQueryUrlIsBookmark() { 1771 @Override 1772 public void onQueryUrlIsBookmark(String url, boolean isBookmark) { 1773 if (mCurrentState.mUrl.equals(url)) { 1774 mCurrentState.mIsBookmarkedSite = isBookmark; 1775 mWebViewController.bookmarkedStatusHasChanged(Tab.this); 1776 } 1777 } 1778 }; 1779 getScreenshot()1780 public Bitmap getScreenshot() { 1781 synchronized (Tab.this) { 1782 return mCapture; 1783 } 1784 } 1785 isSnapshot()1786 public boolean isSnapshot() { 1787 return false; 1788 } 1789 1790 private static class SaveCallback implements ValueCallback<Boolean> { 1791 boolean mResult; 1792 1793 @Override onReceiveValue(Boolean value)1794 public void onReceiveValue(Boolean value) { 1795 mResult = value; 1796 synchronized (this) { 1797 notifyAll(); 1798 } 1799 } 1800 1801 } 1802 1803 /** 1804 * Must be called on the UI thread 1805 */ createSnapshotValues()1806 public ContentValues createSnapshotValues() { 1807 return null; 1808 } 1809 1810 /** 1811 * Probably want to call this on a background thread 1812 */ saveViewState(ContentValues values)1813 public boolean saveViewState(ContentValues values) { 1814 return false; 1815 } 1816 compressBitmap(Bitmap bitmap)1817 public byte[] compressBitmap(Bitmap bitmap) { 1818 if (bitmap == null) { 1819 return null; 1820 } 1821 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 1822 bitmap.compress(CompressFormat.PNG, 100, stream); 1823 return stream.toByteArray(); 1824 } 1825 loadUrl(String url, Map<String, String> headers)1826 public void loadUrl(String url, Map<String, String> headers) { 1827 if (mMainView != null) { 1828 mPageLoadProgress = INITIAL_PROGRESS; 1829 mInPageLoad = true; 1830 mCurrentState = new PageState(mContext, false, url, null); 1831 mWebViewController.onPageStarted(this, mMainView, null); 1832 mMainView.loadUrl(url, headers); 1833 } 1834 } 1835 disableUrlOverridingForLoad()1836 public void disableUrlOverridingForLoad() { 1837 mDisableOverrideUrlLoading = true; 1838 } 1839 capture()1840 protected void capture() { 1841 if (mMainView == null || mCapture == null) return; 1842 if (mMainView.getContentWidth() <= 0 || mMainView.getContentHeight() <= 0) { 1843 return; 1844 } 1845 Canvas c = new Canvas(mCapture); 1846 final int left = mMainView.getScrollX(); 1847 final int top = mMainView.getScrollY() + mMainView.getVisibleTitleHeight(); 1848 int state = c.save(); 1849 c.translate(-left, -top); 1850 float scale = mCaptureWidth / (float) mMainView.getWidth(); 1851 c.scale(scale, scale, left, top); 1852 if (mMainView instanceof BrowserWebView) { 1853 ((BrowserWebView)mMainView).drawContent(c); 1854 } else { 1855 mMainView.draw(c); 1856 } 1857 c.restoreToCount(state); 1858 // manually anti-alias the edges for the tilt 1859 c.drawRect(0, 0, 1, mCapture.getHeight(), sAlphaPaint); 1860 c.drawRect(mCapture.getWidth() - 1, 0, mCapture.getWidth(), 1861 mCapture.getHeight(), sAlphaPaint); 1862 c.drawRect(0, 0, mCapture.getWidth(), 1, sAlphaPaint); 1863 c.drawRect(0, mCapture.getHeight() - 1, mCapture.getWidth(), 1864 mCapture.getHeight(), sAlphaPaint); 1865 c.setBitmap(null); 1866 mHandler.removeMessages(MSG_CAPTURE); 1867 persistThumbnail(); 1868 TabControl tc = mWebViewController.getTabControl(); 1869 if (tc != null) { 1870 OnThumbnailUpdatedListener updateListener 1871 = tc.getOnThumbnailUpdatedListener(); 1872 if (updateListener != null) { 1873 updateListener.onThumbnailUpdated(this); 1874 } 1875 } 1876 } 1877 1878 @Override onNewPicture(WebView view, Picture picture)1879 public void onNewPicture(WebView view, Picture picture) { 1880 postCapture(); 1881 } 1882 postCapture()1883 private void postCapture() { 1884 if (!mHandler.hasMessages(MSG_CAPTURE)) { 1885 mHandler.sendEmptyMessageDelayed(MSG_CAPTURE, CAPTURE_DELAY); 1886 } 1887 } 1888 canGoBack()1889 public boolean canGoBack() { 1890 return mMainView != null ? mMainView.canGoBack() : false; 1891 } 1892 canGoForward()1893 public boolean canGoForward() { 1894 return mMainView != null ? mMainView.canGoForward() : false; 1895 } 1896 goBack()1897 public void goBack() { 1898 if (mMainView != null) { 1899 mMainView.goBack(); 1900 } 1901 } 1902 goForward()1903 public void goForward() { 1904 if (mMainView != null) { 1905 mMainView.goForward(); 1906 } 1907 } 1908 1909 /** 1910 * Causes the tab back/forward stack to be cleared once, if the given URL is the next URL 1911 * to be added to the stack. 1912 * 1913 * This is used to ensure that preloaded URLs that are not subsequently seen by the user do 1914 * not appear in the back stack. 1915 */ clearBackStackWhenItemAdded(Pattern urlPattern)1916 public void clearBackStackWhenItemAdded(Pattern urlPattern) { 1917 mClearHistoryUrlPattern = urlPattern; 1918 } 1919 persistThumbnail()1920 protected void persistThumbnail() { 1921 DataController.getInstance(mContext).saveThumbnail(this); 1922 } 1923 deleteThumbnail()1924 protected void deleteThumbnail() { 1925 DataController.getInstance(mContext).deleteThumbnail(this); 1926 } 1927 updateCaptureFromBlob(byte[] blob)1928 void updateCaptureFromBlob(byte[] blob) { 1929 synchronized (Tab.this) { 1930 if (mCapture == null) { 1931 return; 1932 } 1933 ByteBuffer buffer = ByteBuffer.wrap(blob); 1934 try { 1935 mCapture.copyPixelsFromBuffer(buffer); 1936 } catch (RuntimeException rex) { 1937 Log.e(LOGTAG, "Load capture has mismatched sizes; buffer: " 1938 + buffer.capacity() + " blob: " + blob.length 1939 + "capture: " + mCapture.getByteCount()); 1940 throw rex; 1941 } 1942 } 1943 } 1944 1945 @Override toString()1946 public String toString() { 1947 StringBuilder builder = new StringBuilder(100); 1948 builder.append(mId); 1949 builder.append(") has parent: "); 1950 if (getParent() != null) { 1951 builder.append("true["); 1952 builder.append(getParent().getId()); 1953 builder.append("]"); 1954 } else { 1955 builder.append("false"); 1956 } 1957 builder.append(", incog: "); 1958 builder.append(isPrivateBrowsingEnabled()); 1959 if (!isPrivateBrowsingEnabled()) { 1960 builder.append(", title: "); 1961 builder.append(getTitle()); 1962 builder.append(", url: "); 1963 builder.append(getUrl()); 1964 } 1965 return builder.toString(); 1966 } 1967 handleProceededAfterSslError(SslError error)1968 private void handleProceededAfterSslError(SslError error) { 1969 if (error.getUrl().equals(mCurrentState.mUrl)) { 1970 // The security state should currently be SECURITY_STATE_SECURE. 1971 setSecurityState(SecurityState.SECURITY_STATE_BAD_CERTIFICATE); 1972 mCurrentState.mSslCertificateError = error; 1973 } else if (getSecurityState() == SecurityState.SECURITY_STATE_SECURE) { 1974 // The page's main resource is secure and this error is for a 1975 // sub-resource. 1976 setSecurityState(SecurityState.SECURITY_STATE_MIXED); 1977 } 1978 } 1979 setAcceptThirdPartyCookies(boolean accept)1980 public void setAcceptThirdPartyCookies(boolean accept) { 1981 CookieManager cookieManager = CookieManager.getInstance(); 1982 if (mMainView != null) 1983 cookieManager.setAcceptThirdPartyCookies(mMainView, accept); 1984 if (mSubView != null) 1985 cookieManager.setAcceptThirdPartyCookies(mSubView, accept); 1986 } 1987 } 1988