blob: 1700961701130818f4df7ac2cac57629cdd876ae [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
19import java.io.File;
Leon Scroggins58d56c62010-01-28 15:12:40 -050020import java.util.ArrayList;
Leon Scroggins9df94972010-03-08 18:20:35 -050021import java.util.HashMap;
22import java.util.Iterator;
Grace Kloba22ac16e2009-10-07 18:00:23 -070023import java.util.LinkedList;
Leon Scroggins9df94972010-03-08 18:20:35 -050024import java.util.Map;
Grace Kloba22ac16e2009-10-07 18:00:23 -070025import java.util.Vector;
26
27import android.app.AlertDialog;
Leon Scroggins58d56c62010-01-28 15:12:40 -050028import android.app.SearchManager;
Grace Kloba22ac16e2009-10-07 18:00:23 -070029import android.content.ContentResolver;
30import android.content.ContentValues;
31import android.content.DialogInterface;
32import android.content.DialogInterface.OnCancelListener;
Leon Scroggins58d56c62010-01-28 15:12:40 -050033import android.content.Intent;
Grace Kloba22ac16e2009-10-07 18:00:23 -070034import android.database.Cursor;
35import android.database.sqlite.SQLiteDatabase;
36import android.database.sqlite.SQLiteException;
37import android.graphics.Bitmap;
38import android.net.Uri;
39import android.net.http.SslError;
40import android.os.AsyncTask;
41import android.os.Bundle;
42import android.os.Message;
Kristian Monsen4dce3bf2010-02-02 13:37:09 +000043import android.os.SystemClock;
Grace Kloba22ac16e2009-10-07 18:00:23 -070044import android.provider.Browser;
Leon Scrogginsa1cc3fd2010-02-01 16:14:11 -050045import android.speech.RecognizerResultsIntent;
Grace Kloba22ac16e2009-10-07 18:00:23 -070046import android.util.Log;
47import android.view.KeyEvent;
48import android.view.LayoutInflater;
49import android.view.View;
50import android.view.ViewGroup;
Grace Kloba50c241e2010-04-20 11:07:50 -070051import android.view.ViewStub;
Grace Kloba22ac16e2009-10-07 18:00:23 -070052import android.view.View.OnClickListener;
Ben Murdochc42addf2010-01-28 15:19:59 +000053import android.webkit.ConsoleMessage;
Grace Kloba22ac16e2009-10-07 18:00:23 -070054import android.webkit.CookieSyncManager;
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -050055import android.webkit.DownloadListener;
Grace Kloba22ac16e2009-10-07 18:00:23 -070056import android.webkit.GeolocationPermissions;
57import android.webkit.HttpAuthHandler;
58import android.webkit.SslErrorHandler;
59import android.webkit.URLUtil;
60import android.webkit.ValueCallback;
61import android.webkit.WebBackForwardList;
Leon Scroggins0c75a8e2010-03-03 16:40:58 -050062import android.webkit.WebBackForwardListClient;
Grace Kloba22ac16e2009-10-07 18:00:23 -070063import android.webkit.WebChromeClient;
64import android.webkit.WebHistoryItem;
65import android.webkit.WebIconDatabase;
66import android.webkit.WebStorage;
67import android.webkit.WebView;
68import android.webkit.WebViewClient;
69import android.widget.FrameLayout;
70import android.widget.ImageButton;
71import android.widget.LinearLayout;
72import android.widget.TextView;
73
Leon Scroggins1fe13a52010-02-09 15:31:26 -050074import com.android.common.speech.LoggingEvents;
75
Grace Kloba22ac16e2009-10-07 18:00:23 -070076/**
77 * Class for maintaining Tabs with a main WebView and a subwindow.
78 */
79class Tab {
80 // Log Tag
81 private static final String LOGTAG = "Tab";
Ben Murdochc42addf2010-01-28 15:19:59 +000082 // Special case the logtag for messages for the Console to make it easier to
83 // filter them and match the logtag used for these messages in older versions
84 // of the browser.
85 private static final String CONSOLE_LOGTAG = "browser";
86
Grace Kloba22ac16e2009-10-07 18:00:23 -070087 // The Geolocation permissions prompt
88 private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
89 // Main WebView wrapper
90 private View mContainer;
91 // Main WebView
92 private WebView mMainView;
93 // Subwindow container
94 private View mSubViewContainer;
95 // Subwindow WebView
96 private WebView mSubView;
97 // Saved bundle for when we are running low on memory. It contains the
98 // information needed to restore the WebView if the user goes back to the
99 // tab.
100 private Bundle mSavedState;
101 // Data used when displaying the tab in the picker.
102 private PickerData mPickerData;
103 // Parent Tab. This is the Tab that created this Tab, or null if the Tab was
104 // created by the UI
105 private Tab mParentTab;
106 // Tab that constructed by this Tab. This is used when this Tab is
107 // destroyed, it clears all mParentTab values in the children.
108 private Vector<Tab> mChildTabs;
109 // If true, the tab will be removed when back out of the first page.
110 private boolean mCloseOnExit;
111 // If true, the tab is in the foreground of the current activity.
112 private boolean mInForeground;
113 // If true, the tab is in loading state.
114 private boolean mInLoad;
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;
123 // Error console for the tab
124 private ErrorConsoleView mErrorConsole;
125 // the lock icon type and previous lock icon type for the tab
126 private int mLockIconType;
127 private int mPrevLockIconType;
128 // Inflation service for making subwindows.
129 private final LayoutInflater mInflateService;
130 // The BrowserActivity which owners the Tab
131 private final BrowserActivity mActivity;
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -0500132 // The listener that gets invoked when a download is started from the
133 // mMainView
134 private final DownloadListener mDownloadListener;
Leon Scroggins0c75a8e2010-03-03 16:40:58 -0500135 // Listener used to know when we move forward or back in the history list.
136 private final WebBackForwardListClient mWebBackForwardListClient;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700137
138 // AsyncTask for downloading touch icons
139 DownloadTouchIcon mTouchIconLoader;
140
141 // Extra saved information for displaying the tab in the picker.
142 private static class PickerData {
143 String mUrl;
144 String mTitle;
145 Bitmap mFavicon;
146 }
147
148 // Used for saving and restoring each Tab
149 static final String WEBVIEW = "webview";
150 static final String NUMTABS = "numTabs";
151 static final String CURRTAB = "currentTab";
152 static final String CURRURL = "currentUrl";
153 static final String CURRTITLE = "currentTitle";
154 static final String CURRPICTURE = "currentPicture";
155 static final String CLOSEONEXIT = "closeonexit";
156 static final String PARENTTAB = "parentTab";
157 static final String APPID = "appid";
158 static final String ORIGINALURL = "originalUrl";
159
160 // -------------------------------------------------------------------------
161
Leon Scroggins58d56c62010-01-28 15:12:40 -0500162 /**
163 * Private information regarding the latest voice search. If the Tab is not
164 * in voice search mode, this will be null.
165 */
166 private VoiceSearchData mVoiceSearchData;
167 /**
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400168 * Remove voice search mode from this tab.
169 */
170 public void revertVoiceSearchMode() {
171 if (mVoiceSearchData != null) {
172 mVoiceSearchData = null;
173 if (mInForeground) {
174 mActivity.revertVoiceTitleBar();
175 }
176 }
177 }
178 /**
Leon Scroggins58d56c62010-01-28 15:12:40 -0500179 * Return whether the tab is in voice search mode.
180 */
181 public boolean isInVoiceSearchMode() {
182 return mVoiceSearchData != null;
183 }
184 /**
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500185 * Return true if the voice search Intent came with a String identifying
186 * that Google provided the Intent.
187 */
188 public boolean voiceSearchSourceIsGoogle() {
189 return mVoiceSearchData != null && mVoiceSearchData.mSourceIsGoogle;
190 }
191 /**
Leon Scroggins58d56c62010-01-28 15:12:40 -0500192 * Get the title to display for the current voice search page. If the Tab
193 * is not in voice search mode, return null.
194 */
195 public String getVoiceDisplayTitle() {
196 if (mVoiceSearchData == null) return null;
197 return mVoiceSearchData.mLastVoiceSearchTitle;
198 }
199 /**
200 * Get the latest array of voice search results, to be passed to the
201 * BrowserProvider. If the Tab is not in voice search mode, return null.
202 */
203 public ArrayList<String> getVoiceSearchResults() {
204 if (mVoiceSearchData == null) return null;
205 return mVoiceSearchData.mVoiceSearchResults;
206 }
207 /**
208 * Activate voice search mode.
209 * @param intent Intent which has the results to use, or an index into the
210 * results when reusing the old results.
211 */
212 /* package */ void activateVoiceSearchMode(Intent intent) {
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500213 int index = 0;
Leon Scroggins58d56c62010-01-28 15:12:40 -0500214 ArrayList<String> results = intent.getStringArrayListExtra(
Leon Scrogginsa1cc3fd2010-02-01 16:14:11 -0500215 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_STRINGS);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500216 if (results != null) {
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500217 ArrayList<String> urls = intent.getStringArrayListExtra(
218 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_URLS);
219 ArrayList<String> htmls = intent.getStringArrayListExtra(
220 RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_HTML);
221 ArrayList<String> baseUrls = intent.getStringArrayListExtra(
222 RecognizerResultsIntent
223 .EXTRA_VOICE_SEARCH_RESULT_HTML_BASE_URLS);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500224 // This tab is now entering voice search mode for the first time, or
225 // a new voice search was done.
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500226 int size = results.size();
227 if (urls == null || size != urls.size()) {
Leon Scroggins58d56c62010-01-28 15:12:40 -0500228 throw new AssertionError("improper extras passed in Intent");
229 }
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500230 if (htmls == null || htmls.size() != size || baseUrls == null ||
231 (baseUrls.size() != size && baseUrls.size() != 1)) {
232 // If either of these arrays are empty/incorrectly sized, ignore
233 // them.
234 htmls = null;
235 baseUrls = null;
236 }
237 mVoiceSearchData = new VoiceSearchData(results, urls, htmls,
238 baseUrls);
Leon Scroggins9df94972010-03-08 18:20:35 -0500239 mVoiceSearchData.mHeaders = intent.getParcelableArrayListExtra(
240 RecognizerResultsIntent
241 .EXTRA_VOICE_SEARCH_RESULT_HTTP_HEADERS);
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500242 mVoiceSearchData.mSourceIsGoogle = intent.getBooleanExtra(
243 VoiceSearchData.SOURCE_IS_GOOGLE, false);
Leon Scroggins2ee4a5a2010-03-15 16:56:57 -0400244 mVoiceSearchData.mVoiceSearchIntent = new Intent(intent);
Leon Scrogginse10dde52010-03-08 19:53:03 -0500245 }
246 String extraData = intent.getStringExtra(
247 SearchManager.EXTRA_DATA_KEY);
248 if (extraData != null) {
249 index = Integer.parseInt(extraData);
250 if (index >= mVoiceSearchData.mVoiceSearchResults.size()) {
251 throw new AssertionError("index must be less than "
252 + "size of mVoiceSearchResults");
253 }
254 if (mVoiceSearchData.mSourceIsGoogle) {
255 Intent logIntent = new Intent(
256 LoggingEvents.ACTION_LOG_EVENT);
257 logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
258 LoggingEvents.VoiceSearch.N_BEST_CHOOSE);
259 logIntent.putExtra(
260 LoggingEvents.VoiceSearch.EXTRA_N_BEST_CHOOSE_INDEX,
261 index);
262 mActivity.sendBroadcast(logIntent);
263 }
264 if (mVoiceSearchData.mVoiceSearchIntent != null) {
Leon Scroggins2ee4a5a2010-03-15 16:56:57 -0400265 // Copy the Intent, so that each history item will have its own
266 // Intent, with different (or none) extra data.
267 Intent latest = new Intent(mVoiceSearchData.mVoiceSearchIntent);
268 latest.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
269 mVoiceSearchData.mVoiceSearchIntent = latest;
Leon Scroggins58d56c62010-01-28 15:12:40 -0500270 }
271 }
272 mVoiceSearchData.mLastVoiceSearchTitle
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500273 = mVoiceSearchData.mVoiceSearchResults.get(index);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500274 if (mInForeground) {
275 mActivity.showVoiceTitleBar(mVoiceSearchData.mLastVoiceSearchTitle);
276 }
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500277 if (mVoiceSearchData.mVoiceSearchHtmls != null) {
278 // When index was found it was already ensured that it was valid
279 String uriString = mVoiceSearchData.mVoiceSearchHtmls.get(index);
280 if (uriString != null) {
281 Uri dataUri = Uri.parse(uriString);
282 if (RecognizerResultsIntent.URI_SCHEME_INLINE.equals(
283 dataUri.getScheme())) {
284 // If there is only one base URL, use it. If there are
285 // more, there will be one for each index, so use the base
286 // URL corresponding to the index.
287 String baseUrl = mVoiceSearchData.mVoiceSearchBaseUrls.get(
288 mVoiceSearchData.mVoiceSearchBaseUrls.size() > 1 ?
289 index : 0);
290 mVoiceSearchData.mLastVoiceSearchUrl = baseUrl;
291 mMainView.loadDataWithBaseURL(baseUrl,
292 uriString.substring(RecognizerResultsIntent
293 .URI_SCHEME_INLINE.length() + 1), "text/html",
294 "utf-8", baseUrl);
295 return;
296 }
297 }
298 }
Leon Scroggins58d56c62010-01-28 15:12:40 -0500299 mVoiceSearchData.mLastVoiceSearchUrl
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500300 = mVoiceSearchData.mVoiceSearchUrls.get(index);
301 if (null == mVoiceSearchData.mLastVoiceSearchUrl) {
302 mVoiceSearchData.mLastVoiceSearchUrl = mActivity.smartUrlFilter(
303 mVoiceSearchData.mLastVoiceSearchTitle);
304 }
Leon Scroggins9df94972010-03-08 18:20:35 -0500305 Map<String, String> headers = null;
306 if (mVoiceSearchData.mHeaders != null) {
307 int bundleIndex = mVoiceSearchData.mHeaders.size() == 1 ? 0
308 : index;
309 Bundle bundle = mVoiceSearchData.mHeaders.get(bundleIndex);
310 if (bundle != null && !bundle.isEmpty()) {
311 Iterator<String> iter = bundle.keySet().iterator();
312 headers = new HashMap<String, String>();
313 while (iter.hasNext()) {
314 String key = iter.next();
315 headers.put(key, bundle.getString(key));
316 }
317 }
318 }
319 mMainView.loadUrl(mVoiceSearchData.mLastVoiceSearchUrl, headers);
Leon Scroggins58d56c62010-01-28 15:12:40 -0500320 }
321 /* package */ static class VoiceSearchData {
Leon Scroggins58d56c62010-01-28 15:12:40 -0500322 public VoiceSearchData(ArrayList<String> results,
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500323 ArrayList<String> urls, ArrayList<String> htmls,
324 ArrayList<String> baseUrls) {
Leon Scroggins58d56c62010-01-28 15:12:40 -0500325 mVoiceSearchResults = results;
326 mVoiceSearchUrls = urls;
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500327 mVoiceSearchHtmls = htmls;
328 mVoiceSearchBaseUrls = baseUrls;
Leon Scroggins58d56c62010-01-28 15:12:40 -0500329 }
330 /*
331 * ArrayList of suggestions to be displayed when opening the
332 * SearchManager
333 */
334 public ArrayList<String> mVoiceSearchResults;
335 /*
336 * ArrayList of urls, associated with the suggestions in
337 * mVoiceSearchResults.
338 */
339 public ArrayList<String> mVoiceSearchUrls;
340 /*
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500341 * ArrayList holding content to load for each item in
342 * mVoiceSearchResults.
343 */
344 public ArrayList<String> mVoiceSearchHtmls;
345 /*
346 * ArrayList holding base urls for the items in mVoiceSearchResults.
347 * If non null, this will either have the same size as
348 * mVoiceSearchResults or have a size of 1, in which case all will use
349 * the same base url
350 */
351 public ArrayList<String> mVoiceSearchBaseUrls;
352 /*
Leon Scroggins58d56c62010-01-28 15:12:40 -0500353 * The last url provided by voice search. Used for comparison to see if
Leon Scroggins82c1baa2010-02-02 16:10:57 -0500354 * we are going to a page by some method besides voice search.
Leon Scroggins58d56c62010-01-28 15:12:40 -0500355 */
356 public String mLastVoiceSearchUrl;
357 /**
358 * The last title used for voice search. Needed to update the title bar
359 * when switching tabs.
360 */
361 public String mLastVoiceSearchTitle;
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500362 /**
363 * Whether the Intent which turned on voice search mode contained the
364 * String signifying that Google was the source.
365 */
366 public boolean mSourceIsGoogle;
367 /**
Leon Scroggins9df94972010-03-08 18:20:35 -0500368 * List of headers to be passed into the WebView containing location
369 * information
370 */
371 public ArrayList<Bundle> mHeaders;
372 /**
Leon Scroggins0c75a8e2010-03-03 16:40:58 -0500373 * The Intent used to invoke voice search. Placed on the
374 * WebHistoryItem so that when coming back to a previous voice search
375 * page we can again activate voice search.
376 */
Leon Scrogginse10dde52010-03-08 19:53:03 -0500377 public Intent mVoiceSearchIntent;
Leon Scroggins0c75a8e2010-03-03 16:40:58 -0500378 /**
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500379 * String used to identify Google as the source of voice search.
380 */
381 public static String SOURCE_IS_GOOGLE
382 = "android.speech.extras.SOURCE_IS_GOOGLE";
Leon Scroggins58d56c62010-01-28 15:12:40 -0500383 }
384
Grace Kloba22ac16e2009-10-07 18:00:23 -0700385 // Container class for the next error dialog that needs to be displayed
386 private class ErrorDialog {
387 public final int mTitle;
388 public final String mDescription;
389 public final int mError;
390 ErrorDialog(int title, String desc, int error) {
391 mTitle = title;
392 mDescription = desc;
393 mError = error;
394 }
395 };
396
397 private void processNextError() {
398 if (mQueuedErrors == null) {
399 return;
400 }
401 // The first one is currently displayed so just remove it.
402 mQueuedErrors.removeFirst();
403 if (mQueuedErrors.size() == 0) {
404 mQueuedErrors = null;
405 return;
406 }
407 showError(mQueuedErrors.getFirst());
408 }
409
410 private DialogInterface.OnDismissListener mDialogListener =
411 new DialogInterface.OnDismissListener() {
412 public void onDismiss(DialogInterface d) {
413 processNextError();
414 }
415 };
416 private LinkedList<ErrorDialog> mQueuedErrors;
417
418 private void queueError(int err, String desc) {
419 if (mQueuedErrors == null) {
420 mQueuedErrors = new LinkedList<ErrorDialog>();
421 }
422 for (ErrorDialog d : mQueuedErrors) {
423 if (d.mError == err) {
424 // Already saw a similar error, ignore the new one.
425 return;
426 }
427 }
428 ErrorDialog errDialog = new ErrorDialog(
429 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
430 R.string.browserFrameFileErrorLabel :
431 R.string.browserFrameNetworkErrorLabel,
432 desc, err);
433 mQueuedErrors.addLast(errDialog);
434
435 // Show the dialog now if the queue was empty and it is in foreground
436 if (mQueuedErrors.size() == 1 && mInForeground) {
437 showError(errDialog);
438 }
439 }
440
441 private void showError(ErrorDialog errDialog) {
442 if (mInForeground) {
443 AlertDialog d = new AlertDialog.Builder(mActivity)
444 .setTitle(errDialog.mTitle)
445 .setMessage(errDialog.mDescription)
446 .setPositiveButton(R.string.ok, null)
447 .create();
448 d.setOnDismissListener(mDialogListener);
449 d.show();
450 }
451 }
452
453 // -------------------------------------------------------------------------
454 // WebViewClient implementation for the main WebView
455 // -------------------------------------------------------------------------
456
457 private final WebViewClient mWebViewClient = new WebViewClient() {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500458 private Message mDontResend;
459 private Message mResend;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700460 @Override
461 public void onPageStarted(WebView view, String url, Bitmap favicon) {
462 mInLoad = true;
Kristian Monsen4dce3bf2010-02-02 13:37:09 +0000463 mLoadStartTime = SystemClock.uptimeMillis();
Leon Scroggins58d56c62010-01-28 15:12:40 -0500464 if (mVoiceSearchData != null
465 && !url.equals(mVoiceSearchData.mLastVoiceSearchUrl)) {
Leon Scroggins1fe13a52010-02-09 15:31:26 -0500466 if (mVoiceSearchData.mSourceIsGoogle) {
467 Intent i = new Intent(LoggingEvents.ACTION_LOG_EVENT);
468 i.putExtra(LoggingEvents.EXTRA_FLUSH, true);
469 mActivity.sendBroadcast(i);
470 }
Leon Scroggins III95d9bfd2010-09-14 14:02:36 -0400471 revertVoiceSearchMode();
Leon Scroggins58d56c62010-01-28 15:12:40 -0500472 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700473
474 // We've started to load a new page. If there was a pending message
475 // to save a screenshot then we will now take the new page and save
476 // an incorrect screenshot. Therefore, remove any pending thumbnail
477 // messages from the queue.
478 mActivity.removeMessages(BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL,
479 view);
480
481 // If we start a touch icon load and then load a new page, we don't
482 // want to cancel the current touch icon loader. But, we do want to
483 // create a new one when the touch icon url is known.
484 if (mTouchIconLoader != null) {
485 mTouchIconLoader.mTab = null;
486 mTouchIconLoader = null;
487 }
488
489 // reset the error console
490 if (mErrorConsole != null) {
491 mErrorConsole.clearErrorMessages();
492 if (mActivity.shouldShowErrorConsole()) {
493 mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
494 }
495 }
496
497 // update the bookmark database for favicon
498 if (favicon != null) {
499 BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
Patrick Scottcc949122010-03-17 16:06:30 -0400500 .getContentResolver(), null, url, favicon);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700501 }
502
503 // reset sync timer to avoid sync starts during loading a page
504 CookieSyncManager.getInstance().resetSync();
505
506 if (!mActivity.isNetworkUp()) {
507 view.setNetworkAvailable(false);
508 }
509
510 // finally update the UI in the activity if it is in the foreground
511 if (mInForeground) {
512 mActivity.onPageStarted(view, url, favicon);
513 }
514 }
515
516 @Override
517 public void onPageFinished(WebView view, String url) {
Kristian Monsen4dce3bf2010-02-02 13:37:09 +0000518 LogTag.logPageFinishedLoading(
519 url, SystemClock.uptimeMillis() - mLoadStartTime);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700520 mInLoad = false;
521
522 if (mInForeground && !mActivity.didUserStopLoading()
523 || !mInForeground) {
524 // Only update the bookmark screenshot if the user did not
525 // cancel the load early.
526 mActivity.postMessage(
527 BrowserActivity.UPDATE_BOOKMARK_THUMBNAIL, 0, 0, view,
528 500);
529 }
530
531 // finally update the UI in the activity if it is in the foreground
532 if (mInForeground) {
533 mActivity.onPageFinished(view, url);
534 }
535 }
536
537 // return true if want to hijack the url to let another app to handle it
538 @Override
539 public boolean shouldOverrideUrlLoading(WebView view, String url) {
540 if (mInForeground) {
541 return mActivity.shouldOverrideUrlLoading(view, url);
542 } else {
543 return false;
544 }
545 }
546
547 /**
548 * Updates the lock icon. This method is called when we discover another
549 * resource to be loaded for this page (for example, javascript). While
550 * we update the icon type, we do not update the lock icon itself until
551 * we are done loading, it is slightly more secure this way.
552 */
553 @Override
554 public void onLoadResource(WebView view, String url) {
555 if (url != null && url.length() > 0) {
556 // It is only if the page claims to be secure that we may have
557 // to update the lock:
558 if (mLockIconType == BrowserActivity.LOCK_ICON_SECURE) {
559 // If NOT a 'safe' url, change the lock to mixed content!
560 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url)
561 || URLUtil.isAboutUrl(url))) {
562 mLockIconType = BrowserActivity.LOCK_ICON_MIXED;
563 }
564 }
565 }
566 }
567
568 /**
Grace Kloba22ac16e2009-10-07 18:00:23 -0700569 * Show a dialog informing the user of the network error reported by
570 * WebCore if it is in the foreground.
571 */
572 @Override
573 public void onReceivedError(WebView view, int errorCode,
574 String description, String failingUrl) {
575 if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
576 errorCode != WebViewClient.ERROR_CONNECT &&
577 errorCode != WebViewClient.ERROR_BAD_URL &&
578 errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
579 errorCode != WebViewClient.ERROR_FILE) {
580 queueError(errorCode, description);
581 }
582 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
583 + " " + description);
584
585 // We need to reset the title after an error if it is in foreground.
586 if (mInForeground) {
587 mActivity.resetTitleAndRevertLockIcon();
588 }
589 }
590
591 /**
592 * Check with the user if it is ok to resend POST data as the page they
593 * are trying to navigate to is the result of a POST.
594 */
595 @Override
596 public void onFormResubmission(WebView view, final Message dontResend,
597 final Message resend) {
598 if (!mInForeground) {
599 dontResend.sendToTarget();
600 return;
601 }
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500602 if (mDontResend != null) {
603 Log.w(LOGTAG, "onFormResubmission should not be called again "
604 + "while dialog is still up");
605 dontResend.sendToTarget();
606 return;
607 }
608 mDontResend = dontResend;
609 mResend = resend;
Grace Kloba22ac16e2009-10-07 18:00:23 -0700610 new AlertDialog.Builder(mActivity).setTitle(
611 R.string.browserFrameFormResubmitLabel).setMessage(
612 R.string.browserFrameFormResubmitMessage)
613 .setPositiveButton(R.string.ok,
614 new DialogInterface.OnClickListener() {
615 public void onClick(DialogInterface dialog,
616 int which) {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500617 if (mResend != null) {
618 mResend.sendToTarget();
619 mResend = null;
620 mDontResend = null;
621 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700622 }
623 }).setNegativeButton(R.string.cancel,
624 new DialogInterface.OnClickListener() {
625 public void onClick(DialogInterface dialog,
626 int which) {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500627 if (mDontResend != null) {
628 mDontResend.sendToTarget();
629 mResend = null;
630 mDontResend = null;
631 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700632 }
633 }).setOnCancelListener(new OnCancelListener() {
634 public void onCancel(DialogInterface dialog) {
Leon Scroggins4a64a8a2010-03-02 17:57:40 -0500635 if (mDontResend != null) {
636 mDontResend.sendToTarget();
637 mResend = null;
638 mDontResend = null;
639 }
Grace Kloba22ac16e2009-10-07 18:00:23 -0700640 }
641 }).show();
642 }
643
644 /**
645 * Insert the url into the visited history database.
646 * @param url The url to be inserted.
647 * @param isReload True if this url is being reloaded.
648 * FIXME: Not sure what to do when reloading the page.
649 */
650 @Override
651 public void doUpdateVisitedHistory(WebView view, String url,
652 boolean isReload) {
653 if (url.regionMatches(true, 0, "about:", 0, 6)) {
654 return;
655 }
656 // remove "client" before updating it to the history so that it wont
657 // show up in the auto-complete list.
658 int index = url.indexOf("client=ms-");
659 if (index > 0 && url.contains(".google.")) {
660 int end = url.indexOf('&', index);
661 if (end > 0) {
662 url = url.substring(0, index)
663 .concat(url.substring(end + 1));
664 } else {
665 // the url.charAt(index-1) should be either '?' or '&'
666 url = url.substring(0, index-1);
667 }
668 }
Leon Scroggins8d06e362010-03-24 14:45:57 -0400669 final ContentResolver cr = mActivity.getContentResolver();
670 final String newUrl = url;
671 new AsyncTask<Void, Void, Void>() {
672 protected Void doInBackground(Void... unused) {
673 Browser.updateVisitedHistory(cr, newUrl, true);
674 return null;
675 }
676 }.execute();
Grace Kloba22ac16e2009-10-07 18:00:23 -0700677 WebIconDatabase.getInstance().retainIconForPageUrl(url);
678 }
679
680 /**
681 * Displays SSL error(s) dialog to the user.
682 */
683 @Override
684 public void onReceivedSslError(final WebView view,
685 final SslErrorHandler handler, final SslError error) {
686 if (!mInForeground) {
687 handler.cancel();
688 return;
689 }
690 if (BrowserSettings.getInstance().showSecurityWarnings()) {
691 final LayoutInflater factory =
692 LayoutInflater.from(mActivity);
693 final View warningsView =
694 factory.inflate(R.layout.ssl_warnings, null);
695 final LinearLayout placeholder =
696 (LinearLayout)warningsView.findViewById(R.id.placeholder);
697
698 if (error.hasError(SslError.SSL_UNTRUSTED)) {
699 LinearLayout ll = (LinearLayout)factory
700 .inflate(R.layout.ssl_warning, null);
701 ((TextView)ll.findViewById(R.id.warning))
702 .setText(R.string.ssl_untrusted);
703 placeholder.addView(ll);
704 }
705
706 if (error.hasError(SslError.SSL_IDMISMATCH)) {
707 LinearLayout ll = (LinearLayout)factory
708 .inflate(R.layout.ssl_warning, null);
709 ((TextView)ll.findViewById(R.id.warning))
710 .setText(R.string.ssl_mismatch);
711 placeholder.addView(ll);
712 }
713
714 if (error.hasError(SslError.SSL_EXPIRED)) {
715 LinearLayout ll = (LinearLayout)factory
716 .inflate(R.layout.ssl_warning, null);
717 ((TextView)ll.findViewById(R.id.warning))
718 .setText(R.string.ssl_expired);
719 placeholder.addView(ll);
720 }
721
722 if (error.hasError(SslError.SSL_NOTYETVALID)) {
723 LinearLayout ll = (LinearLayout)factory
724 .inflate(R.layout.ssl_warning, null);
725 ((TextView)ll.findViewById(R.id.warning))
726 .setText(R.string.ssl_not_yet_valid);
727 placeholder.addView(ll);
728 }
729
730 new AlertDialog.Builder(mActivity).setTitle(
731 R.string.security_warning).setIcon(
732 android.R.drawable.ic_dialog_alert).setView(
733 warningsView).setPositiveButton(R.string.ssl_continue,
734 new DialogInterface.OnClickListener() {
735 public void onClick(DialogInterface dialog,
736 int whichButton) {
737 handler.proceed();
738 }
739 }).setNeutralButton(R.string.view_certificate,
740 new DialogInterface.OnClickListener() {
741 public void onClick(DialogInterface dialog,
742 int whichButton) {
743 mActivity.showSSLCertificateOnError(view,
744 handler, error);
745 }
746 }).setNegativeButton(R.string.cancel,
747 new DialogInterface.OnClickListener() {
748 public void onClick(DialogInterface dialog,
749 int whichButton) {
750 handler.cancel();
751 mActivity.resetTitleAndRevertLockIcon();
752 }
753 }).setOnCancelListener(
754 new DialogInterface.OnCancelListener() {
755 public void onCancel(DialogInterface dialog) {
756 handler.cancel();
757 mActivity.resetTitleAndRevertLockIcon();
758 }
759 }).show();
760 } else {
761 handler.proceed();
762 }
763 }
764
765 /**
766 * Handles an HTTP authentication request.
767 *
768 * @param handler The authentication handler
769 * @param host The host
770 * @param realm The realm
771 */
772 @Override
773 public void onReceivedHttpAuthRequest(WebView view,
774 final HttpAuthHandler handler, final String host,
775 final String realm) {
776 String username = null;
777 String password = null;
778
779 boolean reuseHttpAuthUsernamePassword = handler
780 .useHttpAuthUsernamePassword();
781
Steve Block95a53b22010-03-25 17:24:58 +0000782 if (reuseHttpAuthUsernamePassword && view != null) {
783 String[] credentials = view.getHttpAuthUsernamePassword(
Grace Kloba22ac16e2009-10-07 18:00:23 -0700784 host, realm);
785 if (credentials != null && credentials.length == 2) {
786 username = credentials[0];
787 password = credentials[1];
788 }
789 }
790
791 if (username != null && password != null) {
792 handler.proceed(username, password);
793 } else {
794 if (mInForeground) {
795 mActivity.showHttpAuthentication(handler, host, realm,
796 null, null, null, 0);
797 } else {
798 handler.cancel();
799 }
800 }
801 }
802
803 @Override
804 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
805 if (!mInForeground) {
806 return false;
807 }
808 if (mActivity.isMenuDown()) {
809 // only check shortcut key when MENU is held
810 return mActivity.getWindow().isShortcutKey(event.getKeyCode(),
811 event);
812 } else {
813 return false;
814 }
815 }
816
817 @Override
818 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
Cary Clark1f10cbf2010-03-22 11:45:23 -0400819 if (!mInForeground || mActivity.mActivityInPause) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700820 return;
821 }
822 if (event.isDown()) {
823 mActivity.onKeyDown(event.getKeyCode(), event);
824 } else {
825 mActivity.onKeyUp(event.getKeyCode(), event);
826 }
827 }
828 };
829
830 // -------------------------------------------------------------------------
831 // WebChromeClient implementation for the main WebView
832 // -------------------------------------------------------------------------
833
834 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
835 // Helper method to create a new tab or sub window.
836 private void createWindow(final boolean dialog, final Message msg) {
837 WebView.WebViewTransport transport =
838 (WebView.WebViewTransport) msg.obj;
839 if (dialog) {
840 createSubWindow();
841 mActivity.attachSubWindow(Tab.this);
842 transport.setWebView(mSubView);
843 } else {
844 final Tab newTab = mActivity.openTabAndShow(
845 BrowserActivity.EMPTY_URL_DATA, false, null);
846 if (newTab != Tab.this) {
847 Tab.this.addChildTab(newTab);
848 }
849 transport.setWebView(newTab.getWebView());
850 }
851 msg.sendToTarget();
852 }
853
854 @Override
855 public boolean onCreateWindow(WebView view, final boolean dialog,
856 final boolean userGesture, final Message resultMsg) {
857 // only allow new window or sub window for the foreground case
858 if (!mInForeground) {
859 return false;
860 }
861 // Short-circuit if we can't create any more tabs or sub windows.
862 if (dialog && mSubView != null) {
863 new AlertDialog.Builder(mActivity)
864 .setTitle(R.string.too_many_subwindows_dialog_title)
865 .setIcon(android.R.drawable.ic_dialog_alert)
866 .setMessage(R.string.too_many_subwindows_dialog_message)
867 .setPositiveButton(R.string.ok, null)
868 .show();
869 return false;
870 } else if (!mActivity.getTabControl().canCreateNewTab()) {
871 new AlertDialog.Builder(mActivity)
872 .setTitle(R.string.too_many_windows_dialog_title)
873 .setIcon(android.R.drawable.ic_dialog_alert)
874 .setMessage(R.string.too_many_windows_dialog_message)
875 .setPositiveButton(R.string.ok, null)
876 .show();
877 return false;
878 }
879
880 // Short-circuit if this was a user gesture.
881 if (userGesture) {
882 createWindow(dialog, resultMsg);
883 return true;
884 }
885
886 // Allow the popup and create the appropriate window.
887 final AlertDialog.OnClickListener allowListener =
888 new AlertDialog.OnClickListener() {
889 public void onClick(DialogInterface d,
890 int which) {
891 createWindow(dialog, resultMsg);
892 }
893 };
894
895 // Block the popup by returning a null WebView.
896 final AlertDialog.OnClickListener blockListener =
897 new AlertDialog.OnClickListener() {
898 public void onClick(DialogInterface d, int which) {
899 resultMsg.sendToTarget();
900 }
901 };
902
903 // Build a confirmation dialog to display to the user.
904 final AlertDialog d =
905 new AlertDialog.Builder(mActivity)
906 .setTitle(R.string.attention)
907 .setIcon(android.R.drawable.ic_dialog_alert)
908 .setMessage(R.string.popup_window_attempt)
909 .setPositiveButton(R.string.allow, allowListener)
910 .setNegativeButton(R.string.block, blockListener)
911 .setCancelable(false)
912 .create();
913
914 // Show the confirmation dialog.
915 d.show();
916 return true;
917 }
918
919 @Override
Patrick Scotteb5061b2009-11-18 15:00:30 -0500920 public void onRequestFocus(WebView view) {
921 if (!mInForeground) {
922 mActivity.switchToTab(mActivity.getTabControl().getTabIndex(
923 Tab.this));
924 }
925 }
926
927 @Override
Grace Kloba22ac16e2009-10-07 18:00:23 -0700928 public void onCloseWindow(WebView window) {
929 if (mParentTab != null) {
930 // JavaScript can only close popup window.
931 if (mInForeground) {
932 mActivity.switchToTab(mActivity.getTabControl()
933 .getTabIndex(mParentTab));
934 }
935 mActivity.closeTab(Tab.this);
936 }
937 }
938
939 @Override
940 public void onProgressChanged(WebView view, int newProgress) {
941 if (newProgress == 100) {
942 // sync cookies and cache promptly here.
943 CookieSyncManager.getInstance().sync();
944 }
945 if (mInForeground) {
946 mActivity.onProgressChanged(view, newProgress);
947 }
948 }
949
950 @Override
Leon Scroggins21d9b902010-03-11 09:33:11 -0500951 public void onReceivedTitle(WebView view, final String title) {
952 final String pageUrl = view.getUrl();
Grace Kloba22ac16e2009-10-07 18:00:23 -0700953 if (mInForeground) {
954 // here, if url is null, we want to reset the title
Leon Scroggins21d9b902010-03-11 09:33:11 -0500955 mActivity.setUrlTitle(pageUrl, title);
Grace Kloba22ac16e2009-10-07 18:00:23 -0700956 }
Leon Scroggins21d9b902010-03-11 09:33:11 -0500957 if (pageUrl == null || pageUrl.length()
958 >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
Grace Kloba22ac16e2009-10-07 18:00:23 -0700959 return;
960 }
Leon Scroggins21d9b902010-03-11 09:33:11 -0500961 new AsyncTask<Void, Void, Void>() {
962 protected Void doInBackground(Void... unused) {
963 // See if we can find the current url in our history
964 // database and add the new title to it.
965 String url = pageUrl;
966 if (url.startsWith("http://www.")) {
967 url = url.substring(11);
968 } else if (url.startsWith("http://")) {
969 url = url.substring(4);
970 }
971 Cursor c = null;
972 try {
973 final ContentResolver cr
974 = mActivity.getContentResolver();
975 url = "%" + url;
976 String [] selArgs = new String[] { url };
977 String where = Browser.BookmarkColumns.URL
978 + " LIKE ? AND "
979 + Browser.BookmarkColumns.BOOKMARK + " = 0";
980 c = cr.query(Browser.BOOKMARKS_URI, new String[]
981 { Browser.BookmarkColumns._ID }, where, selArgs,
982 null);
983 if (c.moveToFirst()) {
984 // Current implementation of database only has one
985 // entry per url.
986 ContentValues map = new ContentValues();
987 map.put(Browser.BookmarkColumns.TITLE, title);
988 String[] projection = new String[]
989 { Integer.valueOf(c.getInt(0)).toString() };
990 cr.update(Browser.BOOKMARKS_URI, map, "_id = ?",
991 projection);
992 }
993 } catch (IllegalStateException e) {
994 Log.e(LOGTAG, "Tab onReceived title", e);
995 } catch (SQLiteException ex) {
996 Log.e(LOGTAG,
997 "onReceivedTitle() caught SQLiteException: ",
998 ex);
999 } finally {
1000 if (c != null) c.close();
1001 }
1002 return null;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001003 }
Leon Scroggins21d9b902010-03-11 09:33:11 -05001004 }.execute();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001005 }
1006
1007 @Override
1008 public void onReceivedIcon(WebView view, Bitmap icon) {
1009 if (icon != null) {
1010 BrowserBookmarksAdapter.updateBookmarkFavicon(mActivity
1011 .getContentResolver(), view.getOriginalUrl(), view
1012 .getUrl(), icon);
1013 }
1014 if (mInForeground) {
1015 mActivity.setFavicon(icon);
1016 }
1017 }
1018
1019 @Override
1020 public void onReceivedTouchIconUrl(WebView view, String url,
1021 boolean precomposed) {
1022 final ContentResolver cr = mActivity.getContentResolver();
Leon Scrogginsc8393d92010-04-23 14:58:16 -04001023 // Let precomposed icons take precedence over non-composed
1024 // icons.
1025 if (precomposed && mTouchIconLoader != null) {
1026 mTouchIconLoader.cancel(false);
1027 mTouchIconLoader = null;
1028 }
1029 // Have only one async task at a time.
1030 if (mTouchIconLoader == null) {
1031 mTouchIconLoader = new DownloadTouchIcon(Tab.this, cr, view);
1032 mTouchIconLoader.execute(url);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001033 }
1034 }
1035
1036 @Override
1037 public void onShowCustomView(View view,
1038 WebChromeClient.CustomViewCallback callback) {
1039 if (mInForeground) mActivity.onShowCustomView(view, callback);
1040 }
1041
1042 @Override
1043 public void onHideCustomView() {
1044 if (mInForeground) mActivity.onHideCustomView();
1045 }
1046
1047 /**
1048 * The origin has exceeded its database quota.
1049 * @param url the URL that exceeded the quota
1050 * @param databaseIdentifier the identifier of the database on which the
1051 * transaction that caused the quota overflow was run
1052 * @param currentQuota the current quota for the origin.
1053 * @param estimatedSize the estimated size of the database.
1054 * @param totalUsedQuota is the sum of all origins' quota.
1055 * @param quotaUpdater The callback to run when a decision to allow or
1056 * deny quota has been made. Don't forget to call this!
1057 */
1058 @Override
1059 public void onExceededDatabaseQuota(String url,
1060 String databaseIdentifier, long currentQuota, long estimatedSize,
1061 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
1062 BrowserSettings.getInstance().getWebStorageSizeManager()
1063 .onExceededDatabaseQuota(url, databaseIdentifier,
1064 currentQuota, estimatedSize, totalUsedQuota,
1065 quotaUpdater);
1066 }
1067
1068 /**
1069 * The Application Cache has exceeded its max size.
1070 * @param spaceNeeded is the amount of disk space that would be needed
1071 * in order for the last appcache operation to succeed.
1072 * @param totalUsedQuota is the sum of all origins' quota.
1073 * @param quotaUpdater A callback to inform the WebCore thread that a
1074 * new app cache size is available. This callback must always
1075 * be executed at some point to ensure that the sleeping
1076 * WebCore thread is woken up.
1077 */
1078 @Override
1079 public void onReachedMaxAppCacheSize(long spaceNeeded,
1080 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
1081 BrowserSettings.getInstance().getWebStorageSizeManager()
1082 .onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
1083 quotaUpdater);
1084 }
1085
1086 /**
1087 * Instructs the browser to show a prompt to ask the user to set the
1088 * Geolocation permission state for the specified origin.
1089 * @param origin The origin for which Geolocation permissions are
1090 * requested.
1091 * @param callback The callback to call once the user has set the
1092 * Geolocation permission state.
1093 */
1094 @Override
1095 public void onGeolocationPermissionsShowPrompt(String origin,
1096 GeolocationPermissions.Callback callback) {
1097 if (mInForeground) {
Grace Kloba50c241e2010-04-20 11:07:50 -07001098 getGeolocationPermissionsPrompt().show(origin, callback);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001099 }
1100 }
1101
1102 /**
1103 * Instructs the browser to hide the Geolocation permissions prompt.
1104 */
1105 @Override
1106 public void onGeolocationPermissionsHidePrompt() {
Grace Kloba50c241e2010-04-20 11:07:50 -07001107 if (mInForeground && mGeolocationPermissionsPrompt != null) {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001108 mGeolocationPermissionsPrompt.hide();
1109 }
1110 }
1111
Ben Murdoch65acc352009-11-19 18:16:04 +00001112 /* Adds a JavaScript error message to the system log and if the JS
1113 * console is enabled in the about:debug options, to that console
1114 * also.
Ben Murdochc42addf2010-01-28 15:19:59 +00001115 * @param consoleMessage the message object.
Grace Kloba22ac16e2009-10-07 18:00:23 -07001116 */
1117 @Override
Ben Murdochc42addf2010-01-28 15:19:59 +00001118 public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Grace Kloba22ac16e2009-10-07 18:00:23 -07001119 if (mInForeground) {
1120 // call getErrorConsole(true) so it will create one if needed
1121 ErrorConsoleView errorConsole = getErrorConsole(true);
Ben Murdochc42addf2010-01-28 15:19:59 +00001122 errorConsole.addErrorMessage(consoleMessage);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001123 if (mActivity.shouldShowErrorConsole()
1124 && errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
1125 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1126 }
1127 }
Ben Murdochc42addf2010-01-28 15:19:59 +00001128
1129 String message = "Console: " + consoleMessage.message() + " "
1130 + consoleMessage.sourceId() + ":"
1131 + consoleMessage.lineNumber();
1132
1133 switch (consoleMessage.messageLevel()) {
1134 case TIP:
1135 Log.v(CONSOLE_LOGTAG, message);
1136 break;
1137 case LOG:
1138 Log.i(CONSOLE_LOGTAG, message);
1139 break;
1140 case WARNING:
1141 Log.w(CONSOLE_LOGTAG, message);
1142 break;
1143 case ERROR:
1144 Log.e(CONSOLE_LOGTAG, message);
1145 break;
1146 case DEBUG:
1147 Log.d(CONSOLE_LOGTAG, message);
1148 break;
1149 }
1150
1151 return true;
Grace Kloba22ac16e2009-10-07 18:00:23 -07001152 }
1153
1154 /**
1155 * Ask the browser for an icon to represent a <video> element.
1156 * This icon will be used if the Web page did not specify a poster attribute.
1157 * @return Bitmap The icon or null if no such icon is available.
1158 */
1159 @Override
1160 public Bitmap getDefaultVideoPoster() {
1161 if (mInForeground) {
1162 return mActivity.getDefaultVideoPoster();
1163 }
1164 return null;
1165 }
1166
1167 /**
1168 * Ask the host application for a custom progress view to show while
1169 * a <video> is loading.
1170 * @return View The progress view.
1171 */
1172 @Override
1173 public View getVideoLoadingProgressView() {
1174 if (mInForeground) {
1175 return mActivity.getVideoLoadingProgressView();
1176 }
1177 return null;
1178 }
1179
1180 @Override
1181 public void openFileChooser(ValueCallback<Uri> uploadMsg) {
1182 if (mInForeground) {
1183 mActivity.openFileChooser(uploadMsg);
1184 } else {
1185 uploadMsg.onReceiveValue(null);
1186 }
1187 }
1188
1189 /**
1190 * Deliver a list of already-visited URLs
1191 */
1192 @Override
1193 public void getVisitedHistory(final ValueCallback<String[]> callback) {
1194 AsyncTask<Void, Void, String[]> task = new AsyncTask<Void, Void, String[]>() {
1195 public String[] doInBackground(Void... unused) {
1196 return Browser.getVisitedHistory(mActivity
1197 .getContentResolver());
1198 }
1199 public void onPostExecute(String[] result) {
1200 callback.onReceiveValue(result);
1201 };
1202 };
1203 task.execute();
1204 };
1205 };
1206
1207 // -------------------------------------------------------------------------
1208 // WebViewClient implementation for the sub window
1209 // -------------------------------------------------------------------------
1210
1211 // Subclass of WebViewClient used in subwindows to notify the main
1212 // WebViewClient of certain WebView activities.
1213 private static class SubWindowClient extends WebViewClient {
1214 // The main WebViewClient.
1215 private final WebViewClient mClient;
1216
1217 SubWindowClient(WebViewClient client) {
1218 mClient = client;
1219 }
1220 @Override
1221 public void doUpdateVisitedHistory(WebView view, String url,
1222 boolean isReload) {
1223 mClient.doUpdateVisitedHistory(view, url, isReload);
1224 }
1225 @Override
1226 public boolean shouldOverrideUrlLoading(WebView view, String url) {
1227 return mClient.shouldOverrideUrlLoading(view, url);
1228 }
1229 @Override
1230 public void onReceivedSslError(WebView view, SslErrorHandler handler,
1231 SslError error) {
1232 mClient.onReceivedSslError(view, handler, error);
1233 }
1234 @Override
1235 public void onReceivedHttpAuthRequest(WebView view,
1236 HttpAuthHandler handler, String host, String realm) {
1237 mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
1238 }
1239 @Override
1240 public void onFormResubmission(WebView view, Message dontResend,
1241 Message resend) {
1242 mClient.onFormResubmission(view, dontResend, resend);
1243 }
1244 @Override
1245 public void onReceivedError(WebView view, int errorCode,
1246 String description, String failingUrl) {
1247 mClient.onReceivedError(view, errorCode, description, failingUrl);
1248 }
1249 @Override
1250 public boolean shouldOverrideKeyEvent(WebView view,
1251 android.view.KeyEvent event) {
1252 return mClient.shouldOverrideKeyEvent(view, event);
1253 }
1254 @Override
1255 public void onUnhandledKeyEvent(WebView view,
1256 android.view.KeyEvent event) {
1257 mClient.onUnhandledKeyEvent(view, event);
1258 }
1259 }
1260
1261 // -------------------------------------------------------------------------
1262 // WebChromeClient implementation for the sub window
1263 // -------------------------------------------------------------------------
1264
1265 private class SubWindowChromeClient extends WebChromeClient {
1266 // The main WebChromeClient.
1267 private final WebChromeClient mClient;
1268
1269 SubWindowChromeClient(WebChromeClient client) {
1270 mClient = client;
1271 }
1272 @Override
1273 public void onProgressChanged(WebView view, int newProgress) {
1274 mClient.onProgressChanged(view, newProgress);
1275 }
1276 @Override
1277 public boolean onCreateWindow(WebView view, boolean dialog,
1278 boolean userGesture, android.os.Message resultMsg) {
1279 return mClient.onCreateWindow(view, dialog, userGesture, resultMsg);
1280 }
1281 @Override
1282 public void onCloseWindow(WebView window) {
1283 if (window != mSubView) {
1284 Log.e(LOGTAG, "Can't close the window");
1285 }
1286 mActivity.dismissSubWindow(Tab.this);
1287 }
1288 }
1289
1290 // -------------------------------------------------------------------------
1291
1292 // Construct a new tab
1293 Tab(BrowserActivity activity, WebView w, boolean closeOnExit, String appId,
1294 String url) {
1295 mActivity = activity;
1296 mCloseOnExit = closeOnExit;
1297 mAppId = appId;
1298 mOriginalUrl = url;
1299 mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
1300 mPrevLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
1301 mInLoad = false;
1302 mInForeground = false;
1303
1304 mInflateService = LayoutInflater.from(activity);
1305
1306 // The tab consists of a container view, which contains the main
1307 // WebView, as well as any other UI elements associated with the tab.
1308 mContainer = mInflateService.inflate(R.layout.tab, null);
1309
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001310 mDownloadListener = new DownloadListener() {
1311 public void onDownloadStart(String url, String userAgent,
1312 String contentDisposition, String mimetype,
1313 long contentLength) {
1314 mActivity.onDownloadStart(url, userAgent, contentDisposition,
1315 mimetype, contentLength);
1316 if (mMainView.copyBackForwardList().getSize() == 0) {
1317 // This Tab was opened for the sole purpose of downloading a
1318 // file. Remove it.
1319 if (mActivity.getTabControl().getCurrentWebView()
1320 == mMainView) {
1321 // In this case, the Tab is still on top.
1322 mActivity.goBackOnePageOrQuit();
1323 } else {
1324 // In this case, it is not.
1325 mActivity.closeTab(Tab.this);
1326 }
1327 }
1328 }
1329 };
Leon Scroggins0c75a8e2010-03-03 16:40:58 -05001330 mWebBackForwardListClient = new WebBackForwardListClient() {
1331 @Override
1332 public void onNewHistoryItem(WebHistoryItem item) {
1333 if (isInVoiceSearchMode()) {
1334 item.setCustomData(mVoiceSearchData.mVoiceSearchIntent);
1335 }
1336 }
1337 @Override
1338 public void onIndexChanged(WebHistoryItem item, int index) {
1339 Object data = item.getCustomData();
1340 if (data != null && data instanceof Intent) {
1341 activateVoiceSearchMode((Intent) data);
1342 }
1343 }
1344 };
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001345
Grace Kloba22ac16e2009-10-07 18:00:23 -07001346 setWebView(w);
1347 }
1348
1349 /**
1350 * Sets the WebView for this tab, correctly removing the old WebView from
1351 * the container view.
1352 */
1353 void setWebView(WebView w) {
1354 if (mMainView == w) {
1355 return;
1356 }
1357 // If the WebView is changing, the page will be reloaded, so any ongoing
1358 // Geolocation permission requests are void.
Grace Kloba50c241e2010-04-20 11:07:50 -07001359 if (mGeolocationPermissionsPrompt != null) {
1360 mGeolocationPermissionsPrompt.hide();
1361 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001362
1363 // Just remove the old one.
1364 FrameLayout wrapper =
1365 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
1366 wrapper.removeView(mMainView);
1367
1368 // set the new one
1369 mMainView = w;
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001370 // attach the WebViewClient, WebChromeClient and DownloadListener
Grace Kloba22ac16e2009-10-07 18:00:23 -07001371 if (mMainView != null) {
1372 mMainView.setWebViewClient(mWebViewClient);
1373 mMainView.setWebChromeClient(mWebChromeClient);
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001374 // Attach DownloadManager so that downloads can start in an active
1375 // or a non-active window. This can happen when going to a site that
1376 // does a redirect after a period of time. The user could have
1377 // switched to another tab while waiting for the download to start.
1378 mMainView.setDownloadListener(mDownloadListener);
Leon Scroggins0c75a8e2010-03-03 16:40:58 -05001379 mMainView.setWebBackForwardListClient(mWebBackForwardListClient);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001380 }
1381 }
1382
1383 /**
1384 * Destroy the tab's main WebView and subWindow if any
1385 */
1386 void destroy() {
1387 if (mMainView != null) {
1388 dismissSubWindow();
1389 BrowserSettings.getInstance().deleteObserver(mMainView.getSettings());
1390 // save the WebView to call destroy() after detach it from the tab
1391 WebView webView = mMainView;
1392 setWebView(null);
1393 webView.destroy();
1394 }
1395 }
1396
1397 /**
1398 * Remove the tab from the parent
1399 */
1400 void removeFromTree() {
1401 // detach the children
1402 if (mChildTabs != null) {
1403 for(Tab t : mChildTabs) {
1404 t.setParentTab(null);
1405 }
1406 }
1407 // remove itself from the parent list
1408 if (mParentTab != null) {
1409 mParentTab.mChildTabs.remove(this);
1410 }
1411 }
1412
1413 /**
1414 * Create a new subwindow unless a subwindow already exists.
1415 * @return True if a new subwindow was created. False if one already exists.
1416 */
1417 boolean createSubWindow() {
1418 if (mSubView == null) {
1419 mSubViewContainer = mInflateService.inflate(
1420 R.layout.browser_subwindow, null);
1421 mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview);
Grace Kloba80380ed2010-03-19 17:44:21 -07001422 mSubView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001423 // use trackball directly
1424 mSubView.setMapTrackballToArrowKeys(false);
Grace Kloba140b33a2010-03-19 18:40:09 -07001425 // Enable the built-in zoom
1426 mSubView.getSettings().setBuiltInZoomControls(true);
Grace Kloba22ac16e2009-10-07 18:00:23 -07001427 mSubView.setWebViewClient(new SubWindowClient(mWebViewClient));
1428 mSubView.setWebChromeClient(new SubWindowChromeClient(
1429 mWebChromeClient));
Leon Scrogginsdcc5eeb2010-02-23 17:26:37 -05001430 // Set a different DownloadListener for the mSubView, since it will
1431 // just need to dismiss the mSubView, rather than close the Tab
1432 mSubView.setDownloadListener(new DownloadListener() {
1433 public void onDownloadStart(String url, String userAgent,
1434 String contentDisposition, String mimetype,
1435 long contentLength) {
1436 mActivity.onDownloadStart(url, userAgent,
1437 contentDisposition, mimetype, contentLength);
1438 if (mSubView.copyBackForwardList().getSize() == 0) {
1439 // This subwindow was opened for the sole purpose of
1440 // downloading a file. Remove it.
1441 dismissSubWindow();
1442 }
1443 }
1444 });
Grace Kloba22ac16e2009-10-07 18:00:23 -07001445 mSubView.setOnCreateContextMenuListener(mActivity);
1446 final BrowserSettings s = BrowserSettings.getInstance();
1447 s.addObserver(mSubView.getSettings()).update(s, null);
1448 final ImageButton cancel = (ImageButton) mSubViewContainer
1449 .findViewById(R.id.subwindow_close);
1450 cancel.setOnClickListener(new OnClickListener() {
1451 public void onClick(View v) {
1452 mSubView.getWebChromeClient().onCloseWindow(mSubView);
1453 }
1454 });
1455 return true;
1456 }
1457 return false;
1458 }
1459
1460 /**
1461 * Dismiss the subWindow for the tab.
1462 */
1463 void dismissSubWindow() {
1464 if (mSubView != null) {
1465 BrowserSettings.getInstance().deleteObserver(
1466 mSubView.getSettings());
1467 mSubView.destroy();
1468 mSubView = null;
1469 mSubViewContainer = null;
1470 }
1471 }
1472
1473 /**
1474 * Attach the sub window to the content view.
1475 */
1476 void attachSubWindow(ViewGroup content) {
1477 if (mSubView != null) {
1478 content.addView(mSubViewContainer,
1479 BrowserActivity.COVER_SCREEN_PARAMS);
1480 }
1481 }
1482
1483 /**
1484 * Remove the sub window from the content view.
1485 */
1486 void removeSubWindow(ViewGroup content) {
1487 if (mSubView != null) {
1488 content.removeView(mSubViewContainer);
1489 }
1490 }
1491
1492 /**
1493 * This method attaches both the WebView and any sub window to the
1494 * given content view.
1495 */
1496 void attachTabToContentView(ViewGroup content) {
1497 if (mMainView == null) {
1498 return;
1499 }
1500
1501 // Attach the WebView to the container and then attach the
1502 // container to the content view.
1503 FrameLayout wrapper =
1504 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
Leon Scroggins IIIb00cf362010-03-30 11:24:14 -04001505 ViewGroup parent = (ViewGroup) mMainView.getParent();
1506 if (parent != wrapper) {
1507 if (parent != null) {
1508 Log.w(LOGTAG, "mMainView already has a parent in"
1509 + " attachTabToContentView!");
1510 parent.removeView(mMainView);
1511 }
1512 wrapper.addView(mMainView);
1513 } else {
1514 Log.w(LOGTAG, "mMainView is already attached to wrapper in"
1515 + " attachTabToContentView!");
1516 }
1517 parent = (ViewGroup) mContainer.getParent();
1518 if (parent != content) {
1519 if (parent != null) {
1520 Log.w(LOGTAG, "mContainer already has a parent in"
1521 + " attachTabToContentView!");
1522 parent.removeView(mContainer);
1523 }
1524 content.addView(mContainer, BrowserActivity.COVER_SCREEN_PARAMS);
1525 } else {
1526 Log.w(LOGTAG, "mContainer is already attached to content in"
1527 + " attachTabToContentView!");
1528 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001529 attachSubWindow(content);
1530 }
1531
1532 /**
1533 * Remove the WebView and any sub window from the given content view.
1534 */
1535 void removeTabFromContentView(ViewGroup content) {
1536 if (mMainView == null) {
1537 return;
1538 }
1539
1540 // Remove the container from the content and then remove the
1541 // WebView from the container. This will trigger a focus change
1542 // needed by WebView.
1543 FrameLayout wrapper =
1544 (FrameLayout) mContainer.findViewById(R.id.webview_wrapper);
1545 wrapper.removeView(mMainView);
1546 content.removeView(mContainer);
1547 removeSubWindow(content);
1548 }
1549
1550 /**
1551 * Set the parent tab of this tab.
1552 */
1553 void setParentTab(Tab parent) {
1554 mParentTab = parent;
1555 // This tab may have been freed due to low memory. If that is the case,
1556 // the parent tab index is already saved. If we are changing that index
1557 // (most likely due to removing the parent tab) we must update the
1558 // parent tab index in the saved Bundle.
1559 if (mSavedState != null) {
1560 if (parent == null) {
1561 mSavedState.remove(PARENTTAB);
1562 } else {
1563 mSavedState.putInt(PARENTTAB, mActivity.getTabControl()
1564 .getTabIndex(parent));
1565 }
1566 }
1567 }
1568
1569 /**
1570 * When a Tab is created through the content of another Tab, then we
1571 * associate the Tabs.
1572 * @param child the Tab that was created from this Tab
1573 */
1574 void addChildTab(Tab child) {
1575 if (mChildTabs == null) {
1576 mChildTabs = new Vector<Tab>();
1577 }
1578 mChildTabs.add(child);
1579 child.setParentTab(this);
1580 }
1581
1582 Vector<Tab> getChildTabs() {
1583 return mChildTabs;
1584 }
1585
1586 void resume() {
1587 if (mMainView != null) {
1588 mMainView.onResume();
1589 if (mSubView != null) {
1590 mSubView.onResume();
1591 }
1592 }
1593 }
1594
1595 void pause() {
1596 if (mMainView != null) {
1597 mMainView.onPause();
1598 if (mSubView != null) {
1599 mSubView.onPause();
1600 }
1601 }
1602 }
1603
1604 void putInForeground() {
1605 mInForeground = true;
1606 resume();
1607 mMainView.setOnCreateContextMenuListener(mActivity);
1608 if (mSubView != null) {
1609 mSubView.setOnCreateContextMenuListener(mActivity);
1610 }
1611 // Show the pending error dialog if the queue is not empty
1612 if (mQueuedErrors != null && mQueuedErrors.size() > 0) {
1613 showError(mQueuedErrors.getFirst());
1614 }
1615 }
1616
1617 void putInBackground() {
1618 mInForeground = false;
1619 pause();
1620 mMainView.setOnCreateContextMenuListener(null);
1621 if (mSubView != null) {
1622 mSubView.setOnCreateContextMenuListener(null);
1623 }
1624 }
1625
1626 /**
1627 * Return the top window of this tab; either the subwindow if it is not
1628 * null or the main window.
1629 * @return The top window of this tab.
1630 */
1631 WebView getTopWindow() {
1632 if (mSubView != null) {
1633 return mSubView;
1634 }
1635 return mMainView;
1636 }
1637
1638 /**
1639 * Return the main window of this tab. Note: if a tab is freed in the
1640 * background, this can return null. It is only guaranteed to be
1641 * non-null for the current tab.
1642 * @return The main WebView of this tab.
1643 */
1644 WebView getWebView() {
1645 return mMainView;
1646 }
1647
1648 /**
1649 * Return the subwindow of this tab or null if there is no subwindow.
1650 * @return The subwindow of this tab or null.
1651 */
1652 WebView getSubWebView() {
1653 return mSubView;
1654 }
1655
1656 /**
1657 * @return The geolocation permissions prompt for this tab.
1658 */
1659 GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
Grace Kloba50c241e2010-04-20 11:07:50 -07001660 if (mGeolocationPermissionsPrompt == null) {
1661 ViewStub stub = (ViewStub) mContainer
1662 .findViewById(R.id.geolocation_permissions_prompt);
1663 mGeolocationPermissionsPrompt = (GeolocationPermissionsPrompt) stub
1664 .inflate();
1665 mGeolocationPermissionsPrompt.init();
1666 }
Grace Kloba22ac16e2009-10-07 18:00:23 -07001667 return mGeolocationPermissionsPrompt;
1668 }
1669
1670 /**
1671 * @return The application id string
1672 */
1673 String getAppId() {
1674 return mAppId;
1675 }
1676
1677 /**
1678 * Set the application id string
1679 * @param id
1680 */
1681 void setAppId(String id) {
1682 mAppId = id;
1683 }
1684
1685 /**
1686 * @return The original url associated with this Tab
1687 */
1688 String getOriginalUrl() {
1689 return mOriginalUrl;
1690 }
1691
1692 /**
1693 * Set the original url associated with this tab
1694 */
1695 void setOriginalUrl(String url) {
1696 mOriginalUrl = url;
1697 }
1698
1699 /**
1700 * Get the url of this tab. Valid after calling populatePickerData, but
1701 * before calling wipePickerData, or if the webview has been destroyed.
1702 * @return The WebView's url or null.
1703 */
1704 String getUrl() {
1705 if (mPickerData != null) {
1706 return mPickerData.mUrl;
1707 }
1708 return null;
1709 }
1710
1711 /**
1712 * Get the title of this tab. Valid after calling populatePickerData, but
1713 * before calling wipePickerData, or if the webview has been destroyed. If
1714 * the url has no title, use the url instead.
1715 * @return The WebView's title (or url) or null.
1716 */
1717 String getTitle() {
1718 if (mPickerData != null) {
1719 return mPickerData.mTitle;
1720 }
1721 return null;
1722 }
1723
1724 /**
1725 * Get the favicon of this tab. Valid after calling populatePickerData, but
1726 * before calling wipePickerData, or if the webview has been destroyed.
1727 * @return The WebView's favicon or null.
1728 */
1729 Bitmap getFavicon() {
1730 if (mPickerData != null) {
1731 return mPickerData.mFavicon;
1732 }
1733 return null;
1734 }
1735
1736 /**
1737 * Return the tab's error console. Creates the console if createIfNEcessary
1738 * is true and we haven't already created the console.
1739 * @param createIfNecessary Flag to indicate if the console should be
1740 * created if it has not been already.
1741 * @return The tab's error console, or null if one has not been created and
1742 * createIfNecessary is false.
1743 */
1744 ErrorConsoleView getErrorConsole(boolean createIfNecessary) {
1745 if (createIfNecessary && mErrorConsole == null) {
1746 mErrorConsole = new ErrorConsoleView(mActivity);
1747 mErrorConsole.setWebView(mMainView);
1748 }
1749 return mErrorConsole;
1750 }
1751
1752 /**
1753 * If this Tab was created through another Tab, then this method returns
1754 * that Tab.
1755 * @return the Tab parent or null
1756 */
1757 public Tab getParentTab() {
1758 return mParentTab;
1759 }
1760
1761 /**
1762 * Return whether this tab should be closed when it is backing out of the
1763 * first page.
1764 * @return TRUE if this tab should be closed when exit.
1765 */
1766 boolean closeOnExit() {
1767 return mCloseOnExit;
1768 }
1769
1770 /**
1771 * Saves the current lock-icon state before resetting the lock icon. If we
1772 * have an error, we may need to roll back to the previous state.
1773 */
1774 void resetLockIcon(String url) {
1775 mPrevLockIconType = mLockIconType;
1776 mLockIconType = BrowserActivity.LOCK_ICON_UNSECURE;
1777 if (URLUtil.isHttpsUrl(url)) {
1778 mLockIconType = BrowserActivity.LOCK_ICON_SECURE;
1779 }
1780 }
1781
1782 /**
1783 * Reverts the lock-icon state to the last saved state, for example, if we
1784 * had an error, and need to cancel the load.
1785 */
1786 void revertLockIcon() {
1787 mLockIconType = mPrevLockIconType;
1788 }
1789
1790 /**
1791 * @return The tab's lock icon type.
1792 */
1793 int getLockIconType() {
1794 return mLockIconType;
1795 }
1796
1797 /**
1798 * @return TRUE if onPageStarted is called while onPageFinished is not
1799 * called yet.
1800 */
1801 boolean inLoad() {
1802 return mInLoad;
1803 }
1804
1805 // force mInLoad to be false. This should only be called before closing the
1806 // tab to ensure BrowserActivity's pauseWebViewTimers() is called correctly.
1807 void clearInLoad() {
1808 mInLoad = false;
1809 }
1810
1811 void populatePickerData() {
1812 if (mMainView == null) {
1813 populatePickerDataFromSavedState();
1814 return;
1815 }
1816
1817 // FIXME: The only place we cared about subwindow was for
1818 // bookmarking (i.e. not when saving state). Was this deliberate?
1819 final WebBackForwardList list = mMainView.copyBackForwardList();
1820 final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
1821 populatePickerData(item);
1822 }
1823
1824 // Populate the picker data using the given history item and the current top
1825 // WebView.
1826 private void populatePickerData(WebHistoryItem item) {
1827 mPickerData = new PickerData();
1828 if (item != null) {
1829 mPickerData.mUrl = item.getUrl();
1830 mPickerData.mTitle = item.getTitle();
1831 mPickerData.mFavicon = item.getFavicon();
1832 if (mPickerData.mTitle == null) {
1833 mPickerData.mTitle = mPickerData.mUrl;
1834 }
1835 }
1836 }
1837
1838 // Create the PickerData and populate it using the saved state of the tab.
1839 void populatePickerDataFromSavedState() {
1840 if (mSavedState == null) {
1841 return;
1842 }
1843 mPickerData = new PickerData();
1844 mPickerData.mUrl = mSavedState.getString(CURRURL);
1845 mPickerData.mTitle = mSavedState.getString(CURRTITLE);
1846 }
1847
1848 void clearPickerData() {
1849 mPickerData = null;
1850 }
1851
1852 /**
1853 * Get the saved state bundle.
1854 * @return
1855 */
1856 Bundle getSavedState() {
1857 return mSavedState;
1858 }
1859
1860 /**
1861 * Set the saved state.
1862 */
1863 void setSavedState(Bundle state) {
1864 mSavedState = state;
1865 }
1866
1867 /**
1868 * @return TRUE if succeed in saving the state.
1869 */
1870 boolean saveState() {
1871 // If the WebView is null it means we ran low on memory and we already
1872 // stored the saved state in mSavedState.
1873 if (mMainView == null) {
1874 return mSavedState != null;
1875 }
1876
1877 mSavedState = new Bundle();
1878 final WebBackForwardList list = mMainView.saveState(mSavedState);
1879 if (list != null) {
1880 final File f = new File(mActivity.getTabControl().getThumbnailDir(),
1881 mMainView.hashCode() + "_pic.save");
1882 if (mMainView.savePicture(mSavedState, f)) {
1883 mSavedState.putString(CURRPICTURE, f.getPath());
Mike Reedd5eee692010-03-05 10:13:34 -05001884 } else {
1885 // if savePicture returned false, we can't trust the contents,
1886 // and it may be large, so we delete it right away
1887 f.delete();
Grace Kloba22ac16e2009-10-07 18:00:23 -07001888 }
1889 }
1890
1891 // Store some extra info for displaying the tab in the picker.
1892 final WebHistoryItem item = list != null ? list.getCurrentItem() : null;
1893 populatePickerData(item);
1894
1895 if (mPickerData.mUrl != null) {
1896 mSavedState.putString(CURRURL, mPickerData.mUrl);
1897 }
1898 if (mPickerData.mTitle != null) {
1899 mSavedState.putString(CURRTITLE, mPickerData.mTitle);
1900 }
1901 mSavedState.putBoolean(CLOSEONEXIT, mCloseOnExit);
1902 if (mAppId != null) {
1903 mSavedState.putString(APPID, mAppId);
1904 }
1905 if (mOriginalUrl != null) {
1906 mSavedState.putString(ORIGINALURL, mOriginalUrl);
1907 }
1908 // Remember the parent tab so the relationship can be restored.
1909 if (mParentTab != null) {
1910 mSavedState.putInt(PARENTTAB, mActivity.getTabControl().getTabIndex(
1911 mParentTab));
1912 }
1913 return true;
1914 }
1915
1916 /*
1917 * Restore the state of the tab.
1918 */
1919 boolean restoreState(Bundle b) {
1920 if (b == null) {
1921 return false;
1922 }
1923 // Restore the internal state even if the WebView fails to restore.
1924 // This will maintain the app id, original url and close-on-exit values.
1925 mSavedState = null;
1926 mPickerData = null;
1927 mCloseOnExit = b.getBoolean(CLOSEONEXIT);
1928 mAppId = b.getString(APPID);
1929 mOriginalUrl = b.getString(ORIGINALURL);
1930
1931 final WebBackForwardList list = mMainView.restoreState(b);
1932 if (list == null) {
1933 return false;
1934 }
1935 if (b.containsKey(CURRPICTURE)) {
1936 final File f = new File(b.getString(CURRPICTURE));
1937 mMainView.restorePicture(b, f);
1938 f.delete();
1939 }
1940 return true;
1941 }
1942}