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