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