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