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