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