blob: bb200d81452b74f314a7f2e2b06638a7261c3996 [file] [log] [blame]
Grace Kloba22ac16e2009-10-07 18:00:23 -07001/*
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
17package com.android.browser;
18
Michael Kolb8233fac2010-10-26 16:08:53 -070019import android.app.Activity;
Grace Kloba22ac16e2009-10-07 18:00:23 -070020import android.app.AlertDialog;
Leon Scroggins58d56c62010-01-28 15:12:40 -050021import android.app.SearchManager;
Grace Kloba22ac16e2009-10-07 18:00:23 -070022import android.content.ContentResolver;
John Reckd8c74522011-06-14 08:45:00 -070023import android.content.ContentUris;
24import android.content.ContentValues;
John Reck30c714c2010-12-16 17:30:34 -080025import android.content.Context;
Grace Kloba22ac16e2009-10-07 18:00:23 -070026import android.content.DialogInterface;
Michael Kolbfe251992010-07-08 15:41:55 -070027import android.content.DialogInterface.OnCancelListener;
Jeff Hamilton8ce956c2010-08-17 11:13:53 -050028import android.content.Intent;
Grace Kloba22ac16e2009-10-07 18:00:23 -070029import android.graphics.Bitmap;
John Reck541f55a2011-06-07 16:34:43 -070030import android.graphics.Bitmap.CompressFormat;
John Reck30c714c2010-12-16 17:30:34 -080031import android.graphics.BitmapFactory;
Grace Kloba22ac16e2009-10-07 18:00:23 -070032import android.net.Uri;
33import android.net.http.SslError;
Grace Kloba22ac16e2009-10-07 18:00:23 -070034import android.os.Bundle;
35import android.os.Message;
Kristian Monsen4dce3bf2010-02-02 13:37:09 +000036import android.os.SystemClock;
Brian Carlstrom8862c1d2011-06-02 01:05:55 -070037import android.security.KeyChain;
Brian Carlstromaa09cd82011-06-09 16:04:40 -070038import android.security.KeyChainAliasCallback;
Leon Scrogginsa1cc3fd2010-02-01 16:14:11 -050039import android.speech.RecognizerResultsIntent;
Grace Kloba22ac16e2009-10-07 18:00:23 -070040import android.util.Log;
41import android.view.KeyEvent;
42import android.view.LayoutInflater;
43import android.view.View;
Grace Kloba50c241e2010-04-20 11:07:50 -070044import android.view.ViewStub;
Brian Carlstrom8862c1d2011-06-02 01:05:55 -070045import android.webkit.ClientCertRequestHandler;
Ben Murdochc42addf2010-01-28 15:19:59 +000046import android.webkit.ConsoleMessage;
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -050047import android.webkit.DownloadListener;
Grace Kloba22ac16e2009-10-07 18:00:23 -070048import android.webkit.GeolocationPermissions;
49import android.webkit.HttpAuthHandler;
50import android.webkit.SslErrorHandler;
51import android.webkit.URLUtil;
52import android.webkit.ValueCallback;
53import android.webkit.WebBackForwardList;
Leon Scroggins0c75a8e2010-03-03 16:40:58 -050054import android.webkit.WebBackForwardListClient;
Grace Kloba22ac16e2009-10-07 18:00:23 -070055import android.webkit.WebChromeClient;
56import android.webkit.WebHistoryItem;
John Reck438bf462011-01-12 18:11:46 -080057import android.webkit.WebResourceResponse;
Grace Kloba22ac16e2009-10-07 18:00:23 -070058import android.webkit.WebStorage;
59import android.webkit.WebView;
60import android.webkit.WebViewClient;
Ben Murdoch1d676b62011-01-17 12:54:24 +000061import android.widget.CheckBox;
Grace Kloba22ac16e2009-10-07 18:00:23 -070062import android.widget.LinearLayout;
63import android.widget.TextView;
Ben Murdoch8029a772010-11-16 11:58:21 +000064import android.widget.Toast;
Grace Kloba22ac16e2009-10-07 18:00:23 -070065
John Reck541f55a2011-06-07 16:34:43 -070066import com.android.browser.homepages.HomeProvider;
John Reckd8c74522011-06-14 08:45:00 -070067import com.android.browser.provider.BrowserProvider2.Snapshots;
John Reck541f55a2011-06-07 16:34:43 -070068import com.android.common.speech.LoggingEvents;
69
70import java.io.ByteArrayOutputStream;
71import java.io.DataInputStream;
72import java.io.DataOutputStream;
73import java.io.IOException;
74import java.io.InputStream;
75import java.io.OutputStream;
Michael Kolbfe251992010-07-08 15:41:55 -070076import java.util.ArrayList;
77import java.util.HashMap;
78import java.util.Iterator;
79import java.util.LinkedList;
80import java.util.Map;
81import java.util.Vector;
82
Grace Kloba22ac16e2009-10-07 18:00:23 -070083/**
84 * Class for maintaining Tabs with a main WebView and a subwindow.
85 */
86class Tab {
Michael Kolb8233fac2010-10-26 16:08:53 -070087
Grace Kloba22ac16e2009-10-07 18:00:23 -070088 // Log Tag
89 private static final String LOGTAG = "Tab";
Ben Murdochc42addf2010-01-28 15:19:59 +000090 // Special case the logtag for messages for the Console to make it easier to
91 // filter them and match the logtag used for these messages in older versions
92 // of the browser.
93 private static final String CONSOLE_LOGTAG = "browser";
94
John Reck30c714c2010-12-16 17:30:34 -080095 public enum LockIcon {
96 LOCK_ICON_UNSECURE,
97 LOCK_ICON_SECURE,
98 LOCK_ICON_MIXED,
99 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700100
101 Activity mActivity;
John Reckd8c74522011-06-14 08:45:00 -0700102 protected WebViewController mWebViewController;
Michael Kolb8233fac2010-10-26 16:08:53 -0700103
Michael Kolbc831b632011-05-11 09:30:34 -0700104 // The tab ID
John Reckd8c74522011-06-14 08:45:00 -0700105 private long mId = -1;
Michael Kolbc831b632011-05-11 09:30:34 -0700106
Grace Kloba22ac16e2009-10-07 18:00:23 -0700107 // The Geolocation permissions prompt
108 private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
109 // Main WebView wrapper
Michael Kolba713ec82010-11-29 17:27:06 -0800110 private View mContainer;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700111 // Main WebView
112 private WebView mMainView;
113 // Subwindow container
114 private View mSubViewContainer;
115 // Subwindow WebView
116 private WebView mSubView;
117 // Saved bundle for when we are running low on memory. It contains the
118 // information needed to restore the WebView if the user goes back to the
119 // tab.
120 private Bundle mSavedState;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700121 // Parent Tab. This is the Tab that created this Tab, or null if the Tab was
122 // created by the UI
Michael Kolbc831b632011-05-11 09:30:34 -0700123 private Tab mParent;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700124 // Tab that constructed by this Tab. This is used when this Tab is
125 // destroyed, it clears all mParentTab values in the children.
Michael Kolbc831b632011-05-11 09:30:34 -0700126 private Vector<Tab> mChildren;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700127 // If true, the tab is in the foreground of the current activity.
128 private boolean mInForeground;
Michael Kolb8233fac2010-10-26 16:08:53 -0700129 // If true, the tab is in page loading state (after onPageStarted,
130 // before onPageFinsihed)
131 private boolean mInPageLoad;
John Reck30c714c2010-12-16 17:30:34 -0800132 // The last reported progress of the current page
133 private int mPageLoadProgress;
Kristian Monsen4dce3bf2010-02-02 13:37:09 +0000134 // The time the load started, used to find load page time
135 private long mLoadStartTime;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700136 // Application identifier used to find tabs that another application wants
137 // to reuse.
138 private String mAppId;
139 // Keep the original url around to avoid killing the old WebView if the url
140 // has not changed.
Grace Kloba22ac16e2009-10-07 18:00:23 -0700141 // Error console for the tab
142 private ErrorConsoleView mErrorConsole;
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -0500143 // The listener that gets invoked when a download is started from the
144 // mMainView
145 private final DownloadListener mDownloadListener;
Leon Scroggins0c75a8e2010-03-03 16:40:58 -0500146 // Listener used to know when we move forward or back in the history list.
147 private final WebBackForwardListClient mWebBackForwardListClient;
John Recke969cc52010-12-21 17:24:43 -0800148 private DataController mDataController;
Patrick Scott92066772011-03-10 08:46:27 -0500149 // State of the auto-login request.
150 private DeviceAccountLogin mDeviceAccountLogin;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700151
152 // AsyncTask for downloading touch icons
153 DownloadTouchIcon mTouchIconLoader;
154
Michael Kolbeb95db42011-03-03 10:38:40 -0800155 private Bitmap mScreenshot;
John Reck35e9dd62011-04-25 09:01:54 -0700156 private BrowserSettings mSettings;
Michael Kolbeb95db42011-03-03 10:38:40 -0800157
John Reck30c714c2010-12-16 17:30:34 -0800158 // All the state needed for a page
John Reckd8c74522011-06-14 08:45:00 -0700159 protected static class PageState {
John Reck30c714c2010-12-16 17:30:34 -0800160 String mUrl;
161 String mTitle;
162 LockIcon mLockIcon;
163 Bitmap mFavicon;
John Recke969cc52010-12-21 17:24:43 -0800164 Boolean mIsBookmarkedSite = false;
John Reck30c714c2010-12-16 17:30:34 -0800165
166 PageState(Context c, boolean incognito) {
167 if (incognito) {
168 mUrl = "browser:incognito";
169 mTitle = c.getString(R.string.new_incognito_tab);
John Reck30c714c2010-12-16 17:30:34 -0800170 } else {
171 mUrl = "";
172 mTitle = c.getString(R.string.new_tab);
John Reck30c714c2010-12-16 17:30:34 -0800173 }
Justin Hodc6cbb72011-01-24 15:14:54 -0800174 mFavicon = BitmapFactory.decodeResource(
175 c.getResources(), R.drawable.app_web_browser_sm);
John Reck30c714c2010-12-16 17:30:34 -0800176 mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
177 }
178
179 PageState(Context c, boolean incognito, String url, Bitmap favicon) {
180 mUrl = url;
181 mTitle = null;
182 if (URLUtil.isHttpsUrl(url)) {
183 mLockIcon = LockIcon.LOCK_ICON_SECURE;
184 } else {
185 mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
186 }
187 if (favicon != null) {
188 mFavicon = favicon;
189 } else {
Justin Hodc6cbb72011-01-24 15:14:54 -0800190 mFavicon = BitmapFactory.decodeResource(
191 c.getResources(), R.drawable.app_web_browser_sm);
John Reck30c714c2010-12-16 17:30:34 -0800192 }
193 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700194 }
195
John Reck30c714c2010-12-16 17:30:34 -0800196 // The current/loading page's state
John Reckd8c74522011-06-14 08:45:00 -0700197 protected PageState mCurrentState;
John Reck30c714c2010-12-16 17:30:34 -0800198
Grace Kloba22ac16e2009-10-07 18:00:23 -0700199 // Used for saving and restoring each Tab
Michael Kolbc831b632011-05-11 09:30:34 -0700200 static final String ID = "ID";
Grace Kloba22ac16e2009-10-07 18:00:23 -0700201 static final String CURRURL = "currentUrl";
202 static final String CURRTITLE = "currentTitle";
Grace Kloba22ac16e2009-10-07 18:00:23 -0700203 static final String PARENTTAB = "parentTab";
204 static final String APPID = "appid";
Elliott Slaughter3d6df162010-08-25 13:17:44 -0700205 static final String INCOGNITO = "privateBrowsingEnabled";
Michael Kolbeb95db42011-03-03 10:38:40 -0800206 static final String SCREENSHOT = "screenshot";
John Reckb0a86db2011-05-24 14:05:58 -0700207 static final String USERAGENT = "useragent";
Grace Kloba22ac16e2009-10-07 18:00:23 -0700208
209 // -------------------------------------------------------------------------
210
Leon Scroggins58d56c62010-01-28 15:12:40 -0500211 /**
212 * Private information regarding the latest voice search. If the Tab is not
213 * in voice search mode, this will be null.
214 */
215 private VoiceSearchData mVoiceSearchData;
216 /**
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400217 * Remove voice search mode from this tab.
218 */
219 public void revertVoiceSearchMode() {
220 if (mVoiceSearchData != null) {
221 mVoiceSearchData = null;
222 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700223 mWebViewController.revertVoiceSearchMode(this);
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400224 }
225 }
226 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700227
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400228 /**
Leon Scroggins58d56c62010-01-28 15:12:40 -0500229 * Return whether the tab is in voice search mode.
230 */
231 public boolean isInVoiceSearchMode() {
232 return mVoiceSearchData != null;
233 }
234 /**
Leon Scroggins IIIc1f5ae22010-06-29 17:11:29 -0400235 * Return true if the Tab is in voice search mode and the voice search
236 * Intent came with a String identifying that Google provided the Intent.
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500237 */
238 public boolean voiceSearchSourceIsGoogle() {
239 return mVoiceSearchData != null && mVoiceSearchData.mSourceIsGoogle;
240 }
241 /**
Leon Scroggins58d56c62010-01-28 15:12:40 -0500242 * Get the title to display for the current voice search page. If the Tab
243 * is not in voice search mode, return null.
244 */
245 public String getVoiceDisplayTitle() {
246 if (mVoiceSearchData == null) return null;
247 return mVoiceSearchData.mLastVoiceSearchTitle;
248 }
249 /**
250 * Get the latest array of voice search results, to be passed to the
251 * BrowserProvider. If the Tab is not in voice search mode, return null.
252 */
253 public ArrayList<String> getVoiceSearchResults() {
254 if (mVoiceSearchData == null) return null;
255 return mVoiceSearchData.mVoiceSearchResults;
256 }
257 /**
258 * Activate voice search mode.
259 * @param intent Intent which has the results to use, or an index into the
260 * results when reusing the old results.
261 */
262 /* package */ void activateVoiceSearchMode(Intent intent) {
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500263 int index = 0;
Leon Scroggins58d56c62010-01-28 15:12:40 -0500264 ArrayList<String> results = intent.getStringArrayListExtra(
Leon Scrogginsa1cc3fd2010-02-01 16:14:11 -0500265 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_STRINGS);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500266 if (results != null) {
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500267 ArrayList<String> urls = intent.getStringArrayListExtra(
268 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_URLS);
269 ArrayList<String> htmls = intent.getStringArrayListExtra(
270 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_HTML);
271 ArrayList<String> baseUrls = intent.getStringArrayListExtra(
272 RecognizerResultsIntent
273 .EXTRA_VOICE_SEARCH_RESULT_HTML_BASE_URLS);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500274 // This tab is now entering voice search mode for the first time, or
275 // a new voice search was done.
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500276 int size = results.size();
277 if (urls == null || size != urls.size()) {
Leon Scroggins58d56c62010-01-28 15:12:40 -0500278 throw new AssertionError("improper extras passed in Intent");
279 }
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500280 if (htmls == null || htmls.size() != size || baseUrls == null ||
281 (baseUrls.size() != size && baseUrls.size() != 1)) {
282 // If either of these arrays are empty/incorrectly sized, ignore
283 // them.
284 htmls = null;
285 baseUrls = null;
286 }
287 mVoiceSearchData = new VoiceSearchData(results, urls, htmls,
288 baseUrls);
Leon Scroggins9df94972010-03-08 18:20:35 -0500289 mVoiceSearchData.mHeaders = intent.getParcelableArrayListExtra(
290 RecognizerResultsIntent
291 .EXTRA_VOICE_SEARCH_RESULT_HTTP_HEADERS);
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500292 mVoiceSearchData.mSourceIsGoogle = intent.getBooleanExtra(
293 VoiceSearchData.SOURCE_IS_GOOGLE, false);
Leon Scroggins2ee4a5a2010-03-15 16:56:57 -0400294 mVoiceSearchData.mVoiceSearchIntent = new Intent(intent);
Leon Scrogginse10dde52010-03-08 19:53:03 -0500295 }
296 String extraData = intent.getStringExtra(
297 SearchManager.EXTRA_DATA_KEY);
298 if (extraData != null) {
299 index = Integer.parseInt(extraData);
300 if (index >= mVoiceSearchData.mVoiceSearchResults.size()) {
301 throw new AssertionError("index must be less than "
302 + "size of mVoiceSearchResults");
303 }
304 if (mVoiceSearchData.mSourceIsGoogle) {
305 Intent logIntent = new Intent(
306 LoggingEvents.ACTION_LOG_EVENT);
307 logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
308 LoggingEvents.VoiceSearch.N_BEST_CHOOSE);
309 logIntent.putExtra(
310 LoggingEvents.VoiceSearch.EXTRA_N_BEST_CHOOSE_INDEX,
311 index);
312 mActivity.sendBroadcast(logIntent);
313 }
314 if (mVoiceSearchData.mVoiceSearchIntent != null) {
Leon Scroggins2ee4a5a2010-03-15 16:56:57 -0400315 // Copy the Intent, so that each history item will have its own
316 // Intent, with different (or none) extra data.
317 Intent latest = new Intent(mVoiceSearchData.mVoiceSearchIntent);
318 latest.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
319 mVoiceSearchData.mVoiceSearchIntent = latest;
Leon Scroggins58d56c62010-01-28 15:12:40 -0500320 }
321 }
322 mVoiceSearchData.mLastVoiceSearchTitle
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500323 = mVoiceSearchData.mVoiceSearchResults.get(index);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500324 if (mInForeground) {
Michael Kolb11d19782011-03-20 10:17:40 -0700325 mWebViewController.activateVoiceSearchMode(
326 mVoiceSearchData.mLastVoiceSearchTitle,
327 mVoiceSearchData.mVoiceSearchResults);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500328 }
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500329 if (mVoiceSearchData.mVoiceSearchHtmls != null) {
330 // When index was found it was already ensured that it was valid
331 String uriString = mVoiceSearchData.mVoiceSearchHtmls.get(index);
332 if (uriString != null) {
333 Uri dataUri = Uri.parse(uriString);
334 if (RecognizerResultsIntent.URI_SCHEME_INLINE.equals(
335 dataUri.getScheme())) {
336 // If there is only one base URL, use it. If there are
337 // more, there will be one for each index, so use the base
338 // URL corresponding to the index.
339 String baseUrl = mVoiceSearchData.mVoiceSearchBaseUrls.get(
340 mVoiceSearchData.mVoiceSearchBaseUrls.size() > 1 ?
341 index : 0);
342 mVoiceSearchData.mLastVoiceSearchUrl = baseUrl;
343 mMainView.loadDataWithBaseURL(baseUrl,
344 uriString.substring(RecognizerResultsIntent
345 .URI_SCHEME_INLINE.length() + 1), "text/html",
346 "utf-8", baseUrl);
347 return;
348 }
349 }
350 }
Leon Scroggins58d56c62010-01-28 15:12:40 -0500351 mVoiceSearchData.mLastVoiceSearchUrl
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500352 = mVoiceSearchData.mVoiceSearchUrls.get(index);
353 if (null == mVoiceSearchData.mLastVoiceSearchUrl) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700354 mVoiceSearchData.mLastVoiceSearchUrl = UrlUtils.smartUrlFilter(
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500355 mVoiceSearchData.mLastVoiceSearchTitle);
356 }
Leon Scroggins9df94972010-03-08 18:20:35 -0500357 Map<String, String> headers = null;
358 if (mVoiceSearchData.mHeaders != null) {
359 int bundleIndex = mVoiceSearchData.mHeaders.size() == 1 ? 0
360 : index;
361 Bundle bundle = mVoiceSearchData.mHeaders.get(bundleIndex);
362 if (bundle != null && !bundle.isEmpty()) {
363 Iterator<String> iter = bundle.keySet().iterator();
364 headers = new HashMap<String, String>();
365 while (iter.hasNext()) {
366 String key = iter.next();
367 headers.put(key, bundle.getString(key));
368 }
369 }
370 }
371 mMainView.loadUrl(mVoiceSearchData.mLastVoiceSearchUrl, headers);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500372 }
373 /* package */ static class VoiceSearchData {
Leon Scroggins58d56c62010-01-28 15:12:40 -0500374 public VoiceSearchData(ArrayList<String> results,
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500375 ArrayList<String> urls, ArrayList<String> htmls,
376 ArrayList<String> baseUrls) {
Leon Scroggins58d56c62010-01-28 15:12:40 -0500377 mVoiceSearchResults = results;
378 mVoiceSearchUrls = urls;
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500379 mVoiceSearchHtmls = htmls;
380 mVoiceSearchBaseUrls = baseUrls;
Leon Scroggins58d56c62010-01-28 15:12:40 -0500381 }
382 /*
383 * ArrayList of suggestions to be displayed when opening the
384 * SearchManager
385 */
386 public ArrayList<String> mVoiceSearchResults;
387 /*
388 * ArrayList of urls, associated with the suggestions in
389 * mVoiceSearchResults.
390 */
391 public ArrayList<String> mVoiceSearchUrls;
392 /*
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500393 * ArrayList holding content to load for each item in
394 * mVoiceSearchResults.
395 */
396 public ArrayList<String> mVoiceSearchHtmls;
397 /*
398 * ArrayList holding base urls for the items in mVoiceSearchResults.
399 * If non null, this will either have the same size as
400 * mVoiceSearchResults or have a size of 1, in which case all will use
401 * the same base url
402 */
403 public ArrayList<String> mVoiceSearchBaseUrls;
404 /*
Leon Scroggins58d56c62010-01-28 15:12:40 -0500405 * The last url provided by voice search. Used for comparison to see if
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500406 * we are going to a page by some method besides voice search.
Leon Scroggins58d56c62010-01-28 15:12:40 -0500407 */
408 public String mLastVoiceSearchUrl;
409 /**
410 * The last title used for voice search. Needed to update the title bar
411 * when switching tabs.
412 */
413 public String mLastVoiceSearchTitle;
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500414 /**
415 * Whether the Intent which turned on voice search mode contained the
416 * String signifying that Google was the source.
417 */
418 public boolean mSourceIsGoogle;
419 /**
Leon Scroggins9df94972010-03-08 18:20:35 -0500420 * List of headers to be passed into the WebView containing location
421 * information
422 */
423 public ArrayList<Bundle> mHeaders;
424 /**
Leon Scroggins0c75a8e2010-03-03 16:40:58 -0500425 * The Intent used to invoke voice search. Placed on the
426 * WebHistoryItem so that when coming back to a previous voice search
427 * page we can again activate voice search.
428 */
Leon Scrogginse10dde52010-03-08 19:53:03 -0500429 public Intent mVoiceSearchIntent;
Leon Scroggins0c75a8e2010-03-03 16:40:58 -0500430 /**
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500431 * String used to identify Google as the source of voice search.
432 */
433 public static String SOURCE_IS_GOOGLE
434 = "android.speech.extras.SOURCE_IS_GOOGLE";
Leon Scroggins58d56c62010-01-28 15:12:40 -0500435 }
436
Grace Kloba22ac16e2009-10-07 18:00:23 -0700437 // Container class for the next error dialog that needs to be displayed
438 private class ErrorDialog {
439 public final int mTitle;
440 public final String mDescription;
441 public final int mError;
442 ErrorDialog(int title, String desc, int error) {
443 mTitle = title;
444 mDescription = desc;
445 mError = error;
446 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700447 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700448
449 private void processNextError() {
450 if (mQueuedErrors == null) {
451 return;
452 }
453 // The first one is currently displayed so just remove it.
454 mQueuedErrors.removeFirst();
455 if (mQueuedErrors.size() == 0) {
456 mQueuedErrors = null;
457 return;
458 }
459 showError(mQueuedErrors.getFirst());
460 }
461
462 private DialogInterface.OnDismissListener mDialogListener =
463 new DialogInterface.OnDismissListener() {
464 public void onDismiss(DialogInterface d) {
465 processNextError();
466 }
467 };
468 private LinkedList<ErrorDialog> mQueuedErrors;
469
470 private void queueError(int err, String desc) {
471 if (mQueuedErrors == null) {
472 mQueuedErrors = new LinkedList<ErrorDialog>();
473 }
474 for (ErrorDialog d : mQueuedErrors) {
475 if (d.mError == err) {
476 // Already saw a similar error, ignore the new one.
477 return;
478 }
479 }
480 ErrorDialog errDialog = new ErrorDialog(
481 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
482 R.string.browserFrameFileErrorLabel :
483 R.string.browserFrameNetworkErrorLabel,
484 desc, err);
485 mQueuedErrors.addLast(errDialog);
486
487 // Show the dialog now if the queue was empty and it is in foreground
488 if (mQueuedErrors.size() == 1 && mInForeground) {
489 showError(errDialog);
490 }
491 }
492
493 private void showError(ErrorDialog errDialog) {
494 if (mInForeground) {
495 AlertDialog d = new AlertDialog.Builder(mActivity)
496 .setTitle(errDialog.mTitle)
497 .setMessage(errDialog.mDescription)
498 .setPositiveButton(R.string.ok, null)
499 .create();
500 d.setOnDismissListener(mDialogListener);
501 d.show();
502 }
503 }
504
505 // -------------------------------------------------------------------------
506 // WebViewClient implementation for the main WebView
507 // -------------------------------------------------------------------------
508
509 private final WebViewClient mWebViewClient = new WebViewClient() {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500510 private Message mDontResend;
511 private Message mResend;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700512 @Override
513 public void onPageStarted(WebView view, String url, Bitmap favicon) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700514 mInPageLoad = true;
John Reck30c714c2010-12-16 17:30:34 -0800515 mPageLoadProgress = 0;
516 mCurrentState = new PageState(mActivity,
517 view.isPrivateBrowsingEnabled(), url, favicon);
Kristian Monsen4dce3bf2010-02-02 13:37:09 +0000518 mLoadStartTime = SystemClock.uptimeMillis();
Leon Scroggins58d56c62010-01-28 15:12:40 -0500519 if (mVoiceSearchData != null
520 && !url.equals(mVoiceSearchData.mLastVoiceSearchUrl)) {
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500521 if (mVoiceSearchData.mSourceIsGoogle) {
522 Intent i = new Intent(LoggingEvents.ACTION_LOG_EVENT);
523 i.putExtra(LoggingEvents.EXTRA_FLUSH, true);
524 mActivity.sendBroadcast(i);
525 }
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400526 revertVoiceSearchMode();
Leon Scroggins58d56c62010-01-28 15:12:40 -0500527 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700528
Grace Kloba22ac16e2009-10-07 18:00:23 -0700529
530 // If we start a touch icon load and then load a new page, we don't
531 // want to cancel the current touch icon loader. But, we do want to
532 // create a new one when the touch icon url is known.
533 if (mTouchIconLoader != null) {
534 mTouchIconLoader.mTab = null;
535 mTouchIconLoader = null;
536 }
537
538 // reset the error console
539 if (mErrorConsole != null) {
540 mErrorConsole.clearErrorMessages();
Michael Kolb8233fac2010-10-26 16:08:53 -0700541 if (mWebViewController.shouldShowErrorConsole()) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700542 mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
543 }
544 }
545
Patrick Scott92066772011-03-10 08:46:27 -0500546 // Cancel the auto-login process.
547 if (mDeviceAccountLogin != null) {
548 mDeviceAccountLogin.cancel();
549 mDeviceAccountLogin = null;
550 mWebViewController.hideAutoLogin(Tab.this);
551 }
552
Grace Kloba22ac16e2009-10-07 18:00:23 -0700553 // finally update the UI in the activity if it is in the foreground
John Reck324d4402011-01-11 16:56:42 -0800554 mWebViewController.onPageStarted(Tab.this, view, favicon);
Leon Scroggins4cd97792010-12-03 15:31:56 -0500555
John Recke969cc52010-12-21 17:24:43 -0800556 updateBookmarkedStatus();
Grace Kloba22ac16e2009-10-07 18:00:23 -0700557 }
558
559 @Override
560 public void onPageFinished(WebView view, String url) {
John Reck5b691842010-11-29 11:21:13 -0800561 if (!isPrivateBrowsingEnabled()) {
562 LogTag.logPageFinishedLoading(
563 url, SystemClock.uptimeMillis() - mLoadStartTime);
564 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700565 mInPageLoad = false;
John Reck30c714c2010-12-16 17:30:34 -0800566 // Sync state (in case of stop/timeout)
567 mCurrentState.mUrl = view.getUrl();
John Reck6c702ee2011-01-07 09:41:53 -0800568 if (mCurrentState.mUrl == null) {
569 mCurrentState.mUrl = url != null ? url : "";
570 }
John Reck30c714c2010-12-16 17:30:34 -0800571 mCurrentState.mTitle = view.getTitle();
572 mCurrentState.mFavicon = view.getFavicon();
573 if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) {
574 // In case we stop when loading an HTTPS page from an HTTP page
575 // but before a provisional load occurred
576 mCurrentState.mLockIcon = LockIcon.LOCK_ICON_UNSECURE;
577 }
John Reck324d4402011-01-11 16:56:42 -0800578 mWebViewController.onPageFinished(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700579 }
580
581 // return true if want to hijack the url to let another app to handle it
582 @Override
583 public boolean shouldOverrideUrlLoading(WebView view, String url) {
Leon Scroggins IIIc1f5ae22010-06-29 17:11:29 -0400584 if (voiceSearchSourceIsGoogle()) {
585 // This method is called when the user clicks on a link.
586 // VoiceSearchMode is turned off when the user leaves the
587 // Google results page, so at this point the user must be on
588 // that page. If the user clicked a link on that page, assume
589 // that the voice search was effective, and broadcast an Intent
590 // so a receiver can take note of that fact.
591 Intent logIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT);
592 logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
593 LoggingEvents.VoiceSearch.RESULT_CLICKED);
594 mActivity.sendBroadcast(logIntent);
595 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700596 if (mInForeground) {
Michael Kolb18eb3772010-12-10 14:29:51 -0800597 return mWebViewController.shouldOverrideUrlLoading(Tab.this,
598 view, url);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700599 } else {
600 return false;
601 }
602 }
603
604 /**
605 * Updates the lock icon. This method is called when we discover another
606 * resource to be loaded for this page (for example, javascript). While
607 * we update the icon type, we do not update the lock icon itself until
608 * we are done loading, it is slightly more secure this way.
609 */
610 @Override
611 public void onLoadResource(WebView view, String url) {
612 if (url != null && url.length() > 0) {
613 // It is only if the page claims to be secure that we may have
614 // to update the lock:
John Reck30c714c2010-12-16 17:30:34 -0800615 if (mCurrentState.mLockIcon == LockIcon.LOCK_ICON_SECURE) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700616 // If NOT a 'safe' url, change the lock to mixed content!
617 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url)
618 || URLUtil.isAboutUrl(url))) {
John Reck30c714c2010-12-16 17:30:34 -0800619 mCurrentState.mLockIcon = LockIcon.LOCK_ICON_MIXED;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700620 }
621 }
622 }
623 }
624
625 /**
Grace Kloba22ac16e2009-10-07 18:00:23 -0700626 * Show a dialog informing the user of the network error reported by
627 * WebCore if it is in the foreground.
628 */
629 @Override
630 public void onReceivedError(WebView view, int errorCode,
631 String description, String failingUrl) {
632 if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
633 errorCode != WebViewClient.ERROR_CONNECT &&
634 errorCode != WebViewClient.ERROR_BAD_URL &&
635 errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
636 errorCode != WebViewClient.ERROR_FILE) {
637 queueError(errorCode, description);
638 }
Jeff Hamilton47654f42010-09-07 09:57:51 -0500639
640 // Don't log URLs when in private browsing mode
Rob Tsukf8bdfce2010-10-07 15:41:16 -0700641 if (!isPrivateBrowsingEnabled()) {
Jeff Hamilton47654f42010-09-07 09:57:51 -0500642 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
643 + " " + description);
644 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700645 }
646
647 /**
648 * Check with the user if it is ok to resend POST data as the page they
649 * are trying to navigate to is the result of a POST.
650 */
651 @Override
652 public void onFormResubmission(WebView view, final Message dontResend,
653 final Message resend) {
654 if (!mInForeground) {
655 dontResend.sendToTarget();
656 return;
657 }
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500658 if (mDontResend != null) {
659 Log.w(LOGTAG, "onFormResubmission should not be called again "
660 + "while dialog is still up");
661 dontResend.sendToTarget();
662 return;
663 }
664 mDontResend = dontResend;
665 mResend = resend;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700666 new AlertDialog.Builder(mActivity).setTitle(
667 R.string.browserFrameFormResubmitLabel).setMessage(
668 R.string.browserFrameFormResubmitMessage)
669 .setPositiveButton(R.string.ok,
670 new DialogInterface.OnClickListener() {
671 public void onClick(DialogInterface dialog,
672 int which) {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500673 if (mResend != null) {
674 mResend.sendToTarget();
675 mResend = null;
676 mDontResend = null;
677 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700678 }
679 }).setNegativeButton(R.string.cancel,
680 new DialogInterface.OnClickListener() {
681 public void onClick(DialogInterface dialog,
682 int which) {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500683 if (mDontResend != null) {
684 mDontResend.sendToTarget();
685 mResend = null;
686 mDontResend = null;
687 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700688 }
689 }).setOnCancelListener(new OnCancelListener() {
690 public void onCancel(DialogInterface dialog) {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500691 if (mDontResend != null) {
692 mDontResend.sendToTarget();
693 mResend = null;
694 mDontResend = null;
695 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700696 }
697 }).show();
698 }
699
700 /**
701 * Insert the url into the visited history database.
702 * @param url The url to be inserted.
703 * @param isReload True if this url is being reloaded.
704 * FIXME: Not sure what to do when reloading the page.
705 */
706 @Override
707 public void doUpdateVisitedHistory(WebView view, String url,
708 boolean isReload) {
John Reck324d4402011-01-11 16:56:42 -0800709 mWebViewController.doUpdateVisitedHistory(Tab.this, isReload);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700710 }
711
712 /**
713 * Displays SSL error(s) dialog to the user.
714 */
715 @Override
716 public void onReceivedSslError(final WebView view,
717 final SslErrorHandler handler, final SslError error) {
718 if (!mInForeground) {
719 handler.cancel();
John Reck30c714c2010-12-16 17:30:34 -0800720 setLockIconType(LockIcon.LOCK_ICON_UNSECURE);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700721 return;
722 }
John Reck35e9dd62011-04-25 09:01:54 -0700723 if (mSettings.showSecurityWarnings()) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700724 final LayoutInflater factory =
725 LayoutInflater.from(mActivity);
726 final View warningsView =
727 factory.inflate(R.layout.ssl_warnings, null);
728 final LinearLayout placeholder =
729 (LinearLayout)warningsView.findViewById(R.id.placeholder);
730
731 if (error.hasError(SslError.SSL_UNTRUSTED)) {
732 LinearLayout ll = (LinearLayout)factory
733 .inflate(R.layout.ssl_warning, null);
734 ((TextView)ll.findViewById(R.id.warning))
735 .setText(R.string.ssl_untrusted);
736 placeholder.addView(ll);
737 }
738
739 if (error.hasError(SslError.SSL_IDMISMATCH)) {
740 LinearLayout ll = (LinearLayout)factory
741 .inflate(R.layout.ssl_warning, null);
742 ((TextView)ll.findViewById(R.id.warning))
743 .setText(R.string.ssl_mismatch);
744 placeholder.addView(ll);
745 }
746
747 if (error.hasError(SslError.SSL_EXPIRED)) {
748 LinearLayout ll = (LinearLayout)factory
749 .inflate(R.layout.ssl_warning, null);
750 ((TextView)ll.findViewById(R.id.warning))
751 .setText(R.string.ssl_expired);
752 placeholder.addView(ll);
753 }
754
755 if (error.hasError(SslError.SSL_NOTYETVALID)) {
756 LinearLayout ll = (LinearLayout)factory
757 .inflate(R.layout.ssl_warning, null);
758 ((TextView)ll.findViewById(R.id.warning))
759 .setText(R.string.ssl_not_yet_valid);
760 placeholder.addView(ll);
761 }
762
763 new AlertDialog.Builder(mActivity).setTitle(
764 R.string.security_warning).setIcon(
765 android.R.drawable.ic_dialog_alert).setView(
766 warningsView).setPositiveButton(R.string.ssl_continue,
767 new DialogInterface.OnClickListener() {
768 public void onClick(DialogInterface dialog,
769 int whichButton) {
770 handler.proceed();
771 }
772 }).setNeutralButton(R.string.view_certificate,
773 new DialogInterface.OnClickListener() {
774 public void onClick(DialogInterface dialog,
775 int whichButton) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700776 mWebViewController.showSslCertificateOnError(view,
Grace Kloba22ac16e2009-10-07 18:00:23 -0700777 handler, error);
778 }
Ben Murdocha49b8292010-11-16 11:56:04 +0000779 }).setNegativeButton(R.string.ssl_go_back,
Grace Kloba22ac16e2009-10-07 18:00:23 -0700780 new DialogInterface.OnClickListener() {
781 public void onClick(DialogInterface dialog,
782 int whichButton) {
John Reck30c714c2010-12-16 17:30:34 -0800783 dialog.cancel();
Grace Kloba22ac16e2009-10-07 18:00:23 -0700784 }
785 }).setOnCancelListener(
786 new DialogInterface.OnCancelListener() {
787 public void onCancel(DialogInterface dialog) {
788 handler.cancel();
John Reck30c714c2010-12-16 17:30:34 -0800789 setLockIconType(LockIcon.LOCK_ICON_UNSECURE);
790 mWebViewController.onUserCanceledSsl(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700791 }
792 }).show();
793 } else {
794 handler.proceed();
795 }
796 }
797
798 /**
Brian Carlstrom8862c1d2011-06-02 01:05:55 -0700799 * Displays client certificate request to the user.
800 */
801 @Override
802 public void onReceivedClientCertRequest(final WebView view,
803 final ClientCertRequestHandler handler, final String host_and_port) {
804 if (!mInForeground) {
805 handler.ignore();
806 return;
807 }
Brian Carlstromaa09cd82011-06-09 16:04:40 -0700808 KeyChain.choosePrivateKeyAlias(mActivity, new KeyChainAliasCallback() {
Brian Carlstrom8862c1d2011-06-02 01:05:55 -0700809 @Override public void alias(String alias) {
810 if (alias == null) {
811 handler.cancel();
812 return;
813 }
814 new KeyChainLookup(mActivity, handler, alias).execute();
815 }
Brian Carlstromaa09cd82011-06-09 16:04:40 -0700816 }, null, null, null, -1);
Brian Carlstrom8862c1d2011-06-02 01:05:55 -0700817 }
818
819 /**
Grace Kloba22ac16e2009-10-07 18:00:23 -0700820 * Handles an HTTP authentication request.
821 *
822 * @param handler The authentication handler
823 * @param host The host
824 * @param realm The realm
825 */
826 @Override
827 public void onReceivedHttpAuthRequest(WebView view,
828 final HttpAuthHandler handler, final String host,
829 final String realm) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700830 mWebViewController.onReceivedHttpAuthRequest(Tab.this, view, handler, host, realm);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700831 }
832
833 @Override
John Reck438bf462011-01-12 18:11:46 -0800834 public WebResourceResponse shouldInterceptRequest(WebView view,
835 String url) {
836 WebResourceResponse res = HomeProvider.shouldInterceptRequest(
837 mActivity, url);
838 return res;
839 }
840
841 @Override
Grace Kloba22ac16e2009-10-07 18:00:23 -0700842 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
843 if (!mInForeground) {
844 return false;
845 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700846 return mWebViewController.shouldOverrideKeyEvent(event);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700847 }
848
849 @Override
850 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700851 if (!mInForeground) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700852 return;
853 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700854 mWebViewController.onUnhandledKeyEvent(event);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700855 }
Patrick Scott92066772011-03-10 08:46:27 -0500856
857 @Override
858 public void onReceivedLoginRequest(WebView view, String realm,
859 String account, String args) {
860 new DeviceAccountLogin(mActivity, view, Tab.this, mWebViewController)
861 .handleLogin(realm, account, args);
862 }
863
Grace Kloba22ac16e2009-10-07 18:00:23 -0700864 };
865
Patrick Scott92066772011-03-10 08:46:27 -0500866 // Called by DeviceAccountLogin when the Tab needs to have the auto-login UI
867 // displayed.
868 void setDeviceAccountLogin(DeviceAccountLogin login) {
869 mDeviceAccountLogin = login;
870 }
871
872 // Returns non-null if the title bar should display the auto-login UI.
873 DeviceAccountLogin getDeviceAccountLogin() {
874 return mDeviceAccountLogin;
875 }
876
Grace Kloba22ac16e2009-10-07 18:00:23 -0700877 // -------------------------------------------------------------------------
878 // WebChromeClient implementation for the main WebView
879 // -------------------------------------------------------------------------
880
881 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
882 // Helper method to create a new tab or sub window.
883 private void createWindow(final boolean dialog, final Message msg) {
884 WebView.WebViewTransport transport =
885 (WebView.WebViewTransport) msg.obj;
886 if (dialog) {
887 createSubWindow();
Michael Kolb8233fac2010-10-26 16:08:53 -0700888 mWebViewController.attachSubWindow(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700889 transport.setWebView(mSubView);
890 } else {
Michael Kolb7bcafde2011-05-09 13:55:59 -0700891 final Tab newTab = mWebViewController.openTab(null,
John Reck5949c662011-05-27 09:52:29 -0700892 Tab.this, true, true);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700893 transport.setWebView(newTab.getWebView());
894 }
895 msg.sendToTarget();
896 }
897
898 @Override
899 public boolean onCreateWindow(WebView view, final boolean dialog,
900 final boolean userGesture, final Message resultMsg) {
901 // only allow new window or sub window for the foreground case
902 if (!mInForeground) {
903 return false;
904 }
905 // Short-circuit if we can't create any more tabs or sub windows.
906 if (dialog && mSubView != null) {
907 new AlertDialog.Builder(mActivity)
908 .setTitle(R.string.too_many_subwindows_dialog_title)
909 .setIcon(android.R.drawable.ic_dialog_alert)
910 .setMessage(R.string.too_many_subwindows_dialog_message)
911 .setPositiveButton(R.string.ok, null)
912 .show();
913 return false;
Michael Kolb8233fac2010-10-26 16:08:53 -0700914 } else if (!mWebViewController.getTabControl().canCreateNewTab()) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700915 new AlertDialog.Builder(mActivity)
916 .setTitle(R.string.too_many_windows_dialog_title)
917 .setIcon(android.R.drawable.ic_dialog_alert)
918 .setMessage(R.string.too_many_windows_dialog_message)
919 .setPositiveButton(R.string.ok, null)
920 .show();
921 return false;
922 }
923
924 // Short-circuit if this was a user gesture.
925 if (userGesture) {
926 createWindow(dialog, resultMsg);
927 return true;
928 }
929
930 // Allow the popup and create the appropriate window.
931 final AlertDialog.OnClickListener allowListener =
932 new AlertDialog.OnClickListener() {
933 public void onClick(DialogInterface d,
934 int which) {
935 createWindow(dialog, resultMsg);
936 }
937 };
938
939 // Block the popup by returning a null WebView.
940 final AlertDialog.OnClickListener blockListener =
941 new AlertDialog.OnClickListener() {
942 public void onClick(DialogInterface d, int which) {
943 resultMsg.sendToTarget();
944 }
945 };
946
947 // Build a confirmation dialog to display to the user.
948 final AlertDialog d =
949 new AlertDialog.Builder(mActivity)
950 .setTitle(R.string.attention)
951 .setIcon(android.R.drawable.ic_dialog_alert)
952 .setMessage(R.string.popup_window_attempt)
953 .setPositiveButton(R.string.allow, allowListener)
954 .setNegativeButton(R.string.block, blockListener)
955 .setCancelable(false)
956 .create();
957
958 // Show the confirmation dialog.
959 d.show();
960 return true;
961 }
962
963 @Override
Patrick Scotteb5061b2009-11-18 15:00:30 -0500964 public void onRequestFocus(WebView view) {
965 if (!mInForeground) {
Michael Kolbc831b632011-05-11 09:30:34 -0700966 mWebViewController.switchToTab(Tab.this);
Patrick Scotteb5061b2009-11-18 15:00:30 -0500967 }
968 }
969
970 @Override
Grace Kloba22ac16e2009-10-07 18:00:23 -0700971 public void onCloseWindow(WebView window) {
Michael Kolbc831b632011-05-11 09:30:34 -0700972 if (mParent != null) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700973 // JavaScript can only close popup window.
974 if (mInForeground) {
Michael Kolbc831b632011-05-11 09:30:34 -0700975 mWebViewController.switchToTab(mParent);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700976 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700977 mWebViewController.closeTab(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700978 }
979 }
980
981 @Override
982 public void onProgressChanged(WebView view, int newProgress) {
John Reck30c714c2010-12-16 17:30:34 -0800983 mPageLoadProgress = newProgress;
984 mWebViewController.onProgressChanged(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700985 }
986
987 @Override
Leon Scroggins21d9b902010-03-11 09:33:11 -0500988 public void onReceivedTitle(WebView view, final String title) {
John Reck30c714c2010-12-16 17:30:34 -0800989 mCurrentState.mTitle = title;
Michael Kolb8233fac2010-10-26 16:08:53 -0700990 mWebViewController.onReceivedTitle(Tab.this, title);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700991 }
992
993 @Override
994 public void onReceivedIcon(WebView view, Bitmap icon) {
John Reck30c714c2010-12-16 17:30:34 -0800995 mCurrentState.mFavicon = icon;
Michael Kolb8233fac2010-10-26 16:08:53 -0700996 mWebViewController.onFavicon(Tab.this, view, icon);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700997 }
998
999 @Override
1000 public void onReceivedTouchIconUrl(WebView view, String url,
1001 boolean precomposed) {
1002 final ContentResolver cr = mActivity.getContentResolver();
Leon Scrogginsc8393d92010-04-23 14:58:16 -04001003 // Let precomposed icons take precedence over non-composed
1004 // icons.
1005 if (precomposed && mTouchIconLoader != null) {
1006 mTouchIconLoader.cancel(false);
1007 mTouchIconLoader = null;
1008 }
1009 // Have only one async task at a time.
1010 if (mTouchIconLoader == null) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001011 mTouchIconLoader = new DownloadTouchIcon(Tab.this,
1012 mActivity, cr, view);
Leon Scrogginsc8393d92010-04-23 14:58:16 -04001013 mTouchIconLoader.execute(url);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001014 }
1015 }
1016
1017 @Override
1018 public void onShowCustomView(View view,
1019 WebChromeClient.CustomViewCallback callback) {
Derek Sollenberger2d4f1e22011-06-01 14:50:42 -04001020 onShowCustomView(view, mActivity.getRequestedOrientation(), callback);
1021 }
1022
1023 @Override
1024 public void onShowCustomView(View view, int requestedOrientation,
1025 WebChromeClient.CustomViewCallback callback) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001026 if (mInForeground) mWebViewController.showCustomView(Tab.this, view,
Derek Sollenberger2d4f1e22011-06-01 14:50:42 -04001027 requestedOrientation, callback);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001028 }
1029
1030 @Override
1031 public void onHideCustomView() {
Michael Kolb8233fac2010-10-26 16:08:53 -07001032 if (mInForeground) mWebViewController.hideCustomView();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001033 }
1034
1035 /**
1036 * The origin has exceeded its database quota.
1037 * @param url the URL that exceeded the quota
1038 * @param databaseIdentifier the identifier of the database on which the
1039 * transaction that caused the quota overflow was run
1040 * @param currentQuota the current quota for the origin.
1041 * @param estimatedSize the estimated size of the database.
1042 * @param totalUsedQuota is the sum of all origins' quota.
1043 * @param quotaUpdater The callback to run when a decision to allow or
1044 * deny quota has been made. Don't forget to call this!
1045 */
1046 @Override
1047 public void onExceededDatabaseQuota(String url,
1048 String databaseIdentifier, long currentQuota, long estimatedSize,
1049 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
John Reck35e9dd62011-04-25 09:01:54 -07001050 mSettings.getWebStorageSizeManager()
Grace Kloba22ac16e2009-10-07 18:00:23 -07001051 .onExceededDatabaseQuota(url, databaseIdentifier,
1052 currentQuota, estimatedSize, totalUsedQuota,
1053 quotaUpdater);
1054 }
1055
1056 /**
1057 * The Application Cache has exceeded its max size.
1058 * @param spaceNeeded is the amount of disk space that would be needed
1059 * in order for the last appcache operation to succeed.
1060 * @param totalUsedQuota is the sum of all origins' quota.
1061 * @param quotaUpdater A callback to inform the WebCore thread that a
1062 * new app cache size is available. This callback must always
1063 * be executed at some point to ensure that the sleeping
1064 * WebCore thread is woken up.
1065 */
1066 @Override
1067 public void onReachedMaxAppCacheSize(long spaceNeeded,
1068 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
John Reck35e9dd62011-04-25 09:01:54 -07001069 mSettings.getWebStorageSizeManager()
Grace Kloba22ac16e2009-10-07 18:00:23 -07001070 .onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
1071 quotaUpdater);
1072 }
1073
1074 /**
1075 * Instructs the browser to show a prompt to ask the user to set the
1076 * Geolocation permission state for the specified origin.
1077 * @param origin The origin for which Geolocation permissions are
1078 * requested.
1079 * @param callback The callback to call once the user has set the
1080 * Geolocation permission state.
1081 */
1082 @Override
1083 public void onGeolocationPermissionsShowPrompt(String origin,
1084 GeolocationPermissions.Callback callback) {
1085 if (mInForeground) {
Grace Kloba50c241e2010-04-20 11:07:50 -07001086 getGeolocationPermissionsPrompt().show(origin, callback);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001087 }
1088 }
1089
1090 /**
1091 * Instructs the browser to hide the Geolocation permissions prompt.
1092 */
1093 @Override
1094 public void onGeolocationPermissionsHidePrompt() {
Grace Kloba50c241e2010-04-20 11:07:50 -07001095 if (mInForeground && mGeolocationPermissionsPrompt != null) {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001096 mGeolocationPermissionsPrompt.hide();
1097 }
1098 }
1099
Ben Murdoch65acc352009-11-19 18:16:04 +00001100 /* Adds a JavaScript error message to the system log and if the JS
1101 * console is enabled in the about:debug options, to that console
1102 * also.
Ben Murdochc42addf2010-01-28 15:19:59 +00001103 * @param consoleMessage the message object.
Grace Kloba22ac16e2009-10-07 18:00:23 -07001104 */
1105 @Override
Ben Murdochc42addf2010-01-28 15:19:59 +00001106 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001107 if (mInForeground) {
1108 // call getErrorConsole(true) so it will create one if needed
1109 ErrorConsoleView errorConsole = getErrorConsole(true);
Ben Murdochc42addf2010-01-28 15:19:59 +00001110 errorConsole.addErrorMessage(consoleMessage);
Michael Kolb8233fac2010-10-26 16:08:53 -07001111 if (mWebViewController.shouldShowErrorConsole()
1112 && errorConsole.getShowState() !=
1113 ErrorConsoleView.SHOW_MAXIMIZED) {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001114 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1115 }
1116 }
Ben Murdochc42addf2010-01-28 15:19:59 +00001117
Jeff Hamilton47654f42010-09-07 09:57:51 -05001118 // Don't log console messages in private browsing mode
Rob Tsukf8bdfce2010-10-07 15:41:16 -07001119 if (isPrivateBrowsingEnabled()) return true;
Jeff Hamilton47654f42010-09-07 09:57:51 -05001120
Ben Murdochc42addf2010-01-28 15:19:59 +00001121 String message = "Console: " + consoleMessage.message() + " "
1122 + consoleMessage.sourceId() + ":"
1123 + consoleMessage.lineNumber();
1124
1125 switch (consoleMessage.messageLevel()) {
1126 case TIP:
1127 Log.v(CONSOLE_LOGTAG, message);
1128 break;
1129 case LOG:
1130 Log.i(CONSOLE_LOGTAG, message);
1131 break;
1132 case WARNING:
1133 Log.w(CONSOLE_LOGTAG, message);
1134 break;
1135 case ERROR:
1136 Log.e(CONSOLE_LOGTAG, message);
1137 break;
1138 case DEBUG:
1139 Log.d(CONSOLE_LOGTAG, message);
1140 break;
1141 }
1142
1143 return true;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001144 }
1145
1146 /**
1147 * Ask the browser for an icon to represent a <video> element.
1148 * This icon will be used if the Web page did not specify a poster attribute.
1149 * @return Bitmap The icon or null if no such icon is available.
1150 */
1151 @Override
1152 public Bitmap getDefaultVideoPoster() {
1153 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001154 return mWebViewController.getDefaultVideoPoster();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001155 }
1156 return null;
1157 }
1158
1159 /**
1160 * Ask the host application for a custom progress view to show while
1161 * a <video> is loading.
1162 * @return View The progress view.
1163 */
1164 @Override
1165 public View getVideoLoadingProgressView() {
1166 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001167 return mWebViewController.getVideoLoadingProgressView();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001168 }
1169 return null;
1170 }
1171
1172 @Override
Ben Murdoch62b1b7e2010-05-19 20:38:56 +01001173 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001174 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001175 mWebViewController.openFileChooser(uploadMsg, acceptType);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001176 } else {
1177 uploadMsg.onReceiveValue(null);
1178 }
1179 }
1180
1181 /**
1182 * Deliver a list of already-visited URLs
1183 */
1184 @Override
1185 public void getVisitedHistory(final ValueCallback<String[]> callback) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001186 mWebViewController.getVisitedHistory(callback);
1187 }
Ben Murdoch8029a772010-11-16 11:58:21 +00001188
1189 @Override
1190 public void setupAutoFill(Message message) {
1191 // Prompt the user to set up their profile.
1192 final Message msg = message;
1193 AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
Ben Murdoch1d676b62011-01-17 12:54:24 +00001194 LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
1195 Context.LAYOUT_INFLATER_SERVICE);
1196 final View layout = inflater.inflate(R.layout.setup_autofill_dialog, null);
1197
1198 builder.setView(layout)
1199 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
1200 @Override
1201 public void onClick(DialogInterface dialog, int id) {
1202 CheckBox disableAutoFill = (CheckBox) layout.findViewById(
1203 R.id.setup_autofill_dialog_disable_autofill);
1204
1205 if (disableAutoFill.isChecked()) {
1206 // Disable autofill and show a toast with how to turn it on again.
John Reck35e9dd62011-04-25 09:01:54 -07001207 mSettings.setAutofillEnabled(false);
Ben Murdoch1d676b62011-01-17 12:54:24 +00001208 Toast.makeText(mActivity,
1209 R.string.autofill_setup_dialog_negative_toast,
1210 Toast.LENGTH_LONG).show();
1211 } else {
1212 // Take user to the AutoFill profile editor. When they return,
1213 // we will send the message that we pass here which will trigger
1214 // the form to get filled out with their new profile.
1215 mWebViewController.setupAutoFill(msg);
1216 }
1217 }
1218 })
1219 .setNegativeButton(R.string.cancel, null)
1220 .show();
Ben Murdoch8029a772010-11-16 11:58:21 +00001221 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001222 };
1223
1224 // -------------------------------------------------------------------------
1225 // WebViewClient implementation for the sub window
1226 // -------------------------------------------------------------------------
1227
1228 // Subclass of WebViewClient used in subwindows to notify the main
1229 // WebViewClient of certain WebView activities.
1230 private static class SubWindowClient extends WebViewClient {
1231 // The main WebViewClient.
1232 private final WebViewClient mClient;
Michael Kolb8233fac2010-10-26 16:08:53 -07001233 private final WebViewController mController;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001234
Michael Kolb8233fac2010-10-26 16:08:53 -07001235 SubWindowClient(WebViewClient client, WebViewController controller) {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001236 mClient = client;
Michael Kolb8233fac2010-10-26 16:08:53 -07001237 mController = controller;
Leon Scroggins III211ba542010-04-19 13:21:13 -04001238 }
1239 @Override
1240 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1241 // Unlike the others, do not call mClient's version, which would
1242 // change the progress bar. However, we do want to remove the
Cary Clark01cfcdd2010-06-04 16:36:45 -04001243 // find or select dialog.
Michael Kolb8233fac2010-10-26 16:08:53 -07001244 mController.endActionMode();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001245 }
1246 @Override
1247 public void doUpdateVisitedHistory(WebView view, String url,
1248 boolean isReload) {
1249 mClient.doUpdateVisitedHistory(view, url, isReload);
1250 }
1251 @Override
1252 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1253 return mClient.shouldOverrideUrlLoading(view, url);
1254 }
1255 @Override
1256 public void onReceivedSslError(WebView view, SslErrorHandler handler,
1257 SslError error) {
1258 mClient.onReceivedSslError(view, handler, error);
1259 }
1260 @Override
Brian Carlstrom8862c1d2011-06-02 01:05:55 -07001261 public void onReceivedClientCertRequest(WebView view,
1262 ClientCertRequestHandler handler, String host_and_port) {
1263 mClient.onReceivedClientCertRequest(view, handler, host_and_port);
1264 }
1265 @Override
Grace Kloba22ac16e2009-10-07 18:00:23 -07001266 public void onReceivedHttpAuthRequest(WebView view,
1267 HttpAuthHandler handler, String host, String realm) {
1268 mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
1269 }
1270 @Override
1271 public void onFormResubmission(WebView view, Message dontResend,
1272 Message resend) {
1273 mClient.onFormResubmission(view, dontResend, resend);
1274 }
1275 @Override
1276 public void onReceivedError(WebView view, int errorCode,
1277 String description, String failingUrl) {
1278 mClient.onReceivedError(view, errorCode, description, failingUrl);
1279 }
1280 @Override
1281 public boolean shouldOverrideKeyEvent(WebView view,
1282 android.view.KeyEvent event) {
1283 return mClient.shouldOverrideKeyEvent(view, event);
1284 }
1285 @Override
1286 public void onUnhandledKeyEvent(WebView view,
1287 android.view.KeyEvent event) {
1288 mClient.onUnhandledKeyEvent(view, event);
1289 }
1290 }
1291
1292 // -------------------------------------------------------------------------
1293 // WebChromeClient implementation for the sub window
1294 // -------------------------------------------------------------------------
1295
1296 private class SubWindowChromeClient extends WebChromeClient {
1297 // The main WebChromeClient.
1298 private final WebChromeClient mClient;
1299
1300 SubWindowChromeClient(WebChromeClient client) {
1301 mClient = client;
1302 }
1303 @Override
1304 public void onProgressChanged(WebView view, int newProgress) {
1305 mClient.onProgressChanged(view, newProgress);
1306 }
1307 @Override
1308 public boolean onCreateWindow(WebView view, boolean dialog,
1309 boolean userGesture, android.os.Message resultMsg) {
1310 return mClient.onCreateWindow(view, dialog, userGesture, resultMsg);
1311 }
1312 @Override
1313 public void onCloseWindow(WebView window) {
1314 if (window != mSubView) {
1315 Log.e(LOGTAG, "Can't close the window");
1316 }
Michael Kolb8233fac2010-10-26 16:08:53 -07001317 mWebViewController.dismissSubWindow(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001318 }
1319 }
1320
1321 // -------------------------------------------------------------------------
1322
Michael Kolb8233fac2010-10-26 16:08:53 -07001323 // TODO temporarily use activity here
1324 // remove later
1325
Grace Kloba22ac16e2009-10-07 18:00:23 -07001326 // Construct a new tab
Michael Kolb7bcafde2011-05-09 13:55:59 -07001327 Tab(WebViewController wvcontroller, WebView w) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001328 mWebViewController = wvcontroller;
1329 mActivity = mWebViewController.getActivity();
John Reck35e9dd62011-04-25 09:01:54 -07001330 mSettings = BrowserSettings.getInstance();
John Recke969cc52010-12-21 17:24:43 -08001331 mDataController = DataController.getInstance(mActivity);
John Reck847b5322011-04-14 17:02:18 -07001332 mCurrentState = new PageState(mActivity, w != null
1333 ? w.isPrivateBrowsingEnabled() : false);
Michael Kolb8233fac2010-10-26 16:08:53 -07001334 mInPageLoad = false;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001335 mInForeground = false;
1336
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001337 mDownloadListener = new DownloadListener() {
1338 public void onDownloadStart(String url, String userAgent,
1339 String contentDisposition, String mimetype,
1340 long contentLength) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001341 mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition,
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001342 mimetype, contentLength);
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001343 }
1344 };
Leon Scroggins0c75a8e2010-03-03 16:40:58 -05001345 mWebBackForwardListClient = new WebBackForwardListClient() {
1346 @Override
1347 public void onNewHistoryItem(WebHistoryItem item) {
1348 if (isInVoiceSearchMode()) {
1349 item.setCustomData(mVoiceSearchData.mVoiceSearchIntent);
1350 }
1351 }
1352 @Override
1353 public void onIndexChanged(WebHistoryItem item, int index) {
1354 Object data = item.getCustomData();
1355 if (data != null && data instanceof Intent) {
1356 activateVoiceSearchMode((Intent) data);
1357 }
1358 }
1359 };
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001360
Grace Kloba22ac16e2009-10-07 18:00:23 -07001361 setWebView(w);
1362 }
1363
Michael Kolbc831b632011-05-11 09:30:34 -07001364 public void setId(long id) {
1365 mId = id;
1366 }
1367
1368 public long getId() {
1369 return mId;
1370 }
1371
Grace Kloba22ac16e2009-10-07 18:00:23 -07001372 /**
1373 * Sets the WebView for this tab, correctly removing the old WebView from
1374 * the container view.
1375 */
1376 void setWebView(WebView w) {
1377 if (mMainView == w) {
1378 return;
1379 }
Michael Kolba713ec82010-11-29 17:27:06 -08001380
Grace Kloba22ac16e2009-10-07 18:00:23 -07001381 // If the WebView is changing, the page will be reloaded, so any ongoing
1382 // Geolocation permission requests are void.
Grace Kloba50c241e2010-04-20 11:07:50 -07001383 if (mGeolocationPermissionsPrompt != null) {
1384 mGeolocationPermissionsPrompt.hide();
1385 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001386
Michael Kolba713ec82010-11-29 17:27:06 -08001387 mWebViewController.onSetWebView(this, w);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001388
1389 // set the new one
1390 mMainView = w;
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001391 // attach the WebViewClient, WebChromeClient and DownloadListener
Grace Kloba22ac16e2009-10-07 18:00:23 -07001392 if (mMainView != null) {
1393 mMainView.setWebViewClient(mWebViewClient);
1394 mMainView.setWebChromeClient(mWebChromeClient);
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001395 // Attach DownloadManager so that downloads can start in an active
1396 // or a non-active window. This can happen when going to a site that
1397 // does a redirect after a period of time. The user could have
1398 // switched to another tab while waiting for the download to start.
1399 mMainView.setDownloadListener(mDownloadListener);
Leon Scroggins0c75a8e2010-03-03 16:40:58 -05001400 mMainView.setWebBackForwardListClient(mWebBackForwardListClient);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001401 }
1402 }
1403
1404 /**
1405 * Destroy the tab's main WebView and subWindow if any
1406 */
1407 void destroy() {
1408 if (mMainView != null) {
1409 dismissSubWindow();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001410 // save the WebView to call destroy() after detach it from the tab
1411 WebView webView = mMainView;
1412 setWebView(null);
1413 webView.destroy();
1414 }
1415 }
1416
1417 /**
1418 * Remove the tab from the parent
1419 */
1420 void removeFromTree() {
1421 // detach the children
Michael Kolbc831b632011-05-11 09:30:34 -07001422 if (mChildren != null) {
1423 for(Tab t : mChildren) {
1424 t.setParent(null);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001425 }
1426 }
1427 // remove itself from the parent list
Michael Kolbc831b632011-05-11 09:30:34 -07001428 if (mParent != null) {
1429 mParent.mChildren.remove(this);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001430 }
1431 }
1432
1433 /**
1434 * Create a new subwindow unless a subwindow already exists.
1435 * @return True if a new subwindow was created. False if one already exists.
1436 */
1437 boolean createSubWindow() {
1438 if (mSubView == null) {
Michael Kolb1514bb72010-11-22 09:11:48 -08001439 mWebViewController.createSubWindow(this);
Leon Scroggins III211ba542010-04-19 13:21:13 -04001440 mSubView.setWebViewClient(new SubWindowClient(mWebViewClient,
Michael Kolb8233fac2010-10-26 16:08:53 -07001441 mWebViewController));
Grace Kloba22ac16e2009-10-07 18:00:23 -07001442 mSubView.setWebChromeClient(new SubWindowChromeClient(
1443 mWebChromeClient));
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001444 // Set a different DownloadListener for the mSubView, since it will
1445 // just need to dismiss the mSubView, rather than close the Tab
1446 mSubView.setDownloadListener(new DownloadListener() {
1447 public void onDownloadStart(String url, String userAgent,
1448 String contentDisposition, String mimetype,
1449 long contentLength) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001450 mWebViewController.onDownloadStart(Tab.this, url, userAgent,
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001451 contentDisposition, mimetype, contentLength);
1452 if (mSubView.copyBackForwardList().getSize() == 0) {
1453 // This subwindow was opened for the sole purpose of
1454 // downloading a file. Remove it.
Michael Kolb8233fac2010-10-26 16:08:53 -07001455 mWebViewController.dismissSubWindow(Tab.this);
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001456 }
1457 }
1458 });
Grace Kloba22ac16e2009-10-07 18:00:23 -07001459 mSubView.setOnCreateContextMenuListener(mActivity);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001460 return true;
1461 }
1462 return false;
1463 }
1464
1465 /**
1466 * Dismiss the subWindow for the tab.
1467 */
1468 void dismissSubWindow() {
1469 if (mSubView != null) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001470 mWebViewController.endActionMode();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001471 mSubView.destroy();
1472 mSubView = null;
1473 mSubViewContainer = null;
1474 }
1475 }
1476
Grace Kloba22ac16e2009-10-07 18:00:23 -07001477
1478 /**
1479 * Set the parent tab of this tab.
1480 */
Michael Kolbc831b632011-05-11 09:30:34 -07001481 void setParent(Tab parent) {
1482 mParent = parent;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001483 // This tab may have been freed due to low memory. If that is the case,
Michael Kolbc831b632011-05-11 09:30:34 -07001484 // the parent tab id is already saved. If we are changing that id
Grace Kloba22ac16e2009-10-07 18:00:23 -07001485 // (most likely due to removing the parent tab) we must update the
Michael Kolbc831b632011-05-11 09:30:34 -07001486 // parent tab id in the saved Bundle.
Grace Kloba22ac16e2009-10-07 18:00:23 -07001487 if (mSavedState != null) {
1488 if (parent == null) {
1489 mSavedState.remove(PARENTTAB);
1490 } else {
Michael Kolbc831b632011-05-11 09:30:34 -07001491 mSavedState.putLong(PARENTTAB, parent.getId());
Grace Kloba22ac16e2009-10-07 18:00:23 -07001492 }
1493 }
John Reckb0a86db2011-05-24 14:05:58 -07001494
1495 // Sync the WebView useragent with the parent
1496 if (parent != null && mSettings.hasDesktopUseragent(parent.getWebView())
1497 != mSettings.hasDesktopUseragent(getWebView())) {
1498 mSettings.toggleDesktopUseragent(getWebView());
1499 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001500 }
1501
1502 /**
Michael Kolbc831b632011-05-11 09:30:34 -07001503 * If this Tab was created through another Tab, then this method returns
1504 * that Tab.
1505 * @return the Tab parent or null
1506 */
1507 public Tab getParent() {
1508 return mParent;
1509 }
1510
1511 /**
Grace Kloba22ac16e2009-10-07 18:00:23 -07001512 * When a Tab is created through the content of another Tab, then we
1513 * associate the Tabs.
1514 * @param child the Tab that was created from this Tab
1515 */
1516 void addChildTab(Tab child) {
Michael Kolbc831b632011-05-11 09:30:34 -07001517 if (mChildren == null) {
1518 mChildren = new Vector<Tab>();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001519 }
Michael Kolbc831b632011-05-11 09:30:34 -07001520 mChildren.add(child);
1521 child.setParent(this);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001522 }
1523
Michael Kolbc831b632011-05-11 09:30:34 -07001524 Vector<Tab> getChildren() {
1525 return mChildren;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001526 }
1527
1528 void resume() {
1529 if (mMainView != null) {
1530 mMainView.onResume();
1531 if (mSubView != null) {
1532 mSubView.onResume();
1533 }
1534 }
1535 }
1536
1537 void pause() {
1538 if (mMainView != null) {
1539 mMainView.onPause();
1540 if (mSubView != null) {
1541 mSubView.onPause();
1542 }
1543 }
1544 }
1545
1546 void putInForeground() {
1547 mInForeground = true;
1548 resume();
1549 mMainView.setOnCreateContextMenuListener(mActivity);
1550 if (mSubView != null) {
1551 mSubView.setOnCreateContextMenuListener(mActivity);
1552 }
1553 // Show the pending error dialog if the queue is not empty
1554 if (mQueuedErrors != null && mQueuedErrors.size() > 0) {
1555 showError(mQueuedErrors.getFirst());
1556 }
Leon Scroggins1961ed22010-12-07 15:22:21 -05001557 mWebViewController.bookmarkedStatusHasChanged(this);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001558 }
1559
1560 void putInBackground() {
1561 mInForeground = false;
1562 pause();
1563 mMainView.setOnCreateContextMenuListener(null);
1564 if (mSubView != null) {
1565 mSubView.setOnCreateContextMenuListener(null);
1566 }
1567 }
1568
Michael Kolb8233fac2010-10-26 16:08:53 -07001569 boolean inForeground() {
1570 return mInForeground;
1571 }
1572
Grace Kloba22ac16e2009-10-07 18:00:23 -07001573 /**
1574 * Return the top window of this tab; either the subwindow if it is not
1575 * null or the main window.
1576 * @return The top window of this tab.
1577 */
1578 WebView getTopWindow() {
1579 if (mSubView != null) {
1580 return mSubView;
1581 }
1582 return mMainView;
1583 }
1584
1585 /**
1586 * Return the main window of this tab. Note: if a tab is freed in the
1587 * background, this can return null. It is only guaranteed to be
1588 * non-null for the current tab.
1589 * @return The main WebView of this tab.
1590 */
1591 WebView getWebView() {
1592 return mMainView;
1593 }
1594
Michael Kolba713ec82010-11-29 17:27:06 -08001595 void setViewContainer(View container) {
1596 mContainer = container;
1597 }
1598
Michael Kolb8233fac2010-10-26 16:08:53 -07001599 View getViewContainer() {
1600 return mContainer;
1601 }
1602
Grace Kloba22ac16e2009-10-07 18:00:23 -07001603 /**
Rob Tsukf8bdfce2010-10-07 15:41:16 -07001604 * Return whether private browsing is enabled for the main window of
1605 * this tab.
1606 * @return True if private browsing is enabled.
1607 */
Michael Kolb8233fac2010-10-26 16:08:53 -07001608 boolean isPrivateBrowsingEnabled() {
Rob Tsukf8bdfce2010-10-07 15:41:16 -07001609 WebView webView = getWebView();
1610 if (webView == null) {
1611 return false;
1612 }
1613 return webView.isPrivateBrowsingEnabled();
1614 }
1615
1616 /**
Grace Kloba22ac16e2009-10-07 18:00:23 -07001617 * Return the subwindow of this tab or null if there is no subwindow.
1618 * @return The subwindow of this tab or null.
1619 */
1620 WebView getSubWebView() {
1621 return mSubView;
1622 }
1623
Michael Kolb1514bb72010-11-22 09:11:48 -08001624 void setSubWebView(WebView subView) {
1625 mSubView = subView;
1626 }
1627
Michael Kolb8233fac2010-10-26 16:08:53 -07001628 View getSubViewContainer() {
1629 return mSubViewContainer;
1630 }
1631
Michael Kolb1514bb72010-11-22 09:11:48 -08001632 void setSubViewContainer(View subViewContainer) {
1633 mSubViewContainer = subViewContainer;
1634 }
1635
Grace Kloba22ac16e2009-10-07 18:00:23 -07001636 /**
1637 * @return The geolocation permissions prompt for this tab.
1638 */
1639 GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
Grace Kloba50c241e2010-04-20 11:07:50 -07001640 if (mGeolocationPermissionsPrompt == null) {
1641 ViewStub stub = (ViewStub) mContainer
1642 .findViewById(R.id.geolocation_permissions_prompt);
1643 mGeolocationPermissionsPrompt = (GeolocationPermissionsPrompt) stub
1644 .inflate();
1645 mGeolocationPermissionsPrompt.init();
1646 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001647 return mGeolocationPermissionsPrompt;
1648 }
1649
1650 /**
1651 * @return The application id string
1652 */
1653 String getAppId() {
1654 return mAppId;
1655 }
1656
1657 /**
1658 * Set the application id string
1659 * @param id
1660 */
1661 void setAppId(String id) {
1662 mAppId = id;
1663 }
1664
Grace Kloba22ac16e2009-10-07 18:00:23 -07001665 String getUrl() {
John Reck324d4402011-01-11 16:56:42 -08001666 return UrlUtils.filteredUrl(mCurrentState.mUrl);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001667 }
1668
John Reck49a603c2011-03-03 09:33:05 -08001669 String getOriginalUrl() {
1670 if (mMainView == null) {
1671 return "";
1672 }
1673 return UrlUtils.filteredUrl(mMainView.getOriginalUrl());
1674 }
1675
Grace Kloba22ac16e2009-10-07 18:00:23 -07001676 /**
John Reck30c714c2010-12-16 17:30:34 -08001677 * Get the title of this tab.
Grace Kloba22ac16e2009-10-07 18:00:23 -07001678 */
1679 String getTitle() {
John Reck30c714c2010-12-16 17:30:34 -08001680 if (mCurrentState.mTitle == null && mInPageLoad) {
1681 return mActivity.getString(R.string.title_bar_loading);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001682 }
John Reck30c714c2010-12-16 17:30:34 -08001683 return mCurrentState.mTitle;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001684 }
1685
1686 /**
John Reck30c714c2010-12-16 17:30:34 -08001687 * Get the favicon of this tab.
Grace Kloba22ac16e2009-10-07 18:00:23 -07001688 */
1689 Bitmap getFavicon() {
John Reck30c714c2010-12-16 17:30:34 -08001690 return mCurrentState.mFavicon;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001691 }
1692
John Recke969cc52010-12-21 17:24:43 -08001693 public boolean isBookmarkedSite() {
1694 return mCurrentState.mIsBookmarkedSite;
1695 }
Rob Tsukf8bdfce2010-10-07 15:41:16 -07001696
Grace Kloba22ac16e2009-10-07 18:00:23 -07001697 /**
1698 * Return the tab's error console. Creates the console if createIfNEcessary
1699 * is true and we haven't already created the console.
1700 * @param createIfNecessary Flag to indicate if the console should be
1701 * created if it has not been already.
1702 * @return The tab's error console, or null if one has not been created and
1703 * createIfNecessary is false.
1704 */
1705 ErrorConsoleView getErrorConsole(boolean createIfNecessary) {
1706 if (createIfNecessary && mErrorConsole == null) {
1707 mErrorConsole = new ErrorConsoleView(mActivity);
1708 mErrorConsole.setWebView(mMainView);
1709 }
1710 return mErrorConsole;
1711 }
1712
John Reck30c714c2010-12-16 17:30:34 -08001713 private void setLockIconType(LockIcon icon) {
1714 mCurrentState.mLockIcon = icon;
1715 mWebViewController.onUpdatedLockIcon(this);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001716 }
1717
1718 /**
1719 * @return The tab's lock icon type.
1720 */
John Reck30c714c2010-12-16 17:30:34 -08001721 LockIcon getLockIconType() {
1722 return mCurrentState.mLockIcon;
1723 }
1724
1725 int getLoadProgress() {
1726 if (mInPageLoad) {
1727 return mPageLoadProgress;
1728 }
1729 return 100;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001730 }
1731
1732 /**
1733 * @return TRUE if onPageStarted is called while onPageFinished is not
1734 * called yet.
1735 */
Michael Kolb8233fac2010-10-26 16:08:53 -07001736 boolean inPageLoad() {
1737 return mInPageLoad;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001738 }
1739
1740 // force mInLoad to be false. This should only be called before closing the
1741 // tab to ensure BrowserActivity's pauseWebViewTimers() is called correctly.
Michael Kolb8233fac2010-10-26 16:08:53 -07001742 void clearInPageLoad() {
1743 mInPageLoad = false;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001744 }
1745
Grace Kloba22ac16e2009-10-07 18:00:23 -07001746 /**
John Reck30c714c2010-12-16 17:30:34 -08001747 * Get the cached saved state bundle.
1748 * @return cached state bundle
Grace Kloba22ac16e2009-10-07 18:00:23 -07001749 */
1750 Bundle getSavedState() {
1751 return mSavedState;
1752 }
1753
John Reckaed9c542011-05-27 16:08:53 -07001754 Bundle getSavedState(boolean saveImages) {
1755 if (saveImages && mScreenshot != null) {
1756 Bundle b = new Bundle(mSavedState);
1757 b.putParcelable(SCREENSHOT, mScreenshot);
1758 return b;
1759 }
1760 return mSavedState;
1761 }
1762
Grace Kloba22ac16e2009-10-07 18:00:23 -07001763 /**
1764 * Set the saved state.
1765 */
1766 void setSavedState(Bundle state) {
1767 mSavedState = state;
1768 }
1769
1770 /**
1771 * @return TRUE if succeed in saving the state.
1772 */
1773 boolean saveState() {
1774 // If the WebView is null it means we ran low on memory and we already
1775 // stored the saved state in mSavedState.
1776 if (mMainView == null) {
1777 return mSavedState != null;
1778 }
1779
1780 mSavedState = new Bundle();
John Reck541f55a2011-06-07 16:34:43 -07001781 mMainView.saveState(mSavedState);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001782
Michael Kolbc831b632011-05-11 09:30:34 -07001783 mSavedState.putLong(ID, mId);
John Reck30c714c2010-12-16 17:30:34 -08001784 mSavedState.putString(CURRURL, mCurrentState.mUrl);
1785 mSavedState.putString(CURRTITLE, mCurrentState.mTitle);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001786 if (mAppId != null) {
1787 mSavedState.putString(APPID, mAppId);
1788 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001789 // Remember the parent tab so the relationship can be restored.
Michael Kolbc831b632011-05-11 09:30:34 -07001790 if (mParent != null) {
1791 mSavedState.putLong(PARENTTAB, mParent.mId);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001792 }
John Reckb0a86db2011-05-24 14:05:58 -07001793 mSavedState.putBoolean(USERAGENT,
1794 mSettings.hasDesktopUseragent(getWebView()));
Grace Kloba22ac16e2009-10-07 18:00:23 -07001795 return true;
1796 }
1797
1798 /*
1799 * Restore the state of the tab.
1800 */
1801 boolean restoreState(Bundle b) {
1802 if (b == null) {
1803 return false;
1804 }
1805 // Restore the internal state even if the WebView fails to restore.
1806 // This will maintain the app id, original url and close-on-exit values.
1807 mSavedState = null;
Michael Kolbc831b632011-05-11 09:30:34 -07001808 mId = b.getLong(ID);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001809 mAppId = b.getString(APPID);
Michael Kolbeb95db42011-03-03 10:38:40 -08001810 mScreenshot = b.getParcelable(SCREENSHOT);
John Reckb0a86db2011-05-24 14:05:58 -07001811 if (b.getBoolean(USERAGENT)
1812 != mSettings.hasDesktopUseragent(getWebView())) {
1813 mSettings.toggleDesktopUseragent(getWebView());
1814 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001815
1816 final WebBackForwardList list = mMainView.restoreState(b);
1817 if (list == null) {
1818 return false;
1819 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001820 return true;
1821 }
Leon Scroggins III211ba542010-04-19 13:21:13 -04001822
Leon Scroggins1961ed22010-12-07 15:22:21 -05001823 public void updateBookmarkedStatus() {
John Recke969cc52010-12-21 17:24:43 -08001824 mDataController.queryBookmarkStatus(getUrl(), mIsBookmarkCallback);
Leon Scroggins1961ed22010-12-07 15:22:21 -05001825 }
1826
John Recke969cc52010-12-21 17:24:43 -08001827 private DataController.OnQueryUrlIsBookmark mIsBookmarkCallback
1828 = new DataController.OnQueryUrlIsBookmark() {
1829 @Override
1830 public void onQueryUrlIsBookmark(String url, boolean isBookmark) {
1831 if (mCurrentState.mUrl.equals(url)) {
1832 mCurrentState.mIsBookmarkedSite = isBookmark;
1833 mWebViewController.bookmarkedStatusHasChanged(Tab.this);
1834 }
Leon Scroggins1961ed22010-12-07 15:22:21 -05001835 }
John Recke969cc52010-12-21 17:24:43 -08001836 };
Michael Kolb1acef692011-03-08 14:12:06 -08001837
Michael Kolbeb95db42011-03-03 10:38:40 -08001838 public void setScreenshot(Bitmap screenshot) {
1839 mScreenshot = screenshot;
1840 }
1841
1842 public Bitmap getScreenshot() {
1843 return mScreenshot;
1844 }
1845
John Reck541f55a2011-06-07 16:34:43 -07001846 public boolean isSnapshot() {
John Reck541f55a2011-06-07 16:34:43 -07001847 return false;
1848 }
1849
John Reckd8c74522011-06-14 08:45:00 -07001850 public ContentValues createSnapshotValues() {
1851 if (mMainView == null) return null;
1852 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1853 if (!mMainView.saveViewState(stream)) {
John Reck541f55a2011-06-07 16:34:43 -07001854 return null;
1855 }
John Reckd8c74522011-06-14 08:45:00 -07001856 byte[] data = stream.toByteArray();
1857 ContentResolver cr = mActivity.getContentResolver();
1858 ContentValues values = new ContentValues();
1859 values.put(Snapshots.TITLE, mCurrentState.mTitle);
1860 values.put(Snapshots.URL, mCurrentState.mUrl);
1861 values.put(Snapshots.VIEWSTATE, data);
1862 values.put(Snapshots.BACKGROUND, mMainView.getPageBackgroundColor());
1863 return values;
John Reck541f55a2011-06-07 16:34:43 -07001864 }
1865
Grace Kloba22ac16e2009-10-07 18:00:23 -07001866}