blob: ec425841db18e5bc68893cff04f206a41729a615 [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
Jeff Hamilton8ce956c2010-08-17 11:13:53 -050019import com.android.common.speech.LoggingEvents;
20
Michael Kolb8233fac2010-10-26 16:08:53 -070021import android.app.Activity;
Grace Kloba22ac16e2009-10-07 18:00:23 -070022import android.app.AlertDialog;
Leon Scroggins58d56c62010-01-28 15:12:40 -050023import android.app.SearchManager;
Grace Kloba22ac16e2009-10-07 18:00:23 -070024import android.content.ContentResolver;
Grace Kloba22ac16e2009-10-07 18:00:23 -070025import android.content.DialogInterface;
Michael Kolbfe251992010-07-08 15:41:55 -070026import android.content.DialogInterface.OnCancelListener;
Jeff Hamilton8ce956c2010-08-17 11:13:53 -050027import android.content.Intent;
Grace Kloba22ac16e2009-10-07 18:00:23 -070028import android.graphics.Bitmap;
29import android.net.Uri;
30import android.net.http.SslError;
Grace Kloba22ac16e2009-10-07 18:00:23 -070031import android.os.Bundle;
32import android.os.Message;
Kristian Monsen4dce3bf2010-02-02 13:37:09 +000033import android.os.SystemClock;
Leon Scrogginsa1cc3fd2010-02-01 16:14:11 -050034import android.speech.RecognizerResultsIntent;
Grace Kloba22ac16e2009-10-07 18:00:23 -070035import android.util.Log;
36import android.view.KeyEvent;
37import android.view.LayoutInflater;
38import android.view.View;
Jeff Hamilton8ce956c2010-08-17 11:13:53 -050039import android.view.View.OnClickListener;
Grace Kloba50c241e2010-04-20 11:07:50 -070040import android.view.ViewStub;
Ben Murdochc42addf2010-01-28 15:19:59 +000041import android.webkit.ConsoleMessage;
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -050042import android.webkit.DownloadListener;
Grace Kloba22ac16e2009-10-07 18:00:23 -070043import android.webkit.GeolocationPermissions;
44import android.webkit.HttpAuthHandler;
45import android.webkit.SslErrorHandler;
46import android.webkit.URLUtil;
47import android.webkit.ValueCallback;
48import android.webkit.WebBackForwardList;
Leon Scroggins0c75a8e2010-03-03 16:40:58 -050049import android.webkit.WebBackForwardListClient;
Grace Kloba22ac16e2009-10-07 18:00:23 -070050import android.webkit.WebChromeClient;
51import android.webkit.WebHistoryItem;
Grace Kloba22ac16e2009-10-07 18:00:23 -070052import android.webkit.WebStorage;
53import android.webkit.WebView;
54import android.webkit.WebViewClient;
55import android.widget.FrameLayout;
56import android.widget.ImageButton;
57import android.widget.LinearLayout;
58import android.widget.TextView;
59
Michael Kolbfe251992010-07-08 15:41:55 -070060import java.util.ArrayList;
61import java.util.HashMap;
62import java.util.Iterator;
63import java.util.LinkedList;
64import java.util.Map;
65import java.util.Vector;
66
Grace Kloba22ac16e2009-10-07 18:00:23 -070067/**
68 * Class for maintaining Tabs with a main WebView and a subwindow.
69 */
70class Tab {
Michael Kolb8233fac2010-10-26 16:08:53 -070071
Grace Kloba22ac16e2009-10-07 18:00:23 -070072 // Log Tag
73 private static final String LOGTAG = "Tab";
Ben Murdochc42addf2010-01-28 15:19:59 +000074 // Special case the logtag for messages for the Console to make it easier to
75 // filter them and match the logtag used for these messages in older versions
76 // of the browser.
77 private static final String CONSOLE_LOGTAG = "browser";
78
Michael Kolb8233fac2010-10-26 16:08:53 -070079 final static int LOCK_ICON_UNSECURE = 0;
80 final static int LOCK_ICON_SECURE = 1;
81 final static int LOCK_ICON_MIXED = 2;
82
83 Activity mActivity;
84 private WebViewController mWebViewController;
85
Grace Kloba22ac16e2009-10-07 18:00:23 -070086 // The Geolocation permissions prompt
87 private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
88 // Main WebView wrapper
Leon Scroggins III211ba542010-04-19 13:21:13 -040089 private LinearLayout mContainer;
Grace Kloba22ac16e2009-10-07 18:00:23 -070090 // Main WebView
91 private WebView mMainView;
92 // Subwindow container
93 private View mSubViewContainer;
94 // Subwindow WebView
95 private WebView mSubView;
96 // Saved bundle for when we are running low on memory. It contains the
97 // information needed to restore the WebView if the user goes back to the
98 // tab.
99 private Bundle mSavedState;
100 // Data used when displaying the tab in the picker.
101 private PickerData mPickerData;
102 // Parent Tab. This is the Tab that created this Tab, or null if the Tab was
103 // created by the UI
104 private Tab mParentTab;
105 // Tab that constructed by this Tab. This is used when this Tab is
106 // destroyed, it clears all mParentTab values in the children.
107 private Vector<Tab> mChildTabs;
108 // If true, the tab will be removed when back out of the first page.
109 private boolean mCloseOnExit;
110 // If true, the tab is in the foreground of the current activity.
111 private boolean mInForeground;
Michael Kolb8233fac2010-10-26 16:08:53 -0700112 // If true, the tab is in page loading state (after onPageStarted,
113 // before onPageFinsihed)
114 private boolean mInPageLoad;
Kristian Monsen4dce3bf2010-02-02 13:37:09 +0000115 // The time the load started, used to find load page time
116 private long mLoadStartTime;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700117 // Application identifier used to find tabs that another application wants
118 // to reuse.
119 private String mAppId;
120 // Keep the original url around to avoid killing the old WebView if the url
121 // has not changed.
122 private String mOriginalUrl;
Michael Kolb8233fac2010-10-26 16:08:53 -0700123 // Hold on to the currently loaded url
124 private String mCurrentUrl;
125 //The currently loaded title
126 private String mCurrentTitle;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700127 // Error console for the tab
128 private ErrorConsoleView mErrorConsole;
129 // the lock icon type and previous lock icon type for the tab
130 private int mLockIconType;
131 private int mPrevLockIconType;
132 // Inflation service for making subwindows.
133 private final LayoutInflater mInflateService;
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -0500134 // The listener that gets invoked when a download is started from the
135 // mMainView
136 private final DownloadListener mDownloadListener;
Leon Scroggins0c75a8e2010-03-03 16:40:58 -0500137 // Listener used to know when we move forward or back in the history list.
138 private final WebBackForwardListClient mWebBackForwardListClient;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700139
140 // AsyncTask for downloading touch icons
141 DownloadTouchIcon mTouchIconLoader;
142
143 // Extra saved information for displaying the tab in the picker.
144 private static class PickerData {
145 String mUrl;
146 String mTitle;
147 Bitmap mFavicon;
148 }
149
150 // Used for saving and restoring each Tab
151 static final String WEBVIEW = "webview";
152 static final String NUMTABS = "numTabs";
153 static final String CURRTAB = "currentTab";
154 static final String CURRURL = "currentUrl";
155 static final String CURRTITLE = "currentTitle";
Grace Kloba22ac16e2009-10-07 18:00:23 -0700156 static final String CLOSEONEXIT = "closeonexit";
157 static final String PARENTTAB = "parentTab";
158 static final String APPID = "appid";
159 static final String ORIGINALURL = "originalUrl";
Elliott Slaughter3d6df162010-08-25 13:17:44 -0700160 static final String INCOGNITO = "privateBrowsingEnabled";
Grace Kloba22ac16e2009-10-07 18:00:23 -0700161
162 // -------------------------------------------------------------------------
163
Leon Scroggins58d56c62010-01-28 15:12:40 -0500164 /**
165 * Private information regarding the latest voice search. If the Tab is not
166 * in voice search mode, this will be null.
167 */
168 private VoiceSearchData mVoiceSearchData;
169 /**
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400170 * Remove voice search mode from this tab.
171 */
172 public void revertVoiceSearchMode() {
173 if (mVoiceSearchData != null) {
174 mVoiceSearchData = null;
175 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700176 mWebViewController.revertVoiceSearchMode(this);
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400177 }
178 }
179 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700180
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400181 /**
Leon Scroggins58d56c62010-01-28 15:12:40 -0500182 * Return whether the tab is in voice search mode.
183 */
184 public boolean isInVoiceSearchMode() {
185 return mVoiceSearchData != null;
186 }
187 /**
Leon Scroggins IIIc1f5ae22010-06-29 17:11:29 -0400188 * Return true if the Tab is in voice search mode and the voice search
189 * Intent came with a String identifying that Google provided the Intent.
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500190 */
191 public boolean voiceSearchSourceIsGoogle() {
192 return mVoiceSearchData != null && mVoiceSearchData.mSourceIsGoogle;
193 }
194 /**
Leon Scroggins58d56c62010-01-28 15:12:40 -0500195 * Get the title to display for the current voice search page. If the Tab
196 * is not in voice search mode, return null.
197 */
198 public String getVoiceDisplayTitle() {
199 if (mVoiceSearchData == null) return null;
200 return mVoiceSearchData.mLastVoiceSearchTitle;
201 }
202 /**
203 * Get the latest array of voice search results, to be passed to the
204 * BrowserProvider. If the Tab is not in voice search mode, return null.
205 */
206 public ArrayList<String> getVoiceSearchResults() {
207 if (mVoiceSearchData == null) return null;
208 return mVoiceSearchData.mVoiceSearchResults;
209 }
210 /**
211 * Activate voice search mode.
212 * @param intent Intent which has the results to use, or an index into the
213 * results when reusing the old results.
214 */
215 /* package */ void activateVoiceSearchMode(Intent intent) {
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500216 int index = 0;
Leon Scroggins58d56c62010-01-28 15:12:40 -0500217 ArrayList<String> results = intent.getStringArrayListExtra(
Leon Scrogginsa1cc3fd2010-02-01 16:14:11 -0500218 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_STRINGS);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500219 if (results != null) {
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500220 ArrayList<String> urls = intent.getStringArrayListExtra(
221 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_URLS);
222 ArrayList<String> htmls = intent.getStringArrayListExtra(
223 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_HTML);
224 ArrayList<String> baseUrls = intent.getStringArrayListExtra(
225 RecognizerResultsIntent
226 .EXTRA_VOICE_SEARCH_RESULT_HTML_BASE_URLS);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500227 // This tab is now entering voice search mode for the first time, or
228 // a new voice search was done.
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500229 int size = results.size();
230 if (urls == null || size != urls.size()) {
Leon Scroggins58d56c62010-01-28 15:12:40 -0500231 throw new AssertionError("improper extras passed in Intent");
232 }
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500233 if (htmls == null || htmls.size() != size || baseUrls == null ||
234 (baseUrls.size() != size && baseUrls.size() != 1)) {
235 // If either of these arrays are empty/incorrectly sized, ignore
236 // them.
237 htmls = null;
238 baseUrls = null;
239 }
240 mVoiceSearchData = new VoiceSearchData(results, urls, htmls,
241 baseUrls);
Leon Scroggins9df94972010-03-08 18:20:35 -0500242 mVoiceSearchData.mHeaders = intent.getParcelableArrayListExtra(
243 RecognizerResultsIntent
244 .EXTRA_VOICE_SEARCH_RESULT_HTTP_HEADERS);
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500245 mVoiceSearchData.mSourceIsGoogle = intent.getBooleanExtra(
246 VoiceSearchData.SOURCE_IS_GOOGLE, false);
Leon Scroggins2ee4a5a2010-03-15 16:56:57 -0400247 mVoiceSearchData.mVoiceSearchIntent = new Intent(intent);
Leon Scrogginse10dde52010-03-08 19:53:03 -0500248 }
249 String extraData = intent.getStringExtra(
250 SearchManager.EXTRA_DATA_KEY);
251 if (extraData != null) {
252 index = Integer.parseInt(extraData);
253 if (index >= mVoiceSearchData.mVoiceSearchResults.size()) {
254 throw new AssertionError("index must be less than "
255 + "size of mVoiceSearchResults");
256 }
257 if (mVoiceSearchData.mSourceIsGoogle) {
258 Intent logIntent = new Intent(
259 LoggingEvents.ACTION_LOG_EVENT);
260 logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
261 LoggingEvents.VoiceSearch.N_BEST_CHOOSE);
262 logIntent.putExtra(
263 LoggingEvents.VoiceSearch.EXTRA_N_BEST_CHOOSE_INDEX,
264 index);
265 mActivity.sendBroadcast(logIntent);
266 }
267 if (mVoiceSearchData.mVoiceSearchIntent != null) {
Leon Scroggins2ee4a5a2010-03-15 16:56:57 -0400268 // Copy the Intent, so that each history item will have its own
269 // Intent, with different (or none) extra data.
270 Intent latest = new Intent(mVoiceSearchData.mVoiceSearchIntent);
271 latest.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
272 mVoiceSearchData.mVoiceSearchIntent = latest;
Leon Scroggins58d56c62010-01-28 15:12:40 -0500273 }
274 }
275 mVoiceSearchData.mLastVoiceSearchTitle
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500276 = mVoiceSearchData.mVoiceSearchResults.get(index);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500277 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700278 mWebViewController.activateVoiceSearchMode(mVoiceSearchData.mLastVoiceSearchTitle);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500279 }
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500280 if (mVoiceSearchData.mVoiceSearchHtmls != null) {
281 // When index was found it was already ensured that it was valid
282 String uriString = mVoiceSearchData.mVoiceSearchHtmls.get(index);
283 if (uriString != null) {
284 Uri dataUri = Uri.parse(uriString);
285 if (RecognizerResultsIntent.URI_SCHEME_INLINE.equals(
286 dataUri.getScheme())) {
287 // If there is only one base URL, use it. If there are
288 // more, there will be one for each index, so use the base
289 // URL corresponding to the index.
290 String baseUrl = mVoiceSearchData.mVoiceSearchBaseUrls.get(
291 mVoiceSearchData.mVoiceSearchBaseUrls.size() > 1 ?
292 index : 0);
293 mVoiceSearchData.mLastVoiceSearchUrl = baseUrl;
294 mMainView.loadDataWithBaseURL(baseUrl,
295 uriString.substring(RecognizerResultsIntent
296 .URI_SCHEME_INLINE.length() + 1), "text/html",
297 "utf-8", baseUrl);
298 return;
299 }
300 }
301 }
Leon Scroggins58d56c62010-01-28 15:12:40 -0500302 mVoiceSearchData.mLastVoiceSearchUrl
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500303 = mVoiceSearchData.mVoiceSearchUrls.get(index);
304 if (null == mVoiceSearchData.mLastVoiceSearchUrl) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700305 mVoiceSearchData.mLastVoiceSearchUrl = UrlUtils.smartUrlFilter(
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500306 mVoiceSearchData.mLastVoiceSearchTitle);
307 }
Leon Scroggins9df94972010-03-08 18:20:35 -0500308 Map<String, String> headers = null;
309 if (mVoiceSearchData.mHeaders != null) {
310 int bundleIndex = mVoiceSearchData.mHeaders.size() == 1 ? 0
311 : index;
312 Bundle bundle = mVoiceSearchData.mHeaders.get(bundleIndex);
313 if (bundle != null && !bundle.isEmpty()) {
314 Iterator<String> iter = bundle.keySet().iterator();
315 headers = new HashMap<String, String>();
316 while (iter.hasNext()) {
317 String key = iter.next();
318 headers.put(key, bundle.getString(key));
319 }
320 }
321 }
322 mMainView.loadUrl(mVoiceSearchData.mLastVoiceSearchUrl, headers);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500323 }
324 /* package */ static class VoiceSearchData {
Leon Scroggins58d56c62010-01-28 15:12:40 -0500325 public VoiceSearchData(ArrayList<String> results,
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500326 ArrayList<String> urls, ArrayList<String> htmls,
327 ArrayList<String> baseUrls) {
Leon Scroggins58d56c62010-01-28 15:12:40 -0500328 mVoiceSearchResults = results;
329 mVoiceSearchUrls = urls;
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500330 mVoiceSearchHtmls = htmls;
331 mVoiceSearchBaseUrls = baseUrls;
Leon Scroggins58d56c62010-01-28 15:12:40 -0500332 }
333 /*
334 * ArrayList of suggestions to be displayed when opening the
335 * SearchManager
336 */
337 public ArrayList<String> mVoiceSearchResults;
338 /*
339 * ArrayList of urls, associated with the suggestions in
340 * mVoiceSearchResults.
341 */
342 public ArrayList<String> mVoiceSearchUrls;
343 /*
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500344 * ArrayList holding content to load for each item in
345 * mVoiceSearchResults.
346 */
347 public ArrayList<String> mVoiceSearchHtmls;
348 /*
349 * ArrayList holding base urls for the items in mVoiceSearchResults.
350 * If non null, this will either have the same size as
351 * mVoiceSearchResults or have a size of 1, in which case all will use
352 * the same base url
353 */
354 public ArrayList<String> mVoiceSearchBaseUrls;
355 /*
Leon Scroggins58d56c62010-01-28 15:12:40 -0500356 * The last url provided by voice search. Used for comparison to see if
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500357 * we are going to a page by some method besides voice search.
Leon Scroggins58d56c62010-01-28 15:12:40 -0500358 */
359 public String mLastVoiceSearchUrl;
360 /**
361 * The last title used for voice search. Needed to update the title bar
362 * when switching tabs.
363 */
364 public String mLastVoiceSearchTitle;
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500365 /**
366 * Whether the Intent which turned on voice search mode contained the
367 * String signifying that Google was the source.
368 */
369 public boolean mSourceIsGoogle;
370 /**
Leon Scroggins9df94972010-03-08 18:20:35 -0500371 * List of headers to be passed into the WebView containing location
372 * information
373 */
374 public ArrayList<Bundle> mHeaders;
375 /**
Leon Scroggins0c75a8e2010-03-03 16:40:58 -0500376 * The Intent used to invoke voice search. Placed on the
377 * WebHistoryItem so that when coming back to a previous voice search
378 * page we can again activate voice search.
379 */
Leon Scrogginse10dde52010-03-08 19:53:03 -0500380 public Intent mVoiceSearchIntent;
Leon Scroggins0c75a8e2010-03-03 16:40:58 -0500381 /**
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500382 * String used to identify Google as the source of voice search.
383 */
384 public static String SOURCE_IS_GOOGLE
385 = "android.speech.extras.SOURCE_IS_GOOGLE";
Leon Scroggins58d56c62010-01-28 15:12:40 -0500386 }
387
Grace Kloba22ac16e2009-10-07 18:00:23 -0700388 // Container class for the next error dialog that needs to be displayed
389 private class ErrorDialog {
390 public final int mTitle;
391 public final String mDescription;
392 public final int mError;
393 ErrorDialog(int title, String desc, int error) {
394 mTitle = title;
395 mDescription = desc;
396 mError = error;
397 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700398 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700399
400 private void processNextError() {
401 if (mQueuedErrors == null) {
402 return;
403 }
404 // The first one is currently displayed so just remove it.
405 mQueuedErrors.removeFirst();
406 if (mQueuedErrors.size() == 0) {
407 mQueuedErrors = null;
408 return;
409 }
410 showError(mQueuedErrors.getFirst());
411 }
412
413 private DialogInterface.OnDismissListener mDialogListener =
414 new DialogInterface.OnDismissListener() {
415 public void onDismiss(DialogInterface d) {
416 processNextError();
417 }
418 };
419 private LinkedList<ErrorDialog> mQueuedErrors;
420
421 private void queueError(int err, String desc) {
422 if (mQueuedErrors == null) {
423 mQueuedErrors = new LinkedList<ErrorDialog>();
424 }
425 for (ErrorDialog d : mQueuedErrors) {
426 if (d.mError == err) {
427 // Already saw a similar error, ignore the new one.
428 return;
429 }
430 }
431 ErrorDialog errDialog = new ErrorDialog(
432 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
433 R.string.browserFrameFileErrorLabel :
434 R.string.browserFrameNetworkErrorLabel,
435 desc, err);
436 mQueuedErrors.addLast(errDialog);
437
438 // Show the dialog now if the queue was empty and it is in foreground
439 if (mQueuedErrors.size() == 1 && mInForeground) {
440 showError(errDialog);
441 }
442 }
443
444 private void showError(ErrorDialog errDialog) {
445 if (mInForeground) {
446 AlertDialog d = new AlertDialog.Builder(mActivity)
447 .setTitle(errDialog.mTitle)
448 .setMessage(errDialog.mDescription)
449 .setPositiveButton(R.string.ok, null)
450 .create();
451 d.setOnDismissListener(mDialogListener);
452 d.show();
453 }
454 }
455
456 // -------------------------------------------------------------------------
457 // WebViewClient implementation for the main WebView
458 // -------------------------------------------------------------------------
459
460 private final WebViewClient mWebViewClient = new WebViewClient() {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500461 private Message mDontResend;
462 private Message mResend;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700463 @Override
464 public void onPageStarted(WebView view, String url, Bitmap favicon) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700465 mInPageLoad = true;
Kristian Monsen4dce3bf2010-02-02 13:37:09 +0000466 mLoadStartTime = SystemClock.uptimeMillis();
Leon Scroggins58d56c62010-01-28 15:12:40 -0500467 if (mVoiceSearchData != null
468 && !url.equals(mVoiceSearchData.mLastVoiceSearchUrl)) {
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500469 if (mVoiceSearchData.mSourceIsGoogle) {
470 Intent i = new Intent(LoggingEvents.ACTION_LOG_EVENT);
471 i.putExtra(LoggingEvents.EXTRA_FLUSH, true);
472 mActivity.sendBroadcast(i);
473 }
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400474 revertVoiceSearchMode();
Leon Scroggins58d56c62010-01-28 15:12:40 -0500475 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700476
Grace Kloba22ac16e2009-10-07 18:00:23 -0700477
478 // If we start a touch icon load and then load a new page, we don't
479 // want to cancel the current touch icon loader. But, we do want to
480 // create a new one when the touch icon url is known.
481 if (mTouchIconLoader != null) {
482 mTouchIconLoader.mTab = null;
483 mTouchIconLoader = null;
484 }
485
486 // reset the error console
487 if (mErrorConsole != null) {
488 mErrorConsole.clearErrorMessages();
Michael Kolb8233fac2010-10-26 16:08:53 -0700489 if (mWebViewController.shouldShowErrorConsole()) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700490 mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
491 }
492 }
493
Grace Kloba22ac16e2009-10-07 18:00:23 -0700494
495 // finally update the UI in the activity if it is in the foreground
Michael Kolb8233fac2010-10-26 16:08:53 -0700496 mWebViewController.onPageStarted(Tab.this, view, url, favicon);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700497 }
498
499 @Override
500 public void onPageFinished(WebView view, String url) {
Kristian Monsen4dce3bf2010-02-02 13:37:09 +0000501 LogTag.logPageFinishedLoading(
502 url, SystemClock.uptimeMillis() - mLoadStartTime);
Michael Kolb8233fac2010-10-26 16:08:53 -0700503 mInPageLoad = false;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700504
Michael Kolb8233fac2010-10-26 16:08:53 -0700505 mWebViewController.onPageFinished(Tab.this, url);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700506 }
507
508 // return true if want to hijack the url to let another app to handle it
509 @Override
510 public boolean shouldOverrideUrlLoading(WebView view, String url) {
Leon Scroggins IIIc1f5ae22010-06-29 17:11:29 -0400511 if (voiceSearchSourceIsGoogle()) {
512 // This method is called when the user clicks on a link.
513 // VoiceSearchMode is turned off when the user leaves the
514 // Google results page, so at this point the user must be on
515 // that page. If the user clicked a link on that page, assume
516 // that the voice search was effective, and broadcast an Intent
517 // so a receiver can take note of that fact.
518 Intent logIntent = new Intent(LoggingEvents.ACTION_LOG_EVENT);
519 logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
520 LoggingEvents.VoiceSearch.RESULT_CLICKED);
521 mActivity.sendBroadcast(logIntent);
522 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700523 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700524 return mWebViewController.shouldOverrideUrlLoading(view, url);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700525 } else {
526 return false;
527 }
528 }
529
530 /**
531 * Updates the lock icon. This method is called when we discover another
532 * resource to be loaded for this page (for example, javascript). While
533 * we update the icon type, we do not update the lock icon itself until
534 * we are done loading, it is slightly more secure this way.
535 */
536 @Override
537 public void onLoadResource(WebView view, String url) {
538 if (url != null && url.length() > 0) {
539 // It is only if the page claims to be secure that we may have
540 // to update the lock:
Michael Kolb8233fac2010-10-26 16:08:53 -0700541 if (mLockIconType == LOCK_ICON_SECURE) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700542 // If NOT a 'safe' url, change the lock to mixed content!
543 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url)
544 || URLUtil.isAboutUrl(url))) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700545 mLockIconType = LOCK_ICON_MIXED;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700546 }
547 }
548 }
549 }
550
551 /**
Grace Kloba22ac16e2009-10-07 18:00:23 -0700552 * Show a dialog informing the user of the network error reported by
553 * WebCore if it is in the foreground.
554 */
555 @Override
556 public void onReceivedError(WebView view, int errorCode,
557 String description, String failingUrl) {
558 if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
559 errorCode != WebViewClient.ERROR_CONNECT &&
560 errorCode != WebViewClient.ERROR_BAD_URL &&
561 errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
562 errorCode != WebViewClient.ERROR_FILE) {
563 queueError(errorCode, description);
564 }
Jeff Hamilton47654f42010-09-07 09:57:51 -0500565
566 // Don't log URLs when in private browsing mode
Rob Tsukf8bdfce2010-10-07 15:41:16 -0700567 if (!isPrivateBrowsingEnabled()) {
Jeff Hamilton47654f42010-09-07 09:57:51 -0500568 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
569 + " " + description);
570 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700571
572 // We need to reset the title after an error if it is in foreground.
573 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700574 mWebViewController.resetTitleAndRevertLockIcon(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700575 }
576 }
577
578 /**
579 * Check with the user if it is ok to resend POST data as the page they
580 * are trying to navigate to is the result of a POST.
581 */
582 @Override
583 public void onFormResubmission(WebView view, final Message dontResend,
584 final Message resend) {
585 if (!mInForeground) {
586 dontResend.sendToTarget();
587 return;
588 }
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500589 if (mDontResend != null) {
590 Log.w(LOGTAG, "onFormResubmission should not be called again "
591 + "while dialog is still up");
592 dontResend.sendToTarget();
593 return;
594 }
595 mDontResend = dontResend;
596 mResend = resend;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700597 new AlertDialog.Builder(mActivity).setTitle(
598 R.string.browserFrameFormResubmitLabel).setMessage(
599 R.string.browserFrameFormResubmitMessage)
600 .setPositiveButton(R.string.ok,
601 new DialogInterface.OnClickListener() {
602 public void onClick(DialogInterface dialog,
603 int which) {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500604 if (mResend != null) {
605 mResend.sendToTarget();
606 mResend = null;
607 mDontResend = null;
608 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700609 }
610 }).setNegativeButton(R.string.cancel,
611 new DialogInterface.OnClickListener() {
612 public void onClick(DialogInterface dialog,
613 int which) {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500614 if (mDontResend != null) {
615 mDontResend.sendToTarget();
616 mResend = null;
617 mDontResend = null;
618 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700619 }
620 }).setOnCancelListener(new OnCancelListener() {
621 public void onCancel(DialogInterface dialog) {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500622 if (mDontResend != null) {
623 mDontResend.sendToTarget();
624 mResend = null;
625 mDontResend = null;
626 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700627 }
628 }).show();
629 }
630
631 /**
632 * Insert the url into the visited history database.
633 * @param url The url to be inserted.
634 * @param isReload True if this url is being reloaded.
635 * FIXME: Not sure what to do when reloading the page.
636 */
637 @Override
638 public void doUpdateVisitedHistory(WebView view, String url,
639 boolean isReload) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700640 mWebViewController.doUpdateVisitedHistory(Tab.this, url, isReload);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700641 }
642
643 /**
644 * Displays SSL error(s) dialog to the user.
645 */
646 @Override
647 public void onReceivedSslError(final WebView view,
648 final SslErrorHandler handler, final SslError error) {
649 if (!mInForeground) {
650 handler.cancel();
651 return;
652 }
653 if (BrowserSettings.getInstance().showSecurityWarnings()) {
654 final LayoutInflater factory =
655 LayoutInflater.from(mActivity);
656 final View warningsView =
657 factory.inflate(R.layout.ssl_warnings, null);
658 final LinearLayout placeholder =
659 (LinearLayout)warningsView.findViewById(R.id.placeholder);
660
661 if (error.hasError(SslError.SSL_UNTRUSTED)) {
662 LinearLayout ll = (LinearLayout)factory
663 .inflate(R.layout.ssl_warning, null);
664 ((TextView)ll.findViewById(R.id.warning))
665 .setText(R.string.ssl_untrusted);
666 placeholder.addView(ll);
667 }
668
669 if (error.hasError(SslError.SSL_IDMISMATCH)) {
670 LinearLayout ll = (LinearLayout)factory
671 .inflate(R.layout.ssl_warning, null);
672 ((TextView)ll.findViewById(R.id.warning))
673 .setText(R.string.ssl_mismatch);
674 placeholder.addView(ll);
675 }
676
677 if (error.hasError(SslError.SSL_EXPIRED)) {
678 LinearLayout ll = (LinearLayout)factory
679 .inflate(R.layout.ssl_warning, null);
680 ((TextView)ll.findViewById(R.id.warning))
681 .setText(R.string.ssl_expired);
682 placeholder.addView(ll);
683 }
684
685 if (error.hasError(SslError.SSL_NOTYETVALID)) {
686 LinearLayout ll = (LinearLayout)factory
687 .inflate(R.layout.ssl_warning, null);
688 ((TextView)ll.findViewById(R.id.warning))
689 .setText(R.string.ssl_not_yet_valid);
690 placeholder.addView(ll);
691 }
692
693 new AlertDialog.Builder(mActivity).setTitle(
694 R.string.security_warning).setIcon(
695 android.R.drawable.ic_dialog_alert).setView(
696 warningsView).setPositiveButton(R.string.ssl_continue,
697 new DialogInterface.OnClickListener() {
698 public void onClick(DialogInterface dialog,
699 int whichButton) {
700 handler.proceed();
701 }
702 }).setNeutralButton(R.string.view_certificate,
703 new DialogInterface.OnClickListener() {
704 public void onClick(DialogInterface dialog,
705 int whichButton) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700706 mWebViewController.showSslCertificateOnError(view,
Grace Kloba22ac16e2009-10-07 18:00:23 -0700707 handler, error);
708 }
709 }).setNegativeButton(R.string.cancel,
710 new DialogInterface.OnClickListener() {
711 public void onClick(DialogInterface dialog,
712 int whichButton) {
713 handler.cancel();
Michael Kolb8233fac2010-10-26 16:08:53 -0700714 mWebViewController.resetTitleAndRevertLockIcon(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700715 }
716 }).setOnCancelListener(
717 new DialogInterface.OnCancelListener() {
718 public void onCancel(DialogInterface dialog) {
719 handler.cancel();
Michael Kolb8233fac2010-10-26 16:08:53 -0700720 mWebViewController.resetTitleAndRevertLockIcon(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700721 }
722 }).show();
723 } else {
724 handler.proceed();
725 }
726 }
727
728 /**
729 * Handles an HTTP authentication request.
730 *
731 * @param handler The authentication handler
732 * @param host The host
733 * @param realm The realm
734 */
735 @Override
736 public void onReceivedHttpAuthRequest(WebView view,
737 final HttpAuthHandler handler, final String host,
738 final String realm) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700739 mWebViewController.onReceivedHttpAuthRequest(Tab.this, view, handler, host, realm);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700740 }
741
742 @Override
743 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
744 if (!mInForeground) {
745 return false;
746 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700747 return mWebViewController.shouldOverrideKeyEvent(event);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700748 }
749
750 @Override
751 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700752 if (!mInForeground) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700753 return;
754 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700755 mWebViewController.onUnhandledKeyEvent(event);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700756 }
757 };
758
759 // -------------------------------------------------------------------------
760 // WebChromeClient implementation for the main WebView
761 // -------------------------------------------------------------------------
762
763 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
764 // Helper method to create a new tab or sub window.
765 private void createWindow(final boolean dialog, final Message msg) {
766 WebView.WebViewTransport transport =
767 (WebView.WebViewTransport) msg.obj;
768 if (dialog) {
769 createSubWindow();
Michael Kolb8233fac2010-10-26 16:08:53 -0700770 mWebViewController.attachSubWindow(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700771 transport.setWebView(mSubView);
772 } else {
Michael Kolb8233fac2010-10-26 16:08:53 -0700773 final Tab newTab = mWebViewController.openTabAndShow(
774 IntentHandler.EMPTY_URL_DATA, false, null);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700775 if (newTab != Tab.this) {
776 Tab.this.addChildTab(newTab);
777 }
778 transport.setWebView(newTab.getWebView());
779 }
780 msg.sendToTarget();
781 }
782
783 @Override
784 public boolean onCreateWindow(WebView view, final boolean dialog,
785 final boolean userGesture, final Message resultMsg) {
786 // only allow new window or sub window for the foreground case
787 if (!mInForeground) {
788 return false;
789 }
790 // Short-circuit if we can't create any more tabs or sub windows.
791 if (dialog && mSubView != null) {
792 new AlertDialog.Builder(mActivity)
793 .setTitle(R.string.too_many_subwindows_dialog_title)
794 .setIcon(android.R.drawable.ic_dialog_alert)
795 .setMessage(R.string.too_many_subwindows_dialog_message)
796 .setPositiveButton(R.string.ok, null)
797 .show();
798 return false;
Michael Kolb8233fac2010-10-26 16:08:53 -0700799 } else if (!mWebViewController.getTabControl().canCreateNewTab()) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700800 new AlertDialog.Builder(mActivity)
801 .setTitle(R.string.too_many_windows_dialog_title)
802 .setIcon(android.R.drawable.ic_dialog_alert)
803 .setMessage(R.string.too_many_windows_dialog_message)
804 .setPositiveButton(R.string.ok, null)
805 .show();
806 return false;
807 }
808
809 // Short-circuit if this was a user gesture.
810 if (userGesture) {
811 createWindow(dialog, resultMsg);
812 return true;
813 }
814
815 // Allow the popup and create the appropriate window.
816 final AlertDialog.OnClickListener allowListener =
817 new AlertDialog.OnClickListener() {
818 public void onClick(DialogInterface d,
819 int which) {
820 createWindow(dialog, resultMsg);
821 }
822 };
823
824 // Block the popup by returning a null WebView.
825 final AlertDialog.OnClickListener blockListener =
826 new AlertDialog.OnClickListener() {
827 public void onClick(DialogInterface d, int which) {
828 resultMsg.sendToTarget();
829 }
830 };
831
832 // Build a confirmation dialog to display to the user.
833 final AlertDialog d =
834 new AlertDialog.Builder(mActivity)
835 .setTitle(R.string.attention)
836 .setIcon(android.R.drawable.ic_dialog_alert)
837 .setMessage(R.string.popup_window_attempt)
838 .setPositiveButton(R.string.allow, allowListener)
839 .setNegativeButton(R.string.block, blockListener)
840 .setCancelable(false)
841 .create();
842
843 // Show the confirmation dialog.
844 d.show();
845 return true;
846 }
847
848 @Override
Patrick Scotteb5061b2009-11-18 15:00:30 -0500849 public void onRequestFocus(WebView view) {
850 if (!mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700851 mWebViewController.switchToTab(mWebViewController.getTabControl().getTabIndex(
Patrick Scotteb5061b2009-11-18 15:00:30 -0500852 Tab.this));
853 }
854 }
855
856 @Override
Grace Kloba22ac16e2009-10-07 18:00:23 -0700857 public void onCloseWindow(WebView window) {
858 if (mParentTab != null) {
859 // JavaScript can only close popup window.
860 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700861 mWebViewController.switchToTab(mWebViewController.getTabControl()
Grace Kloba22ac16e2009-10-07 18:00:23 -0700862 .getTabIndex(mParentTab));
863 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700864 mWebViewController.closeTab(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700865 }
866 }
867
868 @Override
869 public void onProgressChanged(WebView view, int newProgress) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700870 mWebViewController.onProgressChanged(Tab.this, newProgress);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700871 }
872
873 @Override
Leon Scroggins21d9b902010-03-11 09:33:11 -0500874 public void onReceivedTitle(WebView view, final String title) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700875 mWebViewController.onReceivedTitle(Tab.this, title);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700876 }
877
878 @Override
879 public void onReceivedIcon(WebView view, Bitmap icon) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700880 mWebViewController.onFavicon(Tab.this, view, icon);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700881 }
882
883 @Override
884 public void onReceivedTouchIconUrl(WebView view, String url,
885 boolean precomposed) {
886 final ContentResolver cr = mActivity.getContentResolver();
Leon Scrogginsc8393d92010-04-23 14:58:16 -0400887 // Let precomposed icons take precedence over non-composed
888 // icons.
889 if (precomposed && mTouchIconLoader != null) {
890 mTouchIconLoader.cancel(false);
891 mTouchIconLoader = null;
892 }
893 // Have only one async task at a time.
894 if (mTouchIconLoader == null) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700895 mTouchIconLoader = new DownloadTouchIcon(Tab.this,
896 mActivity, cr, view);
Leon Scrogginsc8393d92010-04-23 14:58:16 -0400897 mTouchIconLoader.execute(url);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700898 }
899 }
900
901 @Override
902 public void onShowCustomView(View view,
903 WebChromeClient.CustomViewCallback callback) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700904 if (mInForeground) mWebViewController.showCustomView(Tab.this, view,
905 callback);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700906 }
907
908 @Override
909 public void onHideCustomView() {
Michael Kolb8233fac2010-10-26 16:08:53 -0700910 if (mInForeground) mWebViewController.hideCustomView();
Grace Kloba22ac16e2009-10-07 18:00:23 -0700911 }
912
913 /**
914 * The origin has exceeded its database quota.
915 * @param url the URL that exceeded the quota
916 * @param databaseIdentifier the identifier of the database on which the
917 * transaction that caused the quota overflow was run
918 * @param currentQuota the current quota for the origin.
919 * @param estimatedSize the estimated size of the database.
920 * @param totalUsedQuota is the sum of all origins' quota.
921 * @param quotaUpdater The callback to run when a decision to allow or
922 * deny quota has been made. Don't forget to call this!
923 */
924 @Override
925 public void onExceededDatabaseQuota(String url,
926 String databaseIdentifier, long currentQuota, long estimatedSize,
927 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
928 BrowserSettings.getInstance().getWebStorageSizeManager()
929 .onExceededDatabaseQuota(url, databaseIdentifier,
930 currentQuota, estimatedSize, totalUsedQuota,
931 quotaUpdater);
932 }
933
934 /**
935 * The Application Cache has exceeded its max size.
936 * @param spaceNeeded is the amount of disk space that would be needed
937 * in order for the last appcache operation to succeed.
938 * @param totalUsedQuota is the sum of all origins' quota.
939 * @param quotaUpdater A callback to inform the WebCore thread that a
940 * new app cache size is available. This callback must always
941 * be executed at some point to ensure that the sleeping
942 * WebCore thread is woken up.
943 */
944 @Override
945 public void onReachedMaxAppCacheSize(long spaceNeeded,
946 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
947 BrowserSettings.getInstance().getWebStorageSizeManager()
948 .onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
949 quotaUpdater);
950 }
951
952 /**
953 * Instructs the browser to show a prompt to ask the user to set the
954 * Geolocation permission state for the specified origin.
955 * @param origin The origin for which Geolocation permissions are
956 * requested.
957 * @param callback The callback to call once the user has set the
958 * Geolocation permission state.
959 */
960 @Override
961 public void onGeolocationPermissionsShowPrompt(String origin,
962 GeolocationPermissions.Callback callback) {
963 if (mInForeground) {
Grace Kloba50c241e2010-04-20 11:07:50 -0700964 getGeolocationPermissionsPrompt().show(origin, callback);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700965 }
966 }
967
968 /**
969 * Instructs the browser to hide the Geolocation permissions prompt.
970 */
971 @Override
972 public void onGeolocationPermissionsHidePrompt() {
Grace Kloba50c241e2010-04-20 11:07:50 -0700973 if (mInForeground && mGeolocationPermissionsPrompt != null) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700974 mGeolocationPermissionsPrompt.hide();
975 }
976 }
977
Ben Murdoch65acc352009-11-19 18:16:04 +0000978 /* Adds a JavaScript error message to the system log and if the JS
979 * console is enabled in the about:debug options, to that console
980 * also.
Ben Murdochc42addf2010-01-28 15:19:59 +0000981 * @param consoleMessage the message object.
Grace Kloba22ac16e2009-10-07 18:00:23 -0700982 */
983 @Override
Ben Murdochc42addf2010-01-28 15:19:59 +0000984 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700985 if (mInForeground) {
986 // call getErrorConsole(true) so it will create one if needed
987 ErrorConsoleView errorConsole = getErrorConsole(true);
Ben Murdochc42addf2010-01-28 15:19:59 +0000988 errorConsole.addErrorMessage(consoleMessage);
Michael Kolb8233fac2010-10-26 16:08:53 -0700989 if (mWebViewController.shouldShowErrorConsole()
990 && errorConsole.getShowState() !=
991 ErrorConsoleView.SHOW_MAXIMIZED) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700992 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
993 }
994 }
Ben Murdochc42addf2010-01-28 15:19:59 +0000995
Jeff Hamilton47654f42010-09-07 09:57:51 -0500996 // Don't log console messages in private browsing mode
Rob Tsukf8bdfce2010-10-07 15:41:16 -0700997 if (isPrivateBrowsingEnabled()) return true;
Jeff Hamilton47654f42010-09-07 09:57:51 -0500998
Ben Murdochc42addf2010-01-28 15:19:59 +0000999 String message = "Console: " + consoleMessage.message() + " "
1000 + consoleMessage.sourceId() + ":"
1001 + consoleMessage.lineNumber();
1002
1003 switch (consoleMessage.messageLevel()) {
1004 case TIP:
1005 Log.v(CONSOLE_LOGTAG, message);
1006 break;
1007 case LOG:
1008 Log.i(CONSOLE_LOGTAG, message);
1009 break;
1010 case WARNING:
1011 Log.w(CONSOLE_LOGTAG, message);
1012 break;
1013 case ERROR:
1014 Log.e(CONSOLE_LOGTAG, message);
1015 break;
1016 case DEBUG:
1017 Log.d(CONSOLE_LOGTAG, message);
1018 break;
1019 }
1020
1021 return true;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001022 }
1023
1024 /**
1025 * Ask the browser for an icon to represent a <video> element.
1026 * This icon will be used if the Web page did not specify a poster attribute.
1027 * @return Bitmap The icon or null if no such icon is available.
1028 */
1029 @Override
1030 public Bitmap getDefaultVideoPoster() {
1031 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001032 return mWebViewController.getDefaultVideoPoster();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001033 }
1034 return null;
1035 }
1036
1037 /**
1038 * Ask the host application for a custom progress view to show while
1039 * a <video> is loading.
1040 * @return View The progress view.
1041 */
1042 @Override
1043 public View getVideoLoadingProgressView() {
1044 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001045 return mWebViewController.getVideoLoadingProgressView();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001046 }
1047 return null;
1048 }
1049
1050 @Override
Ben Murdoch62b1b7e2010-05-19 20:38:56 +01001051 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001052 if (mInForeground) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001053 mWebViewController.openFileChooser(uploadMsg, acceptType);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001054 } else {
1055 uploadMsg.onReceiveValue(null);
1056 }
1057 }
1058
1059 /**
1060 * Deliver a list of already-visited URLs
1061 */
1062 @Override
1063 public void getVisitedHistory(final ValueCallback<String[]> callback) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001064 mWebViewController.getVisitedHistory(callback);
1065 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001066 };
1067
1068 // -------------------------------------------------------------------------
1069 // WebViewClient implementation for the sub window
1070 // -------------------------------------------------------------------------
1071
1072 // Subclass of WebViewClient used in subwindows to notify the main
1073 // WebViewClient of certain WebView activities.
1074 private static class SubWindowClient extends WebViewClient {
1075 // The main WebViewClient.
1076 private final WebViewClient mClient;
Michael Kolb8233fac2010-10-26 16:08:53 -07001077 private final WebViewController mController;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001078
Michael Kolb8233fac2010-10-26 16:08:53 -07001079 SubWindowClient(WebViewClient client, WebViewController controller) {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001080 mClient = client;
Michael Kolb8233fac2010-10-26 16:08:53 -07001081 mController = controller;
Leon Scroggins III211ba542010-04-19 13:21:13 -04001082 }
1083 @Override
1084 public void onPageStarted(WebView view, String url, Bitmap favicon) {
1085 // Unlike the others, do not call mClient's version, which would
1086 // change the progress bar. However, we do want to remove the
Cary Clark01cfcdd2010-06-04 16:36:45 -04001087 // find or select dialog.
Michael Kolb8233fac2010-10-26 16:08:53 -07001088 mController.endActionMode();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001089 }
1090 @Override
1091 public void doUpdateVisitedHistory(WebView view, String url,
1092 boolean isReload) {
1093 mClient.doUpdateVisitedHistory(view, url, isReload);
1094 }
1095 @Override
1096 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1097 return mClient.shouldOverrideUrlLoading(view, url);
1098 }
1099 @Override
1100 public void onReceivedSslError(WebView view, SslErrorHandler handler,
1101 SslError error) {
1102 mClient.onReceivedSslError(view, handler, error);
1103 }
1104 @Override
1105 public void onReceivedHttpAuthRequest(WebView view,
1106 HttpAuthHandler handler, String host, String realm) {
1107 mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
1108 }
1109 @Override
1110 public void onFormResubmission(WebView view, Message dontResend,
1111 Message resend) {
1112 mClient.onFormResubmission(view, dontResend, resend);
1113 }
1114 @Override
1115 public void onReceivedError(WebView view, int errorCode,
1116 String description, String failingUrl) {
1117 mClient.onReceivedError(view, errorCode, description, failingUrl);
1118 }
1119 @Override
1120 public boolean shouldOverrideKeyEvent(WebView view,
1121 android.view.KeyEvent event) {
1122 return mClient.shouldOverrideKeyEvent(view, event);
1123 }
1124 @Override
1125 public void onUnhandledKeyEvent(WebView view,
1126 android.view.KeyEvent event) {
1127 mClient.onUnhandledKeyEvent(view, event);
1128 }
1129 }
1130
1131 // -------------------------------------------------------------------------
1132 // WebChromeClient implementation for the sub window
1133 // -------------------------------------------------------------------------
1134
1135 private class SubWindowChromeClient extends WebChromeClient {
1136 // The main WebChromeClient.
1137 private final WebChromeClient mClient;
1138
1139 SubWindowChromeClient(WebChromeClient client) {
1140 mClient = client;
1141 }
1142 @Override
1143 public void onProgressChanged(WebView view, int newProgress) {
1144 mClient.onProgressChanged(view, newProgress);
1145 }
1146 @Override
1147 public boolean onCreateWindow(WebView view, boolean dialog,
1148 boolean userGesture, android.os.Message resultMsg) {
1149 return mClient.onCreateWindow(view, dialog, userGesture, resultMsg);
1150 }
1151 @Override
1152 public void onCloseWindow(WebView window) {
1153 if (window != mSubView) {
1154 Log.e(LOGTAG, "Can't close the window");
1155 }
Michael Kolb8233fac2010-10-26 16:08:53 -07001156 mWebViewController.dismissSubWindow(Tab.this);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001157 }
1158 }
1159
1160 // -------------------------------------------------------------------------
1161
Michael Kolb8233fac2010-10-26 16:08:53 -07001162 // TODO temporarily use activity here
1163 // remove later
1164
Grace Kloba22ac16e2009-10-07 18:00:23 -07001165 // Construct a new tab
Michael Kolb8233fac2010-10-26 16:08:53 -07001166 Tab(WebViewController wvcontroller, WebView w, boolean closeOnExit, String appId,
Grace Kloba22ac16e2009-10-07 18:00:23 -07001167 String url) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001168 mWebViewController = wvcontroller;
1169 mActivity = mWebViewController.getActivity();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001170 mCloseOnExit = closeOnExit;
1171 mAppId = appId;
1172 mOriginalUrl = url;
Michael Kolb8233fac2010-10-26 16:08:53 -07001173 mLockIconType = LOCK_ICON_UNSECURE;
1174 mPrevLockIconType = LOCK_ICON_UNSECURE;
1175 mInPageLoad = false;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001176 mInForeground = false;
1177
Michael Kolb8233fac2010-10-26 16:08:53 -07001178 mInflateService = LayoutInflater.from(mActivity);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001179
1180 // The tab consists of a container view, which contains the main
1181 // WebView, as well as any other UI elements associated with the tab.
Leon Scroggins III211ba542010-04-19 13:21:13 -04001182 mContainer = (LinearLayout) mInflateService.inflate(R.layout.tab, null);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001183
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001184 mDownloadListener = new DownloadListener() {
1185 public void onDownloadStart(String url, String userAgent,
1186 String contentDisposition, String mimetype,
1187 long contentLength) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001188 mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition,
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001189 mimetype, contentLength);
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001190 }
1191 };
Leon Scroggins0c75a8e2010-03-03 16:40:58 -05001192 mWebBackForwardListClient = new WebBackForwardListClient() {
1193 @Override
1194 public void onNewHistoryItem(WebHistoryItem item) {
1195 if (isInVoiceSearchMode()) {
1196 item.setCustomData(mVoiceSearchData.mVoiceSearchIntent);
1197 }
1198 }
1199 @Override
1200 public void onIndexChanged(WebHistoryItem item, int index) {
1201 Object data = item.getCustomData();
1202 if (data != null && data instanceof Intent) {
1203 activateVoiceSearchMode((Intent) data);
1204 }
1205 }
1206 };
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001207
Grace Kloba22ac16e2009-10-07 18:00:23 -07001208 setWebView(w);
1209 }
1210
1211 /**
1212 * Sets the WebView for this tab, correctly removing the old WebView from
1213 * the container view.
1214 */
1215 void setWebView(WebView w) {
1216 if (mMainView == w) {
1217 return;
1218 }
1219 // If the WebView is changing, the page will be reloaded, so any ongoing
1220 // Geolocation permission requests are void.
Grace Kloba50c241e2010-04-20 11:07:50 -07001221 if (mGeolocationPermissionsPrompt != null) {
1222 mGeolocationPermissionsPrompt.hide();
1223 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001224
1225 // Just remove the old one.
1226 FrameLayout wrapper =
1227 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
1228 wrapper.removeView(mMainView);
1229
1230 // set the new one
1231 mMainView = w;
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001232 // attach the WebViewClient, WebChromeClient and DownloadListener
Grace Kloba22ac16e2009-10-07 18:00:23 -07001233 if (mMainView != null) {
1234 mMainView.setWebViewClient(mWebViewClient);
1235 mMainView.setWebChromeClient(mWebChromeClient);
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001236 // Attach DownloadManager so that downloads can start in an active
1237 // or a non-active window. This can happen when going to a site that
1238 // does a redirect after a period of time. The user could have
1239 // switched to another tab while waiting for the download to start.
1240 mMainView.setDownloadListener(mDownloadListener);
Leon Scroggins0c75a8e2010-03-03 16:40:58 -05001241 mMainView.setWebBackForwardListClient(mWebBackForwardListClient);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001242 }
1243 }
1244
1245 /**
1246 * Destroy the tab's main WebView and subWindow if any
1247 */
1248 void destroy() {
1249 if (mMainView != null) {
1250 dismissSubWindow();
1251 BrowserSettings.getInstance().deleteObserver(mMainView.getSettings());
1252 // save the WebView to call destroy() after detach it from the tab
1253 WebView webView = mMainView;
1254 setWebView(null);
1255 webView.destroy();
1256 }
1257 }
1258
1259 /**
1260 * Remove the tab from the parent
1261 */
1262 void removeFromTree() {
1263 // detach the children
1264 if (mChildTabs != null) {
1265 for(Tab t : mChildTabs) {
1266 t.setParentTab(null);
1267 }
1268 }
1269 // remove itself from the parent list
1270 if (mParentTab != null) {
1271 mParentTab.mChildTabs.remove(this);
1272 }
1273 }
1274
1275 /**
1276 * Create a new subwindow unless a subwindow already exists.
1277 * @return True if a new subwindow was created. False if one already exists.
1278 */
1279 boolean createSubWindow() {
1280 if (mSubView == null) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001281 mWebViewController.endActionMode();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001282 mSubViewContainer = mInflateService.inflate(
1283 R.layout.browser_subwindow, null);
1284 mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview);
Grace Kloba80380ed2010-03-19 17:44:21 -07001285 mSubView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001286 // use trackball directly
1287 mSubView.setMapTrackballToArrowKeys(false);
Grace Kloba140b33a2010-03-19 18:40:09 -07001288 // Enable the built-in zoom
1289 mSubView.getSettings().setBuiltInZoomControls(true);
Leon Scroggins III211ba542010-04-19 13:21:13 -04001290 mSubView.setWebViewClient(new SubWindowClient(mWebViewClient,
Michael Kolb8233fac2010-10-26 16:08:53 -07001291 mWebViewController));
Grace Kloba22ac16e2009-10-07 18:00:23 -07001292 mSubView.setWebChromeClient(new SubWindowChromeClient(
1293 mWebChromeClient));
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001294 // Set a different DownloadListener for the mSubView, since it will
1295 // just need to dismiss the mSubView, rather than close the Tab
1296 mSubView.setDownloadListener(new DownloadListener() {
1297 public void onDownloadStart(String url, String userAgent,
1298 String contentDisposition, String mimetype,
1299 long contentLength) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001300 mWebViewController.onDownloadStart(Tab.this, url, userAgent,
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001301 contentDisposition, mimetype, contentLength);
1302 if (mSubView.copyBackForwardList().getSize() == 0) {
1303 // This subwindow was opened for the sole purpose of
1304 // downloading a file. Remove it.
Michael Kolb8233fac2010-10-26 16:08:53 -07001305 mWebViewController.dismissSubWindow(Tab.this);
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001306 }
1307 }
1308 });
Grace Kloba22ac16e2009-10-07 18:00:23 -07001309 mSubView.setOnCreateContextMenuListener(mActivity);
1310 final BrowserSettings s = BrowserSettings.getInstance();
1311 s.addObserver(mSubView.getSettings()).update(s, null);
1312 final ImageButton cancel = (ImageButton) mSubViewContainer
1313 .findViewById(R.id.subwindow_close);
1314 cancel.setOnClickListener(new OnClickListener() {
1315 public void onClick(View v) {
1316 mSubView.getWebChromeClient().onCloseWindow(mSubView);
1317 }
1318 });
1319 return true;
1320 }
1321 return false;
1322 }
1323
1324 /**
1325 * Dismiss the subWindow for the tab.
1326 */
1327 void dismissSubWindow() {
1328 if (mSubView != null) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001329 mWebViewController.endActionMode();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001330 BrowserSettings.getInstance().deleteObserver(
1331 mSubView.getSettings());
1332 mSubView.destroy();
1333 mSubView = null;
1334 mSubViewContainer = null;
1335 }
1336 }
1337
Grace Kloba22ac16e2009-10-07 18:00:23 -07001338
1339 /**
1340 * Set the parent tab of this tab.
1341 */
1342 void setParentTab(Tab parent) {
1343 mParentTab = parent;
1344 // This tab may have been freed due to low memory. If that is the case,
1345 // the parent tab index is already saved. If we are changing that index
1346 // (most likely due to removing the parent tab) we must update the
1347 // parent tab index in the saved Bundle.
1348 if (mSavedState != null) {
1349 if (parent == null) {
1350 mSavedState.remove(PARENTTAB);
1351 } else {
Michael Kolb8233fac2010-10-26 16:08:53 -07001352 mSavedState.putInt(PARENTTAB, mWebViewController.getTabControl()
Grace Kloba22ac16e2009-10-07 18:00:23 -07001353 .getTabIndex(parent));
1354 }
1355 }
1356 }
1357
1358 /**
1359 * When a Tab is created through the content of another Tab, then we
1360 * associate the Tabs.
1361 * @param child the Tab that was created from this Tab
1362 */
1363 void addChildTab(Tab child) {
1364 if (mChildTabs == null) {
1365 mChildTabs = new Vector<Tab>();
1366 }
1367 mChildTabs.add(child);
1368 child.setParentTab(this);
1369 }
1370
1371 Vector<Tab> getChildTabs() {
1372 return mChildTabs;
1373 }
1374
1375 void resume() {
1376 if (mMainView != null) {
1377 mMainView.onResume();
1378 if (mSubView != null) {
1379 mSubView.onResume();
1380 }
1381 }
1382 }
1383
1384 void pause() {
1385 if (mMainView != null) {
1386 mMainView.onPause();
1387 if (mSubView != null) {
1388 mSubView.onPause();
1389 }
1390 }
1391 }
1392
1393 void putInForeground() {
1394 mInForeground = true;
1395 resume();
1396 mMainView.setOnCreateContextMenuListener(mActivity);
1397 if (mSubView != null) {
1398 mSubView.setOnCreateContextMenuListener(mActivity);
1399 }
1400 // Show the pending error dialog if the queue is not empty
1401 if (mQueuedErrors != null && mQueuedErrors.size() > 0) {
1402 showError(mQueuedErrors.getFirst());
1403 }
1404 }
1405
1406 void putInBackground() {
1407 mInForeground = false;
1408 pause();
1409 mMainView.setOnCreateContextMenuListener(null);
1410 if (mSubView != null) {
1411 mSubView.setOnCreateContextMenuListener(null);
1412 }
1413 }
1414
Michael Kolb8233fac2010-10-26 16:08:53 -07001415 boolean inForeground() {
1416 return mInForeground;
1417 }
1418
Grace Kloba22ac16e2009-10-07 18:00:23 -07001419 /**
1420 * Return the top window of this tab; either the subwindow if it is not
1421 * null or the main window.
1422 * @return The top window of this tab.
1423 */
1424 WebView getTopWindow() {
1425 if (mSubView != null) {
1426 return mSubView;
1427 }
1428 return mMainView;
1429 }
1430
1431 /**
1432 * Return the main window of this tab. Note: if a tab is freed in the
1433 * background, this can return null. It is only guaranteed to be
1434 * non-null for the current tab.
1435 * @return The main WebView of this tab.
1436 */
1437 WebView getWebView() {
1438 return mMainView;
1439 }
1440
Michael Kolb8233fac2010-10-26 16:08:53 -07001441 View getViewContainer() {
1442 return mContainer;
1443 }
1444
Grace Kloba22ac16e2009-10-07 18:00:23 -07001445 /**
Rob Tsukf8bdfce2010-10-07 15:41:16 -07001446 * Return whether private browsing is enabled for the main window of
1447 * this tab.
1448 * @return True if private browsing is enabled.
1449 */
Michael Kolb8233fac2010-10-26 16:08:53 -07001450 boolean isPrivateBrowsingEnabled() {
Rob Tsukf8bdfce2010-10-07 15:41:16 -07001451 WebView webView = getWebView();
1452 if (webView == null) {
1453 return false;
1454 }
1455 return webView.isPrivateBrowsingEnabled();
1456 }
1457
1458 /**
Grace Kloba22ac16e2009-10-07 18:00:23 -07001459 * Return the subwindow of this tab or null if there is no subwindow.
1460 * @return The subwindow of this tab or null.
1461 */
1462 WebView getSubWebView() {
1463 return mSubView;
1464 }
1465
Michael Kolb8233fac2010-10-26 16:08:53 -07001466 View getSubViewContainer() {
1467 return mSubViewContainer;
1468 }
1469
Grace Kloba22ac16e2009-10-07 18:00:23 -07001470 /**
1471 * @return The geolocation permissions prompt for this tab.
1472 */
1473 GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
Grace Kloba50c241e2010-04-20 11:07:50 -07001474 if (mGeolocationPermissionsPrompt == null) {
1475 ViewStub stub = (ViewStub) mContainer
1476 .findViewById(R.id.geolocation_permissions_prompt);
1477 mGeolocationPermissionsPrompt = (GeolocationPermissionsPrompt) stub
1478 .inflate();
1479 mGeolocationPermissionsPrompt.init();
1480 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001481 return mGeolocationPermissionsPrompt;
1482 }
1483
1484 /**
1485 * @return The application id string
1486 */
1487 String getAppId() {
1488 return mAppId;
1489 }
1490
1491 /**
1492 * Set the application id string
1493 * @param id
1494 */
1495 void setAppId(String id) {
1496 mAppId = id;
1497 }
1498
1499 /**
1500 * @return The original url associated with this Tab
1501 */
1502 String getOriginalUrl() {
1503 return mOriginalUrl;
1504 }
1505
1506 /**
1507 * Set the original url associated with this tab
1508 */
1509 void setOriginalUrl(String url) {
1510 mOriginalUrl = url;
1511 }
1512
1513 /**
Michael Kolb8233fac2010-10-26 16:08:53 -07001514 * set the title for the tab
1515 */
1516 void setCurrentTitle(String title) {
1517 mCurrentTitle = title;
1518 }
1519
1520 /**
1521 * set url for this tab
1522 * @param url
1523 */
1524 void setCurrentUrl(String url) {
1525 mCurrentUrl = url;
1526 }
1527
1528 String getCurrentTitle() {
1529 return mCurrentTitle;
1530 }
1531
1532 String getCurrentUrl() {
1533 return mCurrentUrl;
1534 }
1535 /**
Grace Kloba22ac16e2009-10-07 18:00:23 -07001536 * Get the url of this tab. Valid after calling populatePickerData, but
1537 * before calling wipePickerData, or if the webview has been destroyed.
1538 * @return The WebView's url or null.
1539 */
1540 String getUrl() {
1541 if (mPickerData != null) {
1542 return mPickerData.mUrl;
1543 }
1544 return null;
1545 }
1546
1547 /**
1548 * Get the title of this tab. Valid after calling populatePickerData, but
1549 * before calling wipePickerData, or if the webview has been destroyed. If
1550 * the url has no title, use the url instead.
1551 * @return The WebView's title (or url) or null.
1552 */
1553 String getTitle() {
1554 if (mPickerData != null) {
1555 return mPickerData.mTitle;
1556 }
1557 return null;
1558 }
1559
1560 /**
1561 * Get the favicon of this tab. Valid after calling populatePickerData, but
1562 * before calling wipePickerData, or if the webview has been destroyed.
1563 * @return The WebView's favicon or null.
1564 */
1565 Bitmap getFavicon() {
1566 if (mPickerData != null) {
1567 return mPickerData.mFavicon;
1568 }
1569 return null;
1570 }
1571
Rob Tsukf8bdfce2010-10-07 15:41:16 -07001572
Grace Kloba22ac16e2009-10-07 18:00:23 -07001573 /**
1574 * Return the tab's error console. Creates the console if createIfNEcessary
1575 * is true and we haven't already created the console.
1576 * @param createIfNecessary Flag to indicate if the console should be
1577 * created if it has not been already.
1578 * @return The tab's error console, or null if one has not been created and
1579 * createIfNecessary is false.
1580 */
1581 ErrorConsoleView getErrorConsole(boolean createIfNecessary) {
1582 if (createIfNecessary && mErrorConsole == null) {
1583 mErrorConsole = new ErrorConsoleView(mActivity);
1584 mErrorConsole.setWebView(mMainView);
1585 }
1586 return mErrorConsole;
1587 }
1588
1589 /**
1590 * If this Tab was created through another Tab, then this method returns
1591 * that Tab.
1592 * @return the Tab parent or null
1593 */
1594 public Tab getParentTab() {
1595 return mParentTab;
1596 }
1597
1598 /**
1599 * Return whether this tab should be closed when it is backing out of the
1600 * first page.
1601 * @return TRUE if this tab should be closed when exit.
1602 */
1603 boolean closeOnExit() {
1604 return mCloseOnExit;
1605 }
1606
1607 /**
1608 * Saves the current lock-icon state before resetting the lock icon. If we
1609 * have an error, we may need to roll back to the previous state.
1610 */
1611 void resetLockIcon(String url) {
1612 mPrevLockIconType = mLockIconType;
Michael Kolb8233fac2010-10-26 16:08:53 -07001613 mLockIconType = LOCK_ICON_UNSECURE;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001614 if (URLUtil.isHttpsUrl(url)) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001615 mLockIconType = LOCK_ICON_SECURE;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001616 }
1617 }
1618
1619 /**
1620 * Reverts the lock-icon state to the last saved state, for example, if we
1621 * had an error, and need to cancel the load.
1622 */
1623 void revertLockIcon() {
1624 mLockIconType = mPrevLockIconType;
1625 }
1626
1627 /**
1628 * @return The tab's lock icon type.
1629 */
1630 int getLockIconType() {
1631 return mLockIconType;
1632 }
1633
1634 /**
1635 * @return TRUE if onPageStarted is called while onPageFinished is not
1636 * called yet.
1637 */
Michael Kolb8233fac2010-10-26 16:08:53 -07001638 boolean inPageLoad() {
1639 return mInPageLoad;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001640 }
1641
1642 // force mInLoad to be false. This should only be called before closing the
1643 // tab to ensure BrowserActivity's pauseWebViewTimers() is called correctly.
Michael Kolb8233fac2010-10-26 16:08:53 -07001644 void clearInPageLoad() {
1645 mInPageLoad = false;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001646 }
1647
1648 void populatePickerData() {
1649 if (mMainView == null) {
1650 populatePickerDataFromSavedState();
1651 return;
1652 }
1653
1654 // FIXME: The only place we cared about subwindow was for
1655 // bookmarking (i.e. not when saving state). Was this deliberate?
1656 final WebBackForwardList list = mMainView.copyBackForwardList();
Leon Scroggins70a153b2010-05-10 17:27:26 -04001657 if (list == null) {
1658 Log.w(LOGTAG, "populatePickerData called and WebBackForwardList is null");
1659 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001660 final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
1661 populatePickerData(item);
1662 }
1663
1664 // Populate the picker data using the given history item and the current top
1665 // WebView.
1666 private void populatePickerData(WebHistoryItem item) {
1667 mPickerData = new PickerData();
Leon Scroggins70a153b2010-05-10 17:27:26 -04001668 if (item == null) {
1669 Log.w(LOGTAG, "populatePickerData called with a null WebHistoryItem");
1670 } else {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001671 mPickerData.mUrl = item.getUrl();
1672 mPickerData.mTitle = item.getTitle();
1673 mPickerData.mFavicon = item.getFavicon();
1674 if (mPickerData.mTitle == null) {
1675 mPickerData.mTitle = mPickerData.mUrl;
1676 }
1677 }
1678 }
1679
1680 // Create the PickerData and populate it using the saved state of the tab.
1681 void populatePickerDataFromSavedState() {
1682 if (mSavedState == null) {
1683 return;
1684 }
1685 mPickerData = new PickerData();
1686 mPickerData.mUrl = mSavedState.getString(CURRURL);
1687 mPickerData.mTitle = mSavedState.getString(CURRTITLE);
1688 }
1689
1690 void clearPickerData() {
1691 mPickerData = null;
1692 }
1693
1694 /**
1695 * Get the saved state bundle.
1696 * @return
1697 */
1698 Bundle getSavedState() {
1699 return mSavedState;
1700 }
1701
1702 /**
1703 * Set the saved state.
1704 */
1705 void setSavedState(Bundle state) {
1706 mSavedState = state;
1707 }
1708
1709 /**
1710 * @return TRUE if succeed in saving the state.
1711 */
1712 boolean saveState() {
1713 // If the WebView is null it means we ran low on memory and we already
1714 // stored the saved state in mSavedState.
1715 if (mMainView == null) {
1716 return mSavedState != null;
1717 }
1718
1719 mSavedState = new Bundle();
1720 final WebBackForwardList list = mMainView.saveState(mSavedState);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001721
1722 // Store some extra info for displaying the tab in the picker.
1723 final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
1724 populatePickerData(item);
1725
1726 if (mPickerData.mUrl != null) {
1727 mSavedState.putString(CURRURL, mPickerData.mUrl);
1728 }
1729 if (mPickerData.mTitle != null) {
1730 mSavedState.putString(CURRTITLE, mPickerData.mTitle);
1731 }
1732 mSavedState.putBoolean(CLOSEONEXIT, mCloseOnExit);
1733 if (mAppId != null) {
1734 mSavedState.putString(APPID, mAppId);
1735 }
1736 if (mOriginalUrl != null) {
1737 mSavedState.putString(ORIGINALURL, mOriginalUrl);
1738 }
1739 // Remember the parent tab so the relationship can be restored.
1740 if (mParentTab != null) {
Michael Kolb8233fac2010-10-26 16:08:53 -07001741 mSavedState.putInt(PARENTTAB, mWebViewController.getTabControl().getTabIndex(
Grace Kloba22ac16e2009-10-07 18:00:23 -07001742 mParentTab));
1743 }
1744 return true;
1745 }
1746
1747 /*
1748 * Restore the state of the tab.
1749 */
1750 boolean restoreState(Bundle b) {
1751 if (b == null) {
1752 return false;
1753 }
1754 // Restore the internal state even if the WebView fails to restore.
1755 // This will maintain the app id, original url and close-on-exit values.
1756 mSavedState = null;
1757 mPickerData = null;
1758 mCloseOnExit = b.getBoolean(CLOSEONEXIT);
1759 mAppId = b.getString(APPID);
1760 mOriginalUrl = b.getString(ORIGINALURL);
1761
1762 final WebBackForwardList list = mMainView.restoreState(b);
1763 if (list == null) {
1764 return false;
1765 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001766 return true;
1767 }
Leon Scroggins III211ba542010-04-19 13:21:13 -04001768
Grace Kloba22ac16e2009-10-07 18:00:23 -07001769}