blob: 355319329ea98e69a397ef1a76f5a80f74d9d36d [file] [log] [blame]
The Android Open Source Project0c908882009-03-03 19:32:16 -08001/*
2 * Copyright (C) 2006 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 com.google.android.googleapps.IGoogleLoginService;
20import com.google.android.googlelogin.GoogleLoginServiceConstants;
21
22import android.app.Activity;
The Android Open Source Project0c908882009-03-03 19:32:16 -080023import android.app.AlertDialog;
24import android.app.ProgressDialog;
25import android.app.SearchManager;
26import android.content.ActivityNotFoundException;
27import android.content.BroadcastReceiver;
28import android.content.ComponentName;
29import android.content.ContentResolver;
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -040030import android.content.ContentUris;
The Android Open Source Project0c908882009-03-03 19:32:16 -080031import android.content.ContentValues;
32import android.content.Context;
33import android.content.DialogInterface;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.ServiceConnection;
37import android.content.DialogInterface.OnCancelListener;
Grace Klobab4da0ad2009-05-14 14:45:40 -070038import android.content.pm.PackageInfo;
The Android Open Source Project0c908882009-03-03 19:32:16 -080039import android.content.pm.PackageManager;
40import android.content.pm.ResolveInfo;
41import android.content.res.AssetManager;
42import android.content.res.Configuration;
43import android.content.res.Resources;
44import android.database.Cursor;
45import android.database.sqlite.SQLiteDatabase;
46import android.database.sqlite.SQLiteException;
47import android.graphics.Bitmap;
48import android.graphics.Canvas;
49import android.graphics.Color;
50import android.graphics.DrawFilter;
51import android.graphics.Paint;
52import android.graphics.PaintFlagsDrawFilter;
53import android.graphics.Picture;
54import android.graphics.drawable.BitmapDrawable;
55import android.graphics.drawable.Drawable;
56import android.graphics.drawable.LayerDrawable;
57import android.graphics.drawable.PaintDrawable;
58import android.hardware.SensorListener;
59import android.hardware.SensorManager;
60import android.net.ConnectivityManager;
61import android.net.Uri;
62import android.net.WebAddress;
63import android.net.http.EventHandler;
64import android.net.http.SslCertificate;
65import android.net.http.SslError;
66import android.os.AsyncTask;
67import android.os.Bundle;
68import android.os.Debug;
69import android.os.Environment;
70import android.os.Handler;
71import android.os.IBinder;
72import android.os.Message;
73import android.os.PowerManager;
74import android.os.Process;
75import android.os.RemoteException;
76import android.os.ServiceManager;
77import android.os.SystemClock;
The Android Open Source Project0c908882009-03-03 19:32:16 -080078import android.provider.Browser;
79import android.provider.Contacts;
80import android.provider.Downloads;
81import android.provider.MediaStore;
82import android.provider.Contacts.Intents.Insert;
83import android.text.IClipboard;
84import android.text.TextUtils;
85import android.text.format.DateFormat;
86import android.text.util.Regex;
The Android Open Source Project0c908882009-03-03 19:32:16 -080087import android.util.Log;
88import android.view.ContextMenu;
89import android.view.Gravity;
90import android.view.KeyEvent;
91import android.view.LayoutInflater;
92import android.view.Menu;
93import android.view.MenuInflater;
94import android.view.MenuItem;
95import android.view.View;
96import android.view.ViewGroup;
97import android.view.Window;
98import android.view.WindowManager;
99import android.view.ContextMenu.ContextMenuInfo;
100import android.view.MenuItem.OnMenuItemClickListener;
101import android.view.animation.AlphaAnimation;
102import android.view.animation.Animation;
103import android.view.animation.AnimationSet;
104import android.view.animation.DecelerateInterpolator;
105import android.view.animation.ScaleAnimation;
106import android.view.animation.TranslateAnimation;
107import android.webkit.CookieManager;
108import android.webkit.CookieSyncManager;
109import android.webkit.DownloadListener;
Steve Block2bc69912009-07-30 14:45:13 +0100110import android.webkit.GeolocationPermissions;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800111import android.webkit.HttpAuthHandler;
Grace Klobab4da0ad2009-05-14 14:45:40 -0700112import android.webkit.PluginManager;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800113import android.webkit.SslErrorHandler;
114import android.webkit.URLUtil;
115import android.webkit.WebChromeClient;
Andrei Popescuc9b55562009-07-07 10:51:15 +0100116import android.webkit.WebChromeClient.CustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800117import android.webkit.WebHistoryItem;
118import android.webkit.WebIconDatabase;
Ben Murdoch092dd5d2009-04-22 12:34:12 +0100119import android.webkit.WebStorage;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800120import android.webkit.WebView;
121import android.webkit.WebViewClient;
122import android.widget.EditText;
123import android.widget.FrameLayout;
124import android.widget.LinearLayout;
125import android.widget.TextView;
126import android.widget.Toast;
127
128import java.io.BufferedOutputStream;
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -0400129import java.io.ByteArrayOutputStream;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800130import java.io.File;
131import java.io.FileInputStream;
132import java.io.FileOutputStream;
133import java.io.IOException;
134import java.io.InputStream;
135import java.net.MalformedURLException;
136import java.net.URI;
Dianne Hackborn99189432009-06-17 18:06:18 -0700137import java.net.URISyntaxException;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800138import java.net.URL;
139import java.net.URLEncoder;
140import java.text.ParseException;
141import java.util.Date;
142import java.util.Enumeration;
143import java.util.HashMap;
Patrick Scott37911c72009-03-24 18:02:58 -0700144import java.util.LinkedList;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800145import java.util.Vector;
146import java.util.regex.Matcher;
147import java.util.regex.Pattern;
148import java.util.zip.ZipEntry;
149import java.util.zip.ZipFile;
150
151public class BrowserActivity extends Activity
152 implements KeyTracker.OnKeyTracker,
153 View.OnCreateContextMenuListener,
154 DownloadListener {
155
Dave Bort31a6d1c2009-04-13 15:56:49 -0700156 /* Define some aliases to make these debugging flags easier to refer to.
157 * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
158 */
159 private final static boolean DEBUG = com.android.browser.Browser.DEBUG;
160 private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
161 private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
162
The Android Open Source Project0c908882009-03-03 19:32:16 -0800163 private IGoogleLoginService mGls = null;
164 private ServiceConnection mGlsConnection = null;
165
166 private SensorManager mSensorManager = null;
167
Satish Sampath565505b2009-05-29 15:37:27 +0100168 // These are single-character shortcuts for searching popular sources.
169 private static final int SHORTCUT_INVALID = 0;
170 private static final int SHORTCUT_GOOGLE_SEARCH = 1;
171 private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2;
172 private static final int SHORTCUT_DICTIONARY_SEARCH = 3;
173 private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4;
174
The Android Open Source Project0c908882009-03-03 19:32:16 -0800175 /* Whitelisted webpages
176 private static HashSet<String> sWhiteList;
177
178 static {
179 sWhiteList = new HashSet<String>();
180 sWhiteList.add("cnn.com/");
181 sWhiteList.add("espn.go.com/");
182 sWhiteList.add("nytimes.com/");
183 sWhiteList.add("engadget.com/");
184 sWhiteList.add("yahoo.com/");
185 sWhiteList.add("msn.com/");
186 sWhiteList.add("amazon.com/");
187 sWhiteList.add("consumerist.com/");
188 sWhiteList.add("google.com/m/news");
189 }
190 */
191
192 private void setupHomePage() {
193 final Runnable getAccount = new Runnable() {
194 public void run() {
195 // Lower priority
196 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
197 // get the default home page
198 String homepage = mSettings.getHomePage();
199
200 try {
201 if (mGls == null) return;
202
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700203 if (!homepage.startsWith("http://www.google.")) return;
204 if (homepage.indexOf('?') == -1) return;
205
The Android Open Source Project0c908882009-03-03 19:32:16 -0800206 String hostedUser = mGls.getAccount(GoogleLoginServiceConstants.PREFER_HOSTED);
207 String googleUser = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE);
208
209 // three cases:
210 //
211 // hostedUser == googleUser
212 // The device has only a google account
213 //
214 // hostedUser != googleUser
215 // The device has a hosted account and a google account
216 //
217 // hostedUser != null, googleUser == null
218 // The device has only a hosted account (so far)
219
220 // developers might have no accounts at all
221 if (hostedUser == null) return;
222
223 if (googleUser == null || !hostedUser.equals(googleUser)) {
224 String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1);
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700225 homepage = homepage.replace("?", "/a/" + domain + "?");
The Android Open Source Project0c908882009-03-03 19:32:16 -0800226 }
227 } catch (RemoteException ignore) {
228 // Login service died; carry on
229 } catch (RuntimeException ignore) {
230 // Login service died; carry on
231 } finally {
232 finish(homepage);
233 }
234 }
235
236 private void finish(final String homepage) {
237 mHandler.post(new Runnable() {
238 public void run() {
239 mSettings.setHomePage(BrowserActivity.this, homepage);
240 resumeAfterCredentials();
241
242 // as this is running in a separate thread,
243 // BrowserActivity's onDestroy() may have been called,
244 // which also calls unbindService().
245 if (mGlsConnection != null) {
246 // we no longer need to keep GLS open
247 unbindService(mGlsConnection);
248 mGlsConnection = null;
249 }
250 } });
251 } };
252
253 final boolean[] done = { false };
254
255 // Open a connection to the Google Login Service. The first
256 // time the connection is established, set up the homepage depending on
257 // the account in a background thread.
258 mGlsConnection = new ServiceConnection() {
259 public void onServiceConnected(ComponentName className, IBinder service) {
260 mGls = IGoogleLoginService.Stub.asInterface(service);
261 if (done[0] == false) {
262 done[0] = true;
263 Thread account = new Thread(getAccount);
264 account.setName("GLSAccount");
265 account.start();
266 }
267 }
268 public void onServiceDisconnected(ComponentName className) {
269 mGls = null;
270 }
271 };
272
273 bindService(GoogleLoginServiceConstants.SERVICE_INTENT,
274 mGlsConnection, Context.BIND_AUTO_CREATE);
275 }
276
Cary Clarka9771242009-08-11 16:42:26 -0400277 private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800278 @Override
279 public Void doInBackground(File... files) {
280 if (files != null) {
281 for (File f : files) {
Cary Clarkd6be1752009-08-12 12:56:42 -0400282 if (!f.delete()) {
283 Log.e(LOGTAG, f.getPath() + " was not deleted");
284 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800285 }
286 }
287 return null;
288 }
289 }
290
Leon Scroggins81db3662009-06-04 17:45:11 -0400291 // Flag to enable the touchable browser bar with buttons
292 private final boolean CUSTOM_BROWSER_BAR = true;
293
The Android Open Source Project0c908882009-03-03 19:32:16 -0800294 @Override public void onCreate(Bundle icicle) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700295 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800296 Log.v(LOGTAG, this + " onStart");
297 }
298 super.onCreate(icicle);
Leon Scroggins81db3662009-06-04 17:45:11 -0400299 if (CUSTOM_BROWSER_BAR) {
300 this.requestWindowFeature(Window.FEATURE_NO_TITLE);
Leon Scroggins81db3662009-06-04 17:45:11 -0400301 } else {
302 this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
303 this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
304 this.requestWindowFeature(Window.FEATURE_PROGRESS);
305 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
306 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800307 // test the browser in OpenGL
308 // requestWindowFeature(Window.FEATURE_OPENGL);
309
310 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
311
312 mResolver = getContentResolver();
313
The Android Open Source Project0c908882009-03-03 19:32:16 -0800314 //
315 // start MASF proxy service
316 //
317 //Intent proxyServiceIntent = new Intent();
318 //proxyServiceIntent.setComponent
319 // (new ComponentName(
320 // "com.android.masfproxyservice",
321 // "com.android.masfproxyservice.MasfProxyService"));
322 //startService(proxyServiceIntent, null);
323
324 mSecLockIcon = Resources.getSystem().getDrawable(
325 android.R.drawable.ic_secure);
326 mMixLockIcon = Resources.getSystem().getDrawable(
327 android.R.drawable.ic_partial_secure);
328 mGenericFavicon = getResources().getDrawable(
329 R.drawable.app_web_browser_sm);
330
Leon Scroggins81db3662009-06-04 17:45:11 -0400331 FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
332 .findViewById(com.android.internal.R.id.content);
333 if (CUSTOM_BROWSER_BAR) {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100334 // This FrameLayout will hold the custom FrameLayout and a LinearLayout
335 // that contains the title bar and a FrameLayout, which
Leon Scroggins81db3662009-06-04 17:45:11 -0400336 // holds everything else.
Andrei Popescuadc008d2009-06-26 14:11:30 +0100337 FrameLayout browserFrameLayout = (FrameLayout) LayoutInflater.from(this)
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400338 .inflate(R.layout.custom_screen, null);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400339 mTitleBar = (TitleBarSet) browserFrameLayout.findViewById(R.id.title_bar);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100340 mContentView = (FrameLayout) browserFrameLayout.findViewById(
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400341 R.id.main_content);
Ben Murdochbff2d602009-07-01 20:19:05 +0100342 mErrorConsoleContainer = (LinearLayout) browserFrameLayout.findViewById(
343 R.id.error_console);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100344 mCustomViewContainer = (FrameLayout) browserFrameLayout
345 .findViewById(R.id.fullscreen_custom_content);
346 frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400347 } else {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100348 mCustomViewContainer = new FrameLayout(this);
Andrei Popescu78f75702009-06-26 16:50:04 +0100349 mCustomViewContainer.setBackgroundColor(Color.BLACK);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100350 mContentView = new FrameLayout(this);
Ben Murdochbff2d602009-07-01 20:19:05 +0100351
352 LinearLayout linearLayout = new LinearLayout(this);
353 linearLayout.setOrientation(LinearLayout.VERTICAL);
354 mErrorConsoleContainer = new LinearLayout(this);
355 linearLayout.addView(mErrorConsoleContainer, new LinearLayout.LayoutParams(
356 ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
357 linearLayout.addView(mContentView, COVER_SCREEN_PARAMS);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100358 frameLayout.addView(mCustomViewContainer, COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +0100359 frameLayout.addView(linearLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400360 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800361
362 // Create the tab control and our initial tab
363 mTabControl = new TabControl(this);
364
365 // Open the icon database and retain all the bookmark urls for favicons
366 retainIconsOnStartup();
367
368 // Keep a settings instance handy.
369 mSettings = BrowserSettings.getInstance();
370 mSettings.setTabControl(mTabControl);
371 mSettings.loadFromDb(this);
372
373 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
374 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
375
Grace Klobaa34f6862009-07-31 16:28:17 -0700376 /* enables registration for changes in network status from
377 http stack */
378 mNetworkStateChangedFilter = new IntentFilter();
379 mNetworkStateChangedFilter.addAction(
380 ConnectivityManager.CONNECTIVITY_ACTION);
381 mNetworkStateIntentReceiver = new BroadcastReceiver() {
382 @Override
383 public void onReceive(Context context, Intent intent) {
384 if (intent.getAction().equals(
385 ConnectivityManager.CONNECTIVITY_ACTION)) {
386 boolean down = intent.getBooleanExtra(
387 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
388 onNetworkToggle(!down);
389 }
390 }
391 };
392
Grace Kloba615c6c92009-08-03 10:22:44 -0700393 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
394 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
395 filter.addDataScheme("package");
396 mPackageInstallationReceiver = new BroadcastReceiver() {
397 @Override
398 public void onReceive(Context context, Intent intent) {
399 final String action = intent.getAction();
400 final String packageName = intent.getData()
401 .getSchemeSpecificPart();
402 final boolean replacing = intent.getBooleanExtra(
403 Intent.EXTRA_REPLACING, false);
404 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
405 // if it is replacing, refreshPlugins() when adding
406 return;
407 }
408 PackageManager pm = BrowserActivity.this.getPackageManager();
409 PackageInfo pkgInfo = null;
410 try {
411 pkgInfo = pm.getPackageInfo(packageName,
412 PackageManager.GET_PERMISSIONS);
413 } catch (PackageManager.NameNotFoundException e) {
414 return;
415 }
416 if (pkgInfo != null) {
417 String permissions[] = pkgInfo.requestedPermissions;
418 if (permissions == null) {
419 return;
420 }
421 boolean permissionOk = false;
422 for (String permit : permissions) {
423 if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
424 permissionOk = true;
425 break;
426 }
427 }
428 if (permissionOk) {
429 PluginManager.getInstance(BrowserActivity.this)
430 .refreshPlugins(
431 Intent.ACTION_PACKAGE_ADDED
432 .equals(action));
433 }
434 }
435 }
436 };
437 registerReceiver(mPackageInstallationReceiver, filter);
438
Satish Sampath565505b2009-05-29 15:37:27 +0100439 // If this was a web search request, pass it on to the default web search provider.
440 if (handleWebSearchIntent(getIntent())) {
441 moveTaskToBack(true);
442 return;
443 }
444
The Android Open Source Project0c908882009-03-03 19:32:16 -0800445 if (!mTabControl.restoreState(icicle)) {
446 // clear up the thumbnail directory if we can't restore the state as
447 // none of the files in the directory are referenced any more.
448 new ClearThumbnails().execute(
449 mTabControl.getThumbnailDir().listFiles());
Grace Klobaaab3f092009-07-30 12:29:51 -0700450 // there is no quit on Android. But if we can't restore the state,
451 // we can treat it as a new Browser, remove the old session cookies.
452 CookieManager.getInstance().removeSessionCookie();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800453 final Intent intent = getIntent();
454 final Bundle extra = intent.getExtras();
455 // Create an initial tab.
456 // If the intent is ACTION_VIEW and data is not null, the Browser is
457 // invoked to view the content by another application. In this case,
458 // the tab will be close when exit.
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700459 UrlData urlData = getUrlDataFromIntent(intent);
460
The Android Open Source Project0c908882009-03-03 19:32:16 -0800461 final TabControl.Tab t = mTabControl.createNewTab(
462 Intent.ACTION_VIEW.equals(intent.getAction()) &&
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700463 intent.getData() != null,
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700464 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800465 mTabControl.setCurrentTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800466 attachTabToContentView(t);
467 WebView webView = t.getWebView();
468 if (extra != null) {
469 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
470 if (scale > 0 && scale <= 1000) {
471 webView.setInitialScale(scale);
472 }
473 }
474 // If we are not restoring from an icicle, then there is a high
475 // likely hood this is the first run. So, check to see if the
476 // homepage needs to be configured and copy any plugins from our
477 // asset directory to the data partition.
478 if ((extra == null || !extra.getBoolean("testing"))
479 && !mSettings.isLoginInitialized()) {
480 setupHomePage();
481 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800482
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700483 if (urlData.isEmpty()) {
Leon Scroggins160a7e72009-08-14 18:28:01 -0400484 bookmarksOrHistoryPicker(false, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800485 } else {
Grace Kloba81678d92009-06-30 07:09:56 -0700486 if (extra != null) {
487 urlData.setPostData(extra
488 .getByteArray(Browser.EXTRA_POST_DATA));
489 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700490 urlData.loadIn(webView);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800491 }
492 } else {
493 // TabControl.restoreState() will create a new tab even if
Leon Scroggins1f005d32009-08-10 17:36:42 -0400494 // restoring the state fails.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800495 attachTabToContentView(mTabControl.getCurrentTab());
496 }
Grace Kloba615c6c92009-08-03 10:22:44 -0700497
Leon Scroggins1f005d32009-08-10 17:36:42 -0400498 if (CUSTOM_BROWSER_BAR) {
Leon Scrogginsa81a7642009-08-31 17:05:41 -0400499 mTitleBar.init(this);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400500 // Create title bars for all of the tabs that have been created
501 for (int i = 0; i < mTabControl.getTabCount(); i ++) {
502 WebView view = mTabControl.getTab(i).getWebView();
503 mTitleBar.addTab(view, false);
504 }
505
Leon Scroggins1f005d32009-08-10 17:36:42 -0400506 mTitleBar.setCurrentTab(mTabControl.getCurrentIndex());
507 }
508
Feng Qianb3c02da2009-06-29 15:58:08 -0700509 // Read JavaScript flags if it exists.
510 String jsFlags = mSettings.getJsFlags();
511 if (jsFlags.trim().length() != 0) {
512 mTabControl.getCurrentWebView().setJsFlags(jsFlags);
513 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800514 }
515
516 @Override
517 protected void onNewIntent(Intent intent) {
518 TabControl.Tab current = mTabControl.getCurrentTab();
519 // When a tab is closed on exit, the current tab index is set to -1.
520 // Reset before proceed as Browser requires the current tab to be set.
521 if (current == null) {
522 // Try to reset the tab in case the index was incorrect.
523 current = mTabControl.getTab(0);
524 if (current == null) {
525 // No tabs at all so just ignore this intent.
526 return;
527 }
528 mTabControl.setCurrentTab(current);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400529 if (CUSTOM_BROWSER_BAR) {
530 mTitleBar.setCurrentTab(mTabControl.getTabIndex(current));
531 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800532 attachTabToContentView(current);
533 resetTitleAndIcon(current.getWebView());
534 }
535 final String action = intent.getAction();
536 final int flags = intent.getFlags();
537 if (Intent.ACTION_MAIN.equals(action) ||
538 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
539 // just resume the browser
540 return;
541 }
542 if (Intent.ACTION_VIEW.equals(action)
543 || Intent.ACTION_SEARCH.equals(action)
544 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
545 || Intent.ACTION_WEB_SEARCH.equals(action)) {
Satish Sampath565505b2009-05-29 15:37:27 +0100546 // If this was a search request (e.g. search query directly typed into the address bar),
547 // pass it on to the default web search provider.
548 if (handleWebSearchIntent(intent)) {
549 return;
550 }
551
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700552 UrlData urlData = getUrlDataFromIntent(intent);
553 if (urlData.isEmpty()) {
554 urlData = new UrlData(mSettings.getHomePage());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800555 }
Grace Kloba81678d92009-06-30 07:09:56 -0700556 urlData.setPostData(intent
557 .getByteArrayExtra(Browser.EXTRA_POST_DATA));
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700558
Grace Klobacc634032009-07-28 15:58:19 -0700559 final String appId = intent
560 .getStringExtra(Browser.EXTRA_APPLICATION_ID);
561 if (Intent.ACTION_VIEW.equals(action)
562 && !getPackageName().equals(appId)
563 && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
Patrick Scottcd115892009-07-16 09:42:58 -0400564 TabControl.Tab appTab = mTabControl.getTabFromId(appId);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700565 if (appTab != null) {
566 Log.i(LOGTAG, "Reusing tab for " + appId);
567 // Dismiss the subwindow if applicable.
568 dismissSubWindow(appTab);
569 // Since we might kill the WebView, remove it from the
570 // content view first.
571 removeTabFromContentView(appTab);
572 // Recreate the main WebView after destroying the old one.
573 // If the WebView has the same original url and is on that
574 // page, it can be reused.
575 boolean needsLoad =
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700576 mTabControl.recreateWebView(appTab, urlData.mUrl);
Ben Murdochbff2d602009-07-01 20:19:05 +0100577
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700578 if (current != appTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -0400579 switchToTab(mTabControl.getTabIndex(appTab));
580 if (needsLoad) {
581 urlData.loadIn(appTab.getWebView());
582 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700583 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -0400584 // If the tab was the current tab, we have to attach
585 // it to the view system again.
586 attachTabToContentView(appTab);
587 if (needsLoad) {
588 urlData.loadIn(appTab.getWebView());
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700589 }
590 }
591 return;
Patrick Scottcd115892009-07-16 09:42:58 -0400592 } else {
593 // No matching application tab, try to find a regular tab
594 // with a matching url.
595 appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
Leon Scroggins25515f82009-08-19 15:31:58 -0400596 if (appTab != null) {
597 if (current != appTab) {
598 switchToTab(mTabControl.getTabIndex(appTab));
599 }
600 // Otherwise, we are already viewing the correct tab.
Patrick Scottcd115892009-07-16 09:42:58 -0400601 } else {
602 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
603 // will be opened in a new tab unless we have reached
604 // MAX_TABS. Then the url will be opened in the current
605 // tab. If a new tab is created, it will have "true" for
606 // exit on close.
Leon Scroggins1f005d32009-08-10 17:36:42 -0400607 openTabAndShow(urlData, true, appId);
Patrick Scottcd115892009-07-16 09:42:58 -0400608 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700609 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800610 } else {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700611 if ("about:debug".equals(urlData.mUrl)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800612 mSettings.toggleDebugSettings();
613 return;
614 }
Leon Scroggins1f005d32009-08-10 17:36:42 -0400615 // Get rid of the subwindow if it exists
616 dismissSubWindow(current);
617 urlData.loadIn(current.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800618 }
619 }
620 }
621
Satish Sampath565505b2009-05-29 15:37:27 +0100622 private int parseUrlShortcut(String url) {
623 if (url == null) return SHORTCUT_INVALID;
624
625 // FIXME: quick search, need to be customized by setting
626 if (url.length() > 2 && url.charAt(1) == ' ') {
627 switch (url.charAt(0)) {
628 case 'g': return SHORTCUT_GOOGLE_SEARCH;
629 case 'w': return SHORTCUT_WIKIPEDIA_SEARCH;
630 case 'd': return SHORTCUT_DICTIONARY_SEARCH;
631 case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH;
632 }
633 }
634 return SHORTCUT_INVALID;
635 }
636
637 /**
638 * Launches the default web search activity with the query parameters if the given intent's data
639 * are identified as plain search terms and not URLs/shortcuts.
640 * @return true if the intent was handled and web search activity was launched, false if not.
641 */
642 private boolean handleWebSearchIntent(Intent intent) {
643 if (intent == null) return false;
644
645 String url = null;
646 final String action = intent.getAction();
647 if (Intent.ACTION_VIEW.equals(action)) {
648 url = intent.getData().toString();
649 } else if (Intent.ACTION_SEARCH.equals(action)
650 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
651 || Intent.ACTION_WEB_SEARCH.equals(action)) {
652 url = intent.getStringExtra(SearchManager.QUERY);
653 }
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100654 return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA));
Satish Sampath565505b2009-05-29 15:37:27 +0100655 }
656
657 /**
658 * Launches the default web search activity with the query parameters if the given url string
659 * was identified as plain search terms and not URL/shortcut.
660 * @return true if the request was handled and web search activity was launched, false if not.
661 */
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100662 private boolean handleWebSearchRequest(String inUrl, Bundle appData) {
Satish Sampath565505b2009-05-29 15:37:27 +0100663 if (inUrl == null) return false;
664
665 // In general, we shouldn't modify URL from Intent.
666 // But currently, we get the user-typed URL from search box as well.
667 String url = fixUrl(inUrl).trim();
668
669 // URLs and site specific search shortcuts are handled by the regular flow of control, so
670 // return early.
671 if (Regex.WEB_URL_PATTERN.matcher(url).matches()
Satish Sampathbc5b9f32009-06-04 18:21:40 +0100672 || ACCEPTED_URI_SCHEMA.matcher(url).matches()
Satish Sampath565505b2009-05-29 15:37:27 +0100673 || parseUrlShortcut(url) != SHORTCUT_INVALID) {
674 return false;
675 }
676
677 Browser.updateVisitedHistory(mResolver, url, false);
678 Browser.addSearchUrl(mResolver, url);
679
680 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
681 intent.addCategory(Intent.CATEGORY_DEFAULT);
682 intent.putExtra(SearchManager.QUERY, url);
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100683 if (appData != null) {
684 intent.putExtra(SearchManager.APP_DATA, appData);
685 }
Grace Klobacc634032009-07-28 15:58:19 -0700686 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
Satish Sampath565505b2009-05-29 15:37:27 +0100687 startActivity(intent);
688
689 return true;
690 }
691
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700692 private UrlData getUrlDataFromIntent(Intent intent) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800693 String url = null;
694 if (intent != null) {
695 final String action = intent.getAction();
696 if (Intent.ACTION_VIEW.equals(action)) {
697 url = smartUrlFilter(intent.getData());
698 if (url != null && url.startsWith("content:")) {
699 /* Append mimetype so webview knows how to display */
700 String mimeType = intent.resolveType(getContentResolver());
701 if (mimeType != null) {
702 url += "?" + mimeType;
703 }
704 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700705 if ("inline:".equals(url)) {
706 return new InlinedUrlData(
707 intent.getStringExtra(Browser.EXTRA_INLINE_CONTENT),
708 intent.getType(),
709 intent.getStringExtra(Browser.EXTRA_INLINE_ENCODING),
710 intent.getStringExtra(Browser.EXTRA_INLINE_FAILURL));
711 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800712 } else if (Intent.ACTION_SEARCH.equals(action)
713 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
714 || Intent.ACTION_WEB_SEARCH.equals(action)) {
715 url = intent.getStringExtra(SearchManager.QUERY);
716 if (url != null) {
717 mLastEnteredUrl = url;
718 // Don't add Urls, just search terms.
719 // Urls will get added when the page is loaded.
720 if (!Regex.WEB_URL_PATTERN.matcher(url).matches()) {
721 Browser.updateVisitedHistory(mResolver, url, false);
722 }
723 // In general, we shouldn't modify URL from Intent.
724 // But currently, we get the user-typed URL from search box as well.
725 url = fixUrl(url);
726 url = smartUrlFilter(url);
727 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
728 if (url.contains(searchSource)) {
729 String source = null;
730 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
731 if (appData != null) {
732 source = appData.getString(SearchManager.SOURCE);
733 }
734 if (TextUtils.isEmpty(source)) {
735 source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
736 }
737 url = url.replace(searchSource, "&source=android-"+source+"&");
738 }
739 }
740 }
741 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700742 return new UrlData(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800743 }
744
745 /* package */ static String fixUrl(String inUrl) {
746 if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
747 return inUrl;
748 if (inUrl.startsWith("http:") ||
749 inUrl.startsWith("https:")) {
750 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
751 inUrl = inUrl.replaceFirst("/", "//");
752 } else inUrl = inUrl.replaceFirst(":", "://");
753 }
754 return inUrl;
755 }
756
757 /**
758 * Looking for the pattern like this
759 *
760 * *
761 * * *
762 * *** * *******
763 * * *
764 * * *
765 * *
766 */
767 private final SensorListener mSensorListener = new SensorListener() {
768 private long mLastGestureTime;
769 private float[] mPrev = new float[3];
770 private float[] mPrevDiff = new float[3];
771 private float[] mDiff = new float[3];
772 private float[] mRevertDiff = new float[3];
773
774 public void onSensorChanged(int sensor, float[] values) {
775 boolean show = false;
776 float[] diff = new float[3];
777
778 for (int i = 0; i < 3; i++) {
779 diff[i] = values[i] - mPrev[i];
780 if (Math.abs(diff[i]) > 1) {
781 show = true;
782 }
783 if ((diff[i] > 1.0 && mDiff[i] < 0.2)
784 || (diff[i] < -1.0 && mDiff[i] > -0.2)) {
785 // start track when there is a big move, or revert
786 mRevertDiff[i] = mDiff[i];
787 mDiff[i] = 0;
788 } else if (diff[i] > -0.2 && diff[i] < 0.2) {
789 // reset when it is flat
790 mDiff[i] = mRevertDiff[i] = 0;
791 }
792 mDiff[i] += diff[i];
793 mPrevDiff[i] = diff[i];
794 mPrev[i] = values[i];
795 }
796
797 if (false) {
798 // only shows if we think the delta is big enough, in an attempt
799 // to detect "serious" moves left/right or up/down
800 Log.d("BrowserSensorHack", "sensorChanged " + sensor + " ("
801 + values[0] + ", " + values[1] + ", " + values[2] + ")"
802 + " diff(" + diff[0] + " " + diff[1] + " " + diff[2]
803 + ")");
804 Log.d("BrowserSensorHack", " mDiff(" + mDiff[0] + " "
805 + mDiff[1] + " " + mDiff[2] + ")" + " mRevertDiff("
806 + mRevertDiff[0] + " " + mRevertDiff[1] + " "
807 + mRevertDiff[2] + ")");
808 }
809
810 long now = android.os.SystemClock.uptimeMillis();
811 if (now - mLastGestureTime > 1000) {
812 mLastGestureTime = 0;
813
814 float y = mDiff[1];
815 float z = mDiff[2];
816 float ay = Math.abs(y);
817 float az = Math.abs(z);
818 float ry = mRevertDiff[1];
819 float rz = mRevertDiff[2];
820 float ary = Math.abs(ry);
821 float arz = Math.abs(rz);
822 boolean gestY = ay > 2.5f && ary > 1.0f && ay > ary;
823 boolean gestZ = az > 3.5f && arz > 1.0f && az > arz;
824
825 if ((gestY || gestZ) && !(gestY && gestZ)) {
826 WebView view = mTabControl.getCurrentWebView();
827
828 if (view != null) {
829 if (gestZ) {
830 if (z < 0) {
831 view.zoomOut();
832 } else {
833 view.zoomIn();
834 }
835 } else {
836 view.flingScroll(0, Math.round(y * 100));
837 }
838 }
839 mLastGestureTime = now;
840 }
841 }
842 }
843
844 public void onAccuracyChanged(int sensor, int accuracy) {
845 // TODO Auto-generated method stub
846
847 }
848 };
849
850 @Override protected void onResume() {
851 super.onResume();
Dave Bort31a6d1c2009-04-13 15:56:49 -0700852 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800853 Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
854 }
855
856 if (!mActivityInPause) {
857 Log.e(LOGTAG, "BrowserActivity is already resumed.");
858 return;
859 }
860
Mike Reed7bfa63b2009-05-28 11:08:32 -0400861 mTabControl.resumeCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800862 mActivityInPause = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400863 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800864
865 if (mWakeLock.isHeld()) {
866 mHandler.removeMessages(RELEASE_WAKELOCK);
867 mWakeLock.release();
868 }
869
870 if (mCredsDlg != null) {
871 if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
872 // In case credential request never comes back
873 mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
874 }
875 }
876
877 registerReceiver(mNetworkStateIntentReceiver,
878 mNetworkStateChangedFilter);
879 WebView.enablePlatformNotifications();
880
881 if (mSettings.doFlick()) {
882 if (mSensorManager == null) {
883 mSensorManager = (SensorManager) getSystemService(
884 Context.SENSOR_SERVICE);
885 }
886 mSensorManager.registerListener(mSensorListener,
887 SensorManager.SENSOR_ACCELEROMETER,
888 SensorManager.SENSOR_DELAY_FASTEST);
889 } else {
890 mSensorManager = null;
891 }
892 }
893
Leon Scrogginsa81a7642009-08-31 17:05:41 -0400894 @Override
895 public boolean onMenuOpened(int featureId, Menu menu) {
896 mTitleBar.setVisibility(View.VISIBLE);
897 return true;
898 }
899
The Android Open Source Project0c908882009-03-03 19:32:16 -0800900 /**
901 * onSaveInstanceState(Bundle map)
902 * onSaveInstanceState is called right before onStop(). The map contains
903 * the saved state.
904 */
905 @Override protected void onSaveInstanceState(Bundle outState) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700906 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800907 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
908 }
909 // the default implementation requires each view to have an id. As the
910 // browser handles the state itself and it doesn't use id for the views,
911 // don't call the default implementation. Otherwise it will trigger the
912 // warning like this, "couldn't save which view has focus because the
913 // focused view XXX has no id".
914
915 // Save all the tabs
916 mTabControl.saveState(outState);
917 }
918
919 @Override protected void onPause() {
920 super.onPause();
921
922 if (mActivityInPause) {
923 Log.e(LOGTAG, "BrowserActivity is already paused.");
924 return;
925 }
926
Mike Reed7bfa63b2009-05-28 11:08:32 -0400927 mTabControl.pauseCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800928 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400929 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800930 mWakeLock.acquire();
931 mHandler.sendMessageDelayed(mHandler
932 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
933 }
934
935 // Clear the credentials toast if it is up
936 if (mCredsDlg != null && mCredsDlg.isShowing()) {
937 mCredsDlg.dismiss();
938 }
939 mCredsDlg = null;
940
941 cancelStopToast();
942
943 // unregister network state listener
944 unregisterReceiver(mNetworkStateIntentReceiver);
945 WebView.disablePlatformNotifications();
946
947 if (mSensorManager != null) {
948 mSensorManager.unregisterListener(mSensorListener);
949 }
950 }
951
952 @Override protected void onDestroy() {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700953 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800954 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
955 }
956 super.onDestroy();
957 // Remove the current tab and sub window
958 TabControl.Tab t = mTabControl.getCurrentTab();
Patrick Scottfb5e77f2009-04-08 19:17:37 -0700959 if (t != null) {
960 dismissSubWindow(t);
961 removeTabFromContentView(t);
962 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800963 // Destroy all the tabs
964 mTabControl.destroy();
965 WebIconDatabase.getInstance().close();
966 if (mGlsConnection != null) {
967 unbindService(mGlsConnection);
968 mGlsConnection = null;
969 }
970
971 //
972 // stop MASF proxy service
973 //
974 //Intent proxyServiceIntent = new Intent();
975 //proxyServiceIntent.setComponent
976 // (new ComponentName(
977 // "com.android.masfproxyservice",
978 // "com.android.masfproxyservice.MasfProxyService"));
979 //stopService(proxyServiceIntent);
Grace Klobab4da0ad2009-05-14 14:45:40 -0700980
981 unregisterReceiver(mPackageInstallationReceiver);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800982 }
983
984 @Override
985 public void onConfigurationChanged(Configuration newConfig) {
986 super.onConfigurationChanged(newConfig);
987
988 if (mPageInfoDialog != null) {
989 mPageInfoDialog.dismiss();
990 showPageInfo(
991 mPageInfoView,
992 mPageInfoFromShowSSLCertificateOnError.booleanValue());
993 }
994 if (mSSLCertificateDialog != null) {
995 mSSLCertificateDialog.dismiss();
996 showSSLCertificate(
997 mSSLCertificateView);
998 }
999 if (mSSLCertificateOnErrorDialog != null) {
1000 mSSLCertificateOnErrorDialog.dismiss();
1001 showSSLCertificateOnError(
1002 mSSLCertificateOnErrorView,
1003 mSSLCertificateOnErrorHandler,
1004 mSSLCertificateOnErrorError);
1005 }
1006 if (mHttpAuthenticationDialog != null) {
1007 String title = ((TextView) mHttpAuthenticationDialog
1008 .findViewById(com.android.internal.R.id.alertTitle)).getText()
1009 .toString();
1010 String name = ((TextView) mHttpAuthenticationDialog
1011 .findViewById(R.id.username_edit)).getText().toString();
1012 String password = ((TextView) mHttpAuthenticationDialog
1013 .findViewById(R.id.password_edit)).getText().toString();
1014 int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1015 .getId();
1016 mHttpAuthenticationDialog.dismiss();
1017 showHttpAuthentication(mHttpAuthHandler, null, null, title,
1018 name, password, focusId);
1019 }
1020 if (mFindDialog != null && mFindDialog.isShowing()) {
1021 mFindDialog.onConfigurationChanged(newConfig);
1022 }
1023 }
1024
1025 @Override public void onLowMemory() {
1026 super.onLowMemory();
1027 mTabControl.freeMemory();
1028 }
1029
Mike Reed7bfa63b2009-05-28 11:08:32 -04001030 private boolean resumeWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001031 if ((!mActivityInPause && !mPageStarted) ||
1032 (mActivityInPause && mPageStarted)) {
1033 CookieSyncManager.getInstance().startSync();
1034 WebView w = mTabControl.getCurrentWebView();
1035 if (w != null) {
1036 w.resumeTimers();
1037 }
1038 return true;
1039 } else {
1040 return false;
1041 }
1042 }
1043
Mike Reed7bfa63b2009-05-28 11:08:32 -04001044 private boolean pauseWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001045 if (mActivityInPause && !mPageStarted) {
1046 CookieSyncManager.getInstance().stopSync();
1047 WebView w = mTabControl.getCurrentWebView();
1048 if (w != null) {
1049 w.pauseTimers();
1050 }
1051 return true;
1052 } else {
1053 return false;
1054 }
1055 }
1056
Leon Scroggins1f005d32009-08-10 17:36:42 -04001057 // FIXME: Do we want to call this when loading google for the first time?
The Android Open Source Project0c908882009-03-03 19:32:16 -08001058 /*
1059 * This function is called when we are launching for the first time. We
1060 * are waiting for the login credentials before loading Google home
1061 * pages. This way the user will be logged in straight away.
1062 */
1063 private void waitForCredentials() {
1064 // Show a toast
1065 mCredsDlg = new ProgressDialog(this);
1066 mCredsDlg.setIndeterminate(true);
1067 mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1068 // If the user cancels the operation, then cancel the Google
1069 // Credentials request.
1070 mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1071 mCredsDlg.show();
1072
1073 // We set a timeout for the retrieval of credentials in onResume()
1074 // as that is when we have freed up some CPU time to get
1075 // the login credentials.
1076 }
1077
1078 /*
1079 * If we have received the credentials or we have timed out and we are
1080 * showing the credentials dialog, then it is time to move on.
1081 */
1082 private void resumeAfterCredentials() {
1083 if (mCredsDlg == null) {
1084 return;
1085 }
1086
1087 // Clear the toast
1088 if (mCredsDlg.isShowing()) {
1089 mCredsDlg.dismiss();
1090 }
1091 mCredsDlg = null;
1092
1093 // Clear any pending timeout
1094 mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1095
1096 // Load the page
1097 WebView w = mTabControl.getCurrentWebView();
1098 if (w != null) {
1099 w.loadUrl(mSettings.getHomePage());
1100 }
1101
1102 // Update the settings, need to do this last as it can take a moment
1103 // to persist the settings. In the mean time we could be loading
1104 // content.
1105 mSettings.setLoginInitialized(this);
1106 }
1107
1108 // Open the icon database and retain all the icons for visited sites.
1109 private void retainIconsOnStartup() {
1110 final WebIconDatabase db = WebIconDatabase.getInstance();
1111 db.open(getDir("icons", 0).getPath());
1112 try {
1113 Cursor c = Browser.getAllBookmarks(mResolver);
1114 if (!c.moveToFirst()) {
1115 c.deactivate();
1116 return;
1117 }
1118 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1119 do {
1120 String url = c.getString(urlIndex);
1121 db.retainIconForPageUrl(url);
1122 } while (c.moveToNext());
1123 c.deactivate();
1124 } catch (IllegalStateException e) {
1125 Log.e(LOGTAG, "retainIconsOnStartup", e);
1126 }
1127 }
1128
1129 // Helper method for getting the top window.
1130 WebView getTopWindow() {
1131 return mTabControl.getCurrentTopWebView();
1132 }
1133
1134 @Override
1135 public boolean onCreateOptionsMenu(Menu menu) {
1136 super.onCreateOptionsMenu(menu);
1137
1138 MenuInflater inflater = getMenuInflater();
1139 inflater.inflate(R.menu.browser, menu);
1140 mMenu = menu;
1141 updateInLoadMenuItems();
1142 return true;
1143 }
1144
1145 /**
1146 * As the menu can be open when loading state changes
1147 * we must manually update the state of the stop/reload menu
1148 * item
1149 */
1150 private void updateInLoadMenuItems() {
1151 if (mMenu == null) {
1152 return;
1153 }
1154 MenuItem src = mInLoad ?
1155 mMenu.findItem(R.id.stop_menu_id):
1156 mMenu.findItem(R.id.reload_menu_id);
1157 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1158 dest.setIcon(src.getIcon());
1159 dest.setTitle(src.getTitle());
1160 }
1161
1162 @Override
1163 public boolean onContextItemSelected(MenuItem item) {
1164 // chording is not an issue with context menus, but we use the same
1165 // options selector, so set mCanChord to true so we can access them.
1166 mCanChord = true;
1167 int id = item.getItemId();
1168 final WebView webView = getTopWindow();
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001169 if (null == webView) {
1170 return false;
1171 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001172 final HashMap hrefMap = new HashMap();
1173 hrefMap.put("webview", webView);
1174 final Message msg = mHandler.obtainMessage(
1175 FOCUS_NODE_HREF, id, 0, hrefMap);
1176 switch (id) {
1177 // -- Browser context menu
1178 case R.id.open_context_menu_id:
1179 case R.id.open_newtab_context_menu_id:
1180 case R.id.bookmark_context_menu_id:
1181 case R.id.save_link_context_menu_id:
1182 case R.id.share_link_context_menu_id:
1183 case R.id.copy_link_context_menu_id:
1184 webView.requestFocusNodeHref(msg);
1185 break;
1186
1187 default:
1188 // For other context menus
1189 return onOptionsItemSelected(item);
1190 }
1191 mCanChord = false;
1192 return true;
1193 }
1194
1195 private Bundle createGoogleSearchSourceBundle(String source) {
1196 Bundle bundle = new Bundle();
1197 bundle.putString(SearchManager.SOURCE, source);
1198 return bundle;
1199 }
1200
1201 /**
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001202 * Overriding this to insert a local information bundle
The Android Open Source Project0c908882009-03-03 19:32:16 -08001203 */
1204 @Override
1205 public boolean onSearchRequested() {
Leon Scroggins5bbe9802009-07-31 13:10:55 -04001206 String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
Grace Kloba83f47342009-07-20 10:44:31 -07001207 startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001208 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001209 return true;
1210 }
1211
1212 @Override
1213 public void startSearch(String initialQuery, boolean selectInitialQuery,
1214 Bundle appSearchData, boolean globalSearch) {
1215 if (appSearchData == null) {
1216 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1217 }
1218 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1219 }
1220
Leon Scroggins1f005d32009-08-10 17:36:42 -04001221 /**
1222 * Switch tabs. Called by the TitleBarSet when sliding the title bar
1223 * results in changing tabs.
Leon Scroggins160a7e72009-08-14 18:28:01 -04001224 * @param index Index of the tab to change to, as defined by
1225 * mTabControl.getTabIndex(Tab t).
1226 * @return boolean True if we successfully switched to a different tab. If
1227 * the indexth tab is null, or if that tab is the same as
1228 * the current one, return false.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001229 */
Leon Scroggins160a7e72009-08-14 18:28:01 -04001230 /* package */ boolean switchToTab(int index) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001231 TabControl.Tab tab = mTabControl.getTab(index);
1232 TabControl.Tab currentTab = mTabControl.getCurrentTab();
1233 if (tab == null || tab == currentTab) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001234 return false;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001235 }
1236 if (currentTab != null) {
1237 // currentTab may be null if it was just removed. In that case,
1238 // we do not need to remove it
1239 removeTabFromContentView(currentTab);
1240 }
1241 removeTabFromContentView(tab);
1242 mTabControl.setCurrentTab(tab);
1243 attachTabToContentView(tab);
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001244 if (CUSTOM_BROWSER_BAR) {
1245 mTitleBar.setCurrentTab(index);
Leon Scroggins416b7cb2009-08-19 16:26:12 -04001246 WebView view = tab.getWebView();
1247 view.slideIntoFocus();
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001248 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001249 return true;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001250 }
1251
1252 /* package */ void closeCurrentWindow() {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001253 final TabControl.Tab current = mTabControl.getCurrentTab();
Leon Scroggins160a7e72009-08-14 18:28:01 -04001254 if (mTabControl.getTabCount() == 1) {
1255 // This is the last tab. Open a new one, as well as the history
1256 // picker, and close the current one.
1257 TabControl.Tab newTab = openTabAndShow(
1258 BrowserActivity.EMPTY_URL_DATA, false, null);
1259 bookmarksOrHistoryPicker(false, true);
1260 closeTab(current);
1261 mTabControl.setCurrentTab(newTab);
1262 return;
1263 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001264 final TabControl.Tab parent = current.getParentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04001265 int indexToShow = -1;
1266 if (parent != null) {
1267 indexToShow = mTabControl.getTabIndex(parent);
1268 } else {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001269 final int currentIndex = mTabControl.getCurrentIndex();
1270 // Try to move to the tab to the right
1271 indexToShow = currentIndex + 1;
1272 if (indexToShow > mTabControl.getTabCount() - 1) {
1273 // Try to move to the tab to the left
1274 indexToShow = currentIndex - 1;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001275 }
1276 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001277 if (switchToTab(indexToShow)) {
1278 // Close window
1279 closeTab(current);
1280 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001281 }
1282
The Android Open Source Project0c908882009-03-03 19:32:16 -08001283 @Override
1284 public boolean onOptionsItemSelected(MenuItem item) {
1285 if (!mCanChord) {
1286 // The user has already fired a shortcut with this hold down of the
1287 // menu key.
1288 return false;
1289 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001290 if (null == getTopWindow()) {
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001291 return false;
1292 }
Grace Kloba6ee9c492009-07-13 10:04:34 -07001293 if (mMenuIsDown) {
1294 // The shortcut action consumes the MENU. Even if it is still down,
1295 // it won't trigger the next shortcut action. In the case of the
1296 // shortcut action triggering a new activity, like Bookmarks, we
1297 // won't get onKeyUp for MENU. So it is important to reset it here.
1298 mMenuIsDown = false;
1299 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001300 switch (item.getItemId()) {
1301 // -- Main menu
Leon Scrogginsa81a7642009-08-31 17:05:41 -04001302 case R.id.new_tab_menu_id:
1303 openTabAndShow(EMPTY_URL_DATA, false, null);
1304 bookmarksOrHistoryPicker(false, true);
1305 break;
1306
Leon Scroggins64b80f32009-08-07 12:03:34 -04001307 case R.id.goto_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001308 bookmarksOrHistoryPicker(false, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001309 break;
1310
Leon Scroggins1f005d32009-08-10 17:36:42 -04001311 case R.id.add_bookmark_menu_id:
1312 Intent i = new Intent(BrowserActivity.this,
1313 AddBookmarkPage.class);
1314 WebView w = getTopWindow();
1315 i.putExtra("url", w.getUrl());
1316 i.putExtra("title", w.getTitle());
1317 startActivity(i);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001318 break;
1319
1320 case R.id.stop_reload_menu_id:
1321 if (mInLoad) {
1322 stopLoading();
1323 } else {
1324 getTopWindow().reload();
1325 }
1326 break;
1327
1328 case R.id.back_menu_id:
1329 getTopWindow().goBack();
1330 break;
1331
1332 case R.id.forward_menu_id:
1333 getTopWindow().goForward();
1334 break;
1335
1336 case R.id.close_menu_id:
1337 // Close the subwindow if it exists.
1338 if (mTabControl.getCurrentSubWindow() != null) {
1339 dismissSubWindow(mTabControl.getCurrentTab());
1340 break;
1341 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001342 closeCurrentWindow();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001343 break;
1344
1345 case R.id.homepage_menu_id:
1346 TabControl.Tab current = mTabControl.getCurrentTab();
1347 if (current != null) {
1348 dismissSubWindow(current);
1349 current.getWebView().loadUrl(mSettings.getHomePage());
1350 }
1351 break;
1352
1353 case R.id.preferences_menu_id:
1354 Intent intent = new Intent(this,
1355 BrowserPreferencesPage.class);
1356 startActivityForResult(intent, PREFERENCES_PAGE);
1357 break;
1358
1359 case R.id.find_menu_id:
1360 if (null == mFindDialog) {
1361 mFindDialog = new FindDialog(this);
1362 }
1363 mFindDialog.setWebView(getTopWindow());
1364 mFindDialog.show();
1365 mMenuState = EMPTY_MENU;
1366 break;
1367
1368 case R.id.select_text_id:
1369 getTopWindow().emulateShiftHeld();
1370 break;
1371 case R.id.page_info_menu_id:
1372 showPageInfo(mTabControl.getCurrentTab(), false);
1373 break;
1374
1375 case R.id.classic_history_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001376 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001377 break;
1378
1379 case R.id.share_page_menu_id:
1380 Browser.sendString(this, getTopWindow().getUrl());
1381 break;
1382
1383 case R.id.dump_nav_menu_id:
1384 getTopWindow().debugDump();
1385 break;
1386
1387 case R.id.zoom_in_menu_id:
1388 getTopWindow().zoomIn();
1389 break;
1390
1391 case R.id.zoom_out_menu_id:
1392 getTopWindow().zoomOut();
1393 break;
1394
1395 case R.id.view_downloads_menu_id:
1396 viewDownloads(null);
1397 break;
1398
The Android Open Source Project0c908882009-03-03 19:32:16 -08001399 case R.id.window_one_menu_id:
1400 case R.id.window_two_menu_id:
1401 case R.id.window_three_menu_id:
1402 case R.id.window_four_menu_id:
1403 case R.id.window_five_menu_id:
1404 case R.id.window_six_menu_id:
1405 case R.id.window_seven_menu_id:
1406 case R.id.window_eight_menu_id:
1407 {
1408 int menuid = item.getItemId();
1409 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1410 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1411 TabControl.Tab desiredTab = mTabControl.getTab(id);
1412 if (desiredTab != null &&
1413 desiredTab != mTabControl.getCurrentTab()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001414 switchToTab(id);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001415 }
1416 break;
1417 }
1418 }
1419 }
1420 break;
1421
1422 default:
1423 if (!super.onOptionsItemSelected(item)) {
1424 return false;
1425 }
1426 // Otherwise fall through.
1427 }
1428 mCanChord = false;
1429 return true;
1430 }
1431
1432 public void closeFind() {
1433 mMenuState = R.id.MAIN_MENU;
1434 }
1435
1436 @Override public boolean onPrepareOptionsMenu(Menu menu)
1437 {
1438 // This happens when the user begins to hold down the menu key, so
1439 // allow them to chord to get a shortcut.
1440 mCanChord = true;
1441 // Note: setVisible will decide whether an item is visible; while
1442 // setEnabled() will decide whether an item is enabled, which also means
1443 // whether the matching shortcut key will function.
1444 super.onPrepareOptionsMenu(menu);
1445 switch (mMenuState) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001446 case EMPTY_MENU:
1447 if (mCurrentMenuState != mMenuState) {
1448 menu.setGroupVisible(R.id.MAIN_MENU, false);
1449 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1450 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001451 }
1452 break;
1453 default:
1454 if (mCurrentMenuState != mMenuState) {
1455 menu.setGroupVisible(R.id.MAIN_MENU, true);
1456 menu.setGroupEnabled(R.id.MAIN_MENU, true);
1457 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001458 }
1459 final WebView w = getTopWindow();
1460 boolean canGoBack = false;
1461 boolean canGoForward = false;
1462 boolean isHome = false;
1463 if (w != null) {
1464 canGoBack = w.canGoBack();
1465 canGoForward = w.canGoForward();
1466 isHome = mSettings.getHomePage().equals(w.getUrl());
1467 }
1468 final MenuItem back = menu.findItem(R.id.back_menu_id);
1469 back.setEnabled(canGoBack);
1470
1471 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1472 home.setEnabled(!isHome);
1473
1474 menu.findItem(R.id.forward_menu_id)
1475 .setEnabled(canGoForward);
1476
Leon Scrogginsa81a7642009-08-31 17:05:41 -04001477 menu.findItem(R.id.new_tab_menu_id).setEnabled(
1478 mTabControl.getTabCount() < TabControl.MAX_TABS);
1479
The Android Open Source Project0c908882009-03-03 19:32:16 -08001480 // decide whether to show the share link option
1481 PackageManager pm = getPackageManager();
1482 Intent send = new Intent(Intent.ACTION_SEND);
1483 send.setType("text/plain");
1484 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1485 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1486
The Android Open Source Project0c908882009-03-03 19:32:16 -08001487 boolean isNavDump = mSettings.isNavDump();
1488 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1489 nav.setVisible(isNavDump);
1490 nav.setEnabled(isNavDump);
1491 break;
1492 }
1493 mCurrentMenuState = mMenuState;
1494 return true;
1495 }
1496
1497 @Override
1498 public void onCreateContextMenu(ContextMenu menu, View v,
1499 ContextMenuInfo menuInfo) {
1500 WebView webview = (WebView) v;
1501 WebView.HitTestResult result = webview.getHitTestResult();
1502 if (result == null) {
1503 return;
1504 }
1505
1506 int type = result.getType();
1507 if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1508 Log.w(LOGTAG,
1509 "We should not show context menu when nothing is touched");
1510 return;
1511 }
1512 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1513 // let TextView handles context menu
1514 return;
1515 }
1516
1517 // Note, http://b/issue?id=1106666 is requesting that
1518 // an inflated menu can be used again. This is not available
1519 // yet, so inflate each time (yuk!)
1520 MenuInflater inflater = getMenuInflater();
1521 inflater.inflate(R.menu.browsercontext, menu);
1522
1523 // Show the correct menu group
1524 String extra = result.getExtra();
1525 menu.setGroupVisible(R.id.PHONE_MENU,
1526 type == WebView.HitTestResult.PHONE_TYPE);
1527 menu.setGroupVisible(R.id.EMAIL_MENU,
1528 type == WebView.HitTestResult.EMAIL_TYPE);
1529 menu.setGroupVisible(R.id.GEO_MENU,
1530 type == WebView.HitTestResult.GEO_TYPE);
1531 menu.setGroupVisible(R.id.IMAGE_MENU,
1532 type == WebView.HitTestResult.IMAGE_TYPE
1533 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1534 menu.setGroupVisible(R.id.ANCHOR_MENU,
1535 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1536 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1537
1538 // Setup custom handling depending on the type
1539 switch (type) {
1540 case WebView.HitTestResult.PHONE_TYPE:
1541 menu.setHeaderTitle(Uri.decode(extra));
1542 menu.findItem(R.id.dial_context_menu_id).setIntent(
1543 new Intent(Intent.ACTION_VIEW, Uri
1544 .parse(WebView.SCHEME_TEL + extra)));
1545 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1546 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1547 addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1548 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1549 addIntent);
1550 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1551 new Copy(extra));
1552 break;
1553
1554 case WebView.HitTestResult.EMAIL_TYPE:
1555 menu.setHeaderTitle(extra);
1556 menu.findItem(R.id.email_context_menu_id).setIntent(
1557 new Intent(Intent.ACTION_VIEW, Uri
1558 .parse(WebView.SCHEME_MAILTO + extra)));
1559 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1560 new Copy(extra));
1561 break;
1562
1563 case WebView.HitTestResult.GEO_TYPE:
1564 menu.setHeaderTitle(extra);
1565 menu.findItem(R.id.map_context_menu_id).setIntent(
1566 new Intent(Intent.ACTION_VIEW, Uri
1567 .parse(WebView.SCHEME_GEO
1568 + URLEncoder.encode(extra))));
1569 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1570 new Copy(extra));
1571 break;
1572
1573 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1574 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1575 TextView titleView = (TextView) LayoutInflater.from(this)
1576 .inflate(android.R.layout.browser_link_context_header,
1577 null);
1578 titleView.setText(extra);
1579 menu.setHeaderView(titleView);
1580 // decide whether to show the open link in new tab option
1581 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1582 mTabControl.getTabCount() < TabControl.MAX_TABS);
1583 PackageManager pm = getPackageManager();
1584 Intent send = new Intent(Intent.ACTION_SEND);
1585 send.setType("text/plain");
1586 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1587 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1588 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1589 break;
1590 }
1591 // otherwise fall through to handle image part
1592 case WebView.HitTestResult.IMAGE_TYPE:
1593 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1594 menu.setHeaderTitle(extra);
1595 }
1596 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1597 new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1598 menu.findItem(R.id.download_context_menu_id).
1599 setOnMenuItemClickListener(new Download(extra));
1600 break;
1601
1602 default:
1603 Log.w(LOGTAG, "We should not get here.");
1604 break;
1605 }
1606 }
1607
The Android Open Source Project0c908882009-03-03 19:32:16 -08001608 // Attach the given tab to the content view.
1609 private void attachTabToContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001610 // Attach the container that contains the main WebView and any other UI
1611 // associated with the tab.
1612 mContentView.addView(t.getContainer(), COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +01001613
1614 if (mShouldShowErrorConsole) {
1615 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
1616 if (errorConsole.numberOfErrors() == 0) {
1617 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1618 } else {
1619 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1620 }
1621
1622 mErrorConsoleContainer.addView(errorConsole,
1623 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
1624 ViewGroup.LayoutParams.WRAP_CONTENT));
1625 }
1626
The Android Open Source Project0c908882009-03-03 19:32:16 -08001627 // Attach the sub window if necessary
1628 attachSubWindow(t);
1629 // Request focus on the top window.
1630 t.getTopWindow().requestFocus();
1631 }
1632
1633 // Attach a sub window to the main WebView of the given tab.
1634 private void attachSubWindow(TabControl.Tab t) {
1635 // If a sub window exists, attach it to the content view.
1636 final WebView subView = t.getSubWebView();
1637 if (subView != null) {
1638 final View container = t.getSubWebViewContainer();
1639 mContentView.addView(container, COVER_SCREEN_PARAMS);
1640 subView.requestFocus();
1641 }
1642 }
1643
1644 // Remove the given tab from the content view.
1645 private void removeTabFromContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001646 // Remove the container that contains the main WebView.
1647 mContentView.removeView(t.getContainer());
Ben Murdochbff2d602009-07-01 20:19:05 +01001648
1649 if (mTabControl.getCurrentErrorConsole(false) != null) {
1650 mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
1651 }
1652
The Android Open Source Project0c908882009-03-03 19:32:16 -08001653 // Remove the sub window if it exists.
1654 if (t.getSubWebView() != null) {
1655 mContentView.removeView(t.getSubWebViewContainer());
1656 }
1657 }
1658
1659 // Remove the sub window if it exists. Also called by TabControl when the
1660 // user clicks the 'X' to dismiss a sub window.
1661 /* package */ void dismissSubWindow(TabControl.Tab t) {
1662 final WebView mainView = t.getWebView();
1663 if (t.getSubWebView() != null) {
1664 // Remove the container view and request focus on the main WebView.
1665 mContentView.removeView(t.getSubWebViewContainer());
1666 mainView.requestFocus();
1667 // Tell the TabControl to dismiss the subwindow. This will destroy
1668 // the WebView.
1669 mTabControl.dismissSubWindow(t);
1670 }
1671 }
1672
Leon Scroggins1f005d32009-08-10 17:36:42 -04001673 // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001674 // that accepts url as string.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001675 private TabControl.Tab openTabAndShow(String url, boolean closeOnExit,
1676 String appId) {
1677 return openTabAndShow(new UrlData(url), closeOnExit, appId);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001678 }
1679
1680 // This method does a ton of stuff. It will attempt to create a new tab
1681 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
Leon Scroggins1f005d32009-08-10 17:36:42 -04001682 // url isn't null, it will load the given url.
1683 /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001684 boolean closeOnExit, String appId) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001685 final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1686 final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1687 if (newTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001688 final TabControl.Tab tab = mTabControl.createNewTab(
1689 closeOnExit, appId, urlData.mUrl);
1690 WebView webview = tab.getWebView();
1691 if (CUSTOM_BROWSER_BAR) {
1692 mTitleBar.addTab(webview, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001693 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001694 removeTabFromContentView(currentTab);
1695 attachTabToContentView(tab);
Patrick Scott8bbd69f2009-08-14 13:35:53 -04001696 // We must set the new tab as the current tab to reflect the old
1697 // animation behavior.
1698 mTabControl.setCurrentTab(tab);
Leon Scroggins160a7e72009-08-14 18:28:01 -04001699 if (!urlData.isEmpty()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001700 urlData.loadIn(webview);
1701 }
1702 return tab;
1703 } else {
1704 // Get rid of the subwindow if it exists
1705 dismissSubWindow(currentTab);
1706 if (!urlData.isEmpty()) {
1707 // Load the given url.
1708 urlData.loadIn(currentTab.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001709 }
1710 }
Grace Klobac9181842009-04-14 08:53:22 -07001711 return currentTab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001712 }
1713
Grace Klobac9181842009-04-14 08:53:22 -07001714 private TabControl.Tab openTab(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001715 if (mSettings.openInBackground()) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001716 TabControl.Tab t = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001717 if (t != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001718 WebView view = t.getWebView();
1719 if (CUSTOM_BROWSER_BAR) {
1720 mTitleBar.addTab(view, false);
1721 }
1722 view.loadUrl(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001723 }
Grace Klobac9181842009-04-14 08:53:22 -07001724 return t;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001725 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001726 return openTabAndShow(url, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001727 }
1728 }
1729
1730 private class Copy implements OnMenuItemClickListener {
1731 private CharSequence mText;
1732
1733 public boolean onMenuItemClick(MenuItem item) {
1734 copy(mText);
1735 return true;
1736 }
1737
1738 public Copy(CharSequence toCopy) {
1739 mText = toCopy;
1740 }
1741 }
1742
1743 private class Download implements OnMenuItemClickListener {
1744 private String mText;
1745
1746 public boolean onMenuItemClick(MenuItem item) {
1747 onDownloadStartNoStream(mText, null, null, null, -1);
1748 return true;
1749 }
1750
1751 public Download(String toDownload) {
1752 mText = toDownload;
1753 }
1754 }
1755
1756 private void copy(CharSequence text) {
1757 try {
1758 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
1759 if (clip != null) {
1760 clip.setClipboardText(text);
1761 }
1762 } catch (android.os.RemoteException e) {
1763 Log.e(LOGTAG, "Copy failed", e);
1764 }
1765 }
1766
1767 /**
1768 * Resets the browser title-view to whatever it must be (for example, if we
1769 * load a page from history).
1770 */
1771 private void resetTitle() {
1772 resetLockIcon();
1773 resetTitleIconAndProgress();
1774 }
1775
1776 /**
1777 * Resets the browser title-view to whatever it must be
1778 * (for example, if we had a loading error)
1779 * When we have a new page, we call resetTitle, when we
1780 * have to reset the titlebar to whatever it used to be
1781 * (for example, if the user chose to stop loading), we
1782 * call resetTitleAndRevertLockIcon.
1783 */
1784 /* package */ void resetTitleAndRevertLockIcon() {
1785 revertLockIcon();
1786 resetTitleIconAndProgress();
1787 }
1788
1789 /**
1790 * Reset the title, favicon, and progress.
1791 */
1792 private void resetTitleIconAndProgress() {
1793 WebView current = mTabControl.getCurrentWebView();
1794 if (current == null) {
1795 return;
1796 }
1797 resetTitleAndIcon(current);
1798 int progress = current.getProgress();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001799 mWebChromeClient.onProgressChanged(current, progress);
1800 }
1801
1802 // Reset the title and the icon based on the given item.
1803 private void resetTitleAndIcon(WebView view) {
1804 WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
1805 if (item != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001806 setUrlTitle(item.getUrl(), item.getTitle(), view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001807 setFavicon(item.getFavicon());
1808 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001809 setUrlTitle(null, null, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001810 setFavicon(null);
1811 }
1812 }
1813
1814 /**
1815 * Sets a title composed of the URL and the title string.
1816 * @param url The URL of the site being loaded.
1817 * @param title The title of the site being loaded.
1818 */
Leon Scroggins1f005d32009-08-10 17:36:42 -04001819 private void setUrlTitle(String url, String title, WebView view) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001820 mUrl = url;
1821 mTitle = title;
1822
Leon Scroggins1f005d32009-08-10 17:36:42 -04001823 if (CUSTOM_BROWSER_BAR) {
1824 mTitleBar.setTitleAndUrl(title, url, view);
1825 } else {
1826 setTitle(buildUrlTitle(url, title));
The Android Open Source Project0c908882009-03-03 19:32:16 -08001827 }
1828 }
1829
1830 /**
1831 * Builds and returns the page title, which is some
1832 * combination of the page URL and title.
1833 * @param url The URL of the site being loaded.
1834 * @param title The title of the site being loaded.
1835 * @return The page title.
1836 */
1837 private String buildUrlTitle(String url, String title) {
1838 String urlTitle = "";
1839
1840 if (url != null) {
1841 String titleUrl = buildTitleUrl(url);
1842
1843 if (title != null && 0 < title.length()) {
1844 if (titleUrl != null && 0 < titleUrl.length()) {
1845 urlTitle = titleUrl + ": " + title;
1846 } else {
1847 urlTitle = title;
1848 }
1849 } else {
1850 if (titleUrl != null) {
1851 urlTitle = titleUrl;
1852 }
1853 }
1854 }
1855
1856 return urlTitle;
1857 }
1858
1859 /**
1860 * @param url The URL to build a title version of the URL from.
1861 * @return The title version of the URL or null if fails.
1862 * The title version of the URL can be either the URL hostname,
1863 * or the hostname with an "https://" prefix (for secure URLs),
1864 * or an empty string if, for example, the URL in question is a
1865 * file:// URL with no hostname.
1866 */
Leon Scroggins32e14a62009-06-11 10:26:34 -04001867 /* package */ static String buildTitleUrl(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001868 String titleUrl = null;
1869
1870 if (url != null) {
1871 try {
1872 // parse the url string
1873 URL urlObj = new URL(url);
1874 if (urlObj != null) {
1875 titleUrl = "";
1876
1877 String protocol = urlObj.getProtocol();
1878 String host = urlObj.getHost();
1879
1880 if (host != null && 0 < host.length()) {
1881 titleUrl = host;
1882 if (protocol != null) {
1883 // if a secure site, add an "https://" prefix!
1884 if (protocol.equalsIgnoreCase("https")) {
1885 titleUrl = protocol + "://" + host;
1886 }
1887 }
1888 }
1889 }
1890 } catch (MalformedURLException e) {}
1891 }
1892
1893 return titleUrl;
1894 }
1895
1896 // Set the favicon in the title bar.
1897 private void setFavicon(Bitmap icon) {
Leon Scroggins81db3662009-06-04 17:45:11 -04001898 if (CUSTOM_BROWSER_BAR) {
1899 Drawable[] array = new Drawable[3];
1900 array[0] = new PaintDrawable(Color.BLACK);
1901 PaintDrawable p = new PaintDrawable(Color.WHITE);
1902 array[1] = p;
1903 if (icon == null) {
1904 array[2] = mGenericFavicon;
1905 } else {
1906 array[2] = new BitmapDrawable(icon);
1907 }
1908 LayerDrawable d = new LayerDrawable(array);
1909 d.setLayerInset(1, 1, 1, 1, 1);
1910 d.setLayerInset(2, 2, 2, 2, 2);
Leon Scroggins1f005d32009-08-10 17:36:42 -04001911 mTitleBar.setFavicon(d, getTopWindow());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001912 } else {
Leon Scroggins81db3662009-06-04 17:45:11 -04001913 Drawable[] array = new Drawable[2];
1914 PaintDrawable p = new PaintDrawable(Color.WHITE);
1915 p.setCornerRadius(3f);
1916 array[0] = p;
1917 if (icon == null) {
1918 array[1] = mGenericFavicon;
1919 } else {
1920 array[1] = new BitmapDrawable(icon);
1921 }
1922 LayerDrawable d = new LayerDrawable(array);
1923 d.setLayerInset(1, 2, 2, 2, 2);
1924 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001925 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001926 }
1927
1928 /**
1929 * Saves the current lock-icon state before resetting
1930 * the lock icon. If we have an error, we may need to
1931 * roll back to the previous state.
1932 */
1933 private void saveLockIcon() {
1934 mPrevLockType = mLockIconType;
1935 }
1936
1937 /**
1938 * Reverts the lock-icon state to the last saved state,
1939 * for example, if we had an error, and need to cancel
1940 * the load.
1941 */
1942 private void revertLockIcon() {
1943 mLockIconType = mPrevLockType;
1944
Dave Bort31a6d1c2009-04-13 15:56:49 -07001945 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001946 Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
1947 " revert lock icon to " + mLockIconType);
1948 }
1949
1950 updateLockIconImage(mLockIconType);
1951 }
1952
Leon Scroggins1f005d32009-08-10 17:36:42 -04001953 /**
1954 * Close the tab after removing its associated title bar.
1955 */
1956 private void closeTab(TabControl.Tab t) {
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001957 if (CUSTOM_BROWSER_BAR) {
1958 mTitleBar.removeTab(mTabControl.getTabIndex(t));
1959 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001960 mTabControl.removeTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001961 }
1962
1963 private void goBackOnePageOrQuit() {
1964 TabControl.Tab current = mTabControl.getCurrentTab();
1965 if (current == null) {
1966 /*
1967 * Instead of finishing the activity, simply push this to the back
1968 * of the stack and let ActivityManager to choose the foreground
1969 * activity. As BrowserActivity is singleTask, it will be always the
1970 * root of the task. So we can use either true or false for
1971 * moveTaskToBack().
1972 */
1973 moveTaskToBack(true);
1974 }
1975 WebView w = current.getWebView();
1976 if (w.canGoBack()) {
1977 w.goBack();
1978 } else {
1979 // Check to see if we are closing a window that was created by
1980 // another window. If so, we switch back to that window.
1981 TabControl.Tab parent = current.getParentTab();
1982 if (parent != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001983 switchToTab(mTabControl.getTabIndex(parent));
1984 // Now we close the other tab
1985 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001986 } else {
1987 if (current.closeOnExit()) {
Grace Klobabb0af5c2009-09-01 00:56:09 -07001988 // force mPageStarted to be false as we are going to either
1989 // finish the activity or remove the tab. This will ensure
1990 // pauseWebView() taking action.
1991 mPageStarted = false;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001992 if (mTabControl.getTabCount() == 1) {
1993 finish();
1994 return;
1995 }
Mike Reed7bfa63b2009-05-28 11:08:32 -04001996 // call pauseWebViewTimers() now, we won't be able to call
1997 // it in onPause() as the WebView won't be valid.
Grace Klobaec1b5ad2009-08-18 08:42:32 -07001998 // Temporarily change mActivityInPause to be true as
1999 // pauseWebViewTimers() will do nothing if mActivityInPause
2000 // is false.
Grace Kloba918e1d72009-08-13 14:55:06 -07002001 boolean savedState = mActivityInPause;
2002 if (savedState) {
Grace Klobaec1b5ad2009-08-18 08:42:32 -07002003 Log.e(LOGTAG, "BrowserActivity is already paused "
2004 + "while handing goBackOnePageOrQuit.");
Grace Kloba918e1d72009-08-13 14:55:06 -07002005 }
2006 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002007 pauseWebViewTimers();
Grace Kloba918e1d72009-08-13 14:55:06 -07002008 mActivityInPause = savedState;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002009 removeTabFromContentView(current);
2010 mTabControl.removeTab(current);
2011 }
2012 /*
2013 * Instead of finishing the activity, simply push this to the back
2014 * of the stack and let ActivityManager to choose the foreground
2015 * activity. As BrowserActivity is singleTask, it will be always the
2016 * root of the task. So we can use either true or false for
2017 * moveTaskToBack().
2018 */
2019 moveTaskToBack(true);
2020 }
2021 }
2022 }
2023
2024 public KeyTracker.State onKeyTracker(int keyCode,
2025 KeyEvent event,
2026 KeyTracker.Stage stage,
2027 int duration) {
2028 // if onKeyTracker() is called after activity onStop()
2029 // because of accumulated key events,
2030 // we should ignore it as browser is not active any more.
2031 WebView topWindow = getTopWindow();
Andrei Popescuadc008d2009-06-26 14:11:30 +01002032 if (topWindow == null && mCustomView == null)
The Android Open Source Project0c908882009-03-03 19:32:16 -08002033 return KeyTracker.State.NOT_TRACKING;
2034
2035 if (keyCode == KeyEvent.KEYCODE_BACK) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01002036 // Check if a custom view is currently showing and, if it is, hide it.
2037 if (mCustomView != null) {
2038 mWebChromeClient.onHideCustomView();
2039 return KeyTracker.State.DONE_TRACKING;
2040 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002041 if (stage == KeyTracker.Stage.LONG_REPEAT) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04002042 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002043 return KeyTracker.State.DONE_TRACKING;
2044 } else if (stage == KeyTracker.Stage.UP) {
2045 // FIXME: Currently, we do not have a notion of the
2046 // history picker for the subwindow, but maybe we
2047 // should?
2048 WebView subwindow = mTabControl.getCurrentSubWindow();
2049 if (subwindow != null) {
2050 if (subwindow.canGoBack()) {
2051 subwindow.goBack();
2052 } else {
2053 dismissSubWindow(mTabControl.getCurrentTab());
2054 }
2055 } else {
2056 goBackOnePageOrQuit();
2057 }
2058 return KeyTracker.State.DONE_TRACKING;
2059 }
2060 return KeyTracker.State.KEEP_TRACKING;
2061 }
2062 return KeyTracker.State.NOT_TRACKING;
2063 }
2064
2065 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2066 if (keyCode == KeyEvent.KEYCODE_MENU) {
2067 mMenuIsDown = true;
Grace Kloba6ee9c492009-07-13 10:04:34 -07002068 } else if (mMenuIsDown) {
2069 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2070 // still down, we don't want to trigger the search. Pretend to
2071 // consume the key and do nothing.
2072 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002073 }
2074 boolean handled = mKeyTracker.doKeyDown(keyCode, event);
2075 if (!handled) {
2076 switch (keyCode) {
2077 case KeyEvent.KEYCODE_SPACE:
2078 if (event.isShiftPressed()) {
2079 getTopWindow().pageUp(false);
2080 } else {
2081 getTopWindow().pageDown(false);
2082 }
2083 handled = true;
2084 break;
2085
2086 default:
2087 break;
2088 }
2089 }
2090 return handled || super.onKeyDown(keyCode, event);
2091 }
2092
2093 @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2094 if (keyCode == KeyEvent.KEYCODE_MENU) {
2095 mMenuIsDown = false;
2096 }
2097 return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2098 }
2099
2100 private void stopLoading() {
2101 resetTitleAndRevertLockIcon();
2102 WebView w = getTopWindow();
2103 w.stopLoading();
2104 mWebViewClient.onPageFinished(w, w.getUrl());
2105
2106 cancelStopToast();
2107 mStopToast = Toast
2108 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2109 mStopToast.show();
2110 }
2111
2112 private void cancelStopToast() {
2113 if (mStopToast != null) {
2114 mStopToast.cancel();
2115 mStopToast = null;
2116 }
2117 }
2118
2119 // called by a non-UI thread to post the message
2120 public void postMessage(int what, int arg1, int arg2, Object obj) {
2121 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2122 }
2123
2124 // public message ids
2125 public final static int LOAD_URL = 1001;
2126 public final static int STOP_LOAD = 1002;
2127
2128 // Message Ids
2129 private static final int FOCUS_NODE_HREF = 102;
2130 private static final int CANCEL_CREDS_REQUEST = 103;
Grace Kloba92c18a52009-07-31 23:48:32 -07002131 private static final int RELEASE_WAKELOCK = 107;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002132
2133 // Private handler for handling javascript and saving passwords
2134 private Handler mHandler = new Handler() {
2135
2136 public void handleMessage(Message msg) {
2137 switch (msg.what) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002138 case FOCUS_NODE_HREF:
2139 String url = (String) msg.getData().get("url");
2140 if (url == null || url.length() == 0) {
2141 break;
2142 }
2143 HashMap focusNodeMap = (HashMap) msg.obj;
2144 WebView view = (WebView) focusNodeMap.get("webview");
2145 // Only apply the action if the top window did not change.
2146 if (getTopWindow() != view) {
2147 break;
2148 }
2149 switch (msg.arg1) {
2150 case R.id.open_context_menu_id:
2151 case R.id.view_image_context_menu_id:
2152 loadURL(getTopWindow(), url);
2153 break;
2154 case R.id.open_newtab_context_menu_id:
Grace Klobac9181842009-04-14 08:53:22 -07002155 final TabControl.Tab parent = mTabControl
2156 .getCurrentTab();
2157 final TabControl.Tab newTab = openTab(url);
2158 if (newTab != parent) {
2159 parent.addChildTab(newTab);
2160 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002161 break;
2162 case R.id.bookmark_context_menu_id:
2163 Intent intent = new Intent(BrowserActivity.this,
2164 AddBookmarkPage.class);
2165 intent.putExtra("url", url);
2166 startActivity(intent);
2167 break;
2168 case R.id.share_link_context_menu_id:
2169 Browser.sendString(BrowserActivity.this, url);
2170 break;
2171 case R.id.copy_link_context_menu_id:
2172 copy(url);
2173 break;
2174 case R.id.save_link_context_menu_id:
2175 case R.id.download_context_menu_id:
2176 onDownloadStartNoStream(url, null, null, null, -1);
2177 break;
2178 }
2179 break;
2180
2181 case LOAD_URL:
2182 loadURL(getTopWindow(), (String) msg.obj);
2183 break;
2184
2185 case STOP_LOAD:
2186 stopLoading();
2187 break;
2188
2189 case CANCEL_CREDS_REQUEST:
2190 resumeAfterCredentials();
2191 break;
2192
The Android Open Source Project0c908882009-03-03 19:32:16 -08002193 case RELEASE_WAKELOCK:
2194 if (mWakeLock.isHeld()) {
2195 mWakeLock.release();
2196 }
2197 break;
2198 }
2199 }
2200 };
2201
Leon Scroggins89c6d362009-07-15 16:54:37 -04002202 private void updateScreenshot(WebView view) {
2203 // If this is a bookmarked site, add a screenshot to the database.
2204 // FIXME: When should we update? Every time?
2205 // FIXME: Would like to make sure there is actually something to
2206 // draw, but the API for that (WebViewCore.pictureReady()) is not
2207 // currently accessible here.
Patrick Scott3918d442009-08-04 13:22:29 -04002208 ContentResolver cr = getContentResolver();
2209 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04002210 cr, view.getOriginalUrl(), view.getUrl(), false);
Patrick Scott3918d442009-08-04 13:22:29 -04002211 if (c != null) {
Leon Scroggins89c6d362009-07-15 16:54:37 -04002212 boolean succeed = c.moveToFirst();
2213 ContentValues values = null;
2214 while (succeed) {
2215 if (values == null) {
2216 final ByteArrayOutputStream os
2217 = new ByteArrayOutputStream();
2218 Picture thumbnail = view.capturePicture();
2219 // Keep width and height in sync with BrowserBookmarksPage
2220 // and bookmark_thumb
2221 Bitmap bm = Bitmap.createBitmap(100, 80,
2222 Bitmap.Config.ARGB_4444);
2223 Canvas canvas = new Canvas(bm);
2224 // May need to tweak these values to determine what is the
2225 // best scale factor
2226 canvas.scale(.5f, .5f);
2227 thumbnail.draw(canvas);
2228 bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2229 values = new ContentValues();
2230 values.put(Browser.BookmarkColumns.THUMBNAIL,
2231 os.toByteArray());
2232 }
2233 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2234 c.getInt(0)), values, null, null);
2235 succeed = c.moveToNext();
2236 }
2237 c.close();
2238 }
2239 }
2240
The Android Open Source Project0c908882009-03-03 19:32:16 -08002241 // -------------------------------------------------------------------------
2242 // WebViewClient implementation.
2243 //-------------------------------------------------------------------------
2244
2245 // Use in overrideUrlLoading
2246 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2247 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2248 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2249 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2250
2251 /* package */ WebViewClient getWebViewClient() {
2252 return mWebViewClient;
2253 }
2254
Patrick Scott3918d442009-08-04 13:22:29 -04002255 private void updateIcon(WebView view, Bitmap icon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002256 if (icon != null) {
2257 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
Patrick Scott3918d442009-08-04 13:22:29 -04002258 view, icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002259 }
2260 setFavicon(icon);
2261 }
2262
2263 private final WebViewClient mWebViewClient = new WebViewClient() {
2264 @Override
2265 public void onPageStarted(WebView view, String url, Bitmap favicon) {
2266 resetLockIcon(url);
Leon Scroggins1f005d32009-08-10 17:36:42 -04002267 setUrlTitle(url, null, view);
Ben Murdochbff2d602009-07-01 20:19:05 +01002268
2269 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2270 if (errorConsole != null) {
2271 errorConsole.clearErrorMessages();
2272 if (mShouldShowErrorConsole) {
2273 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2274 }
2275 }
2276
The Android Open Source Project0c908882009-03-03 19:32:16 -08002277 // Call updateIcon instead of setFavicon so the bookmark
2278 // database can be updated.
Patrick Scott3918d442009-08-04 13:22:29 -04002279 updateIcon(view, favicon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002280
Grace Kloba4d7880f2009-08-12 09:35:42 -07002281 if (mSettings.isTracing()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002282 String host;
2283 try {
2284 WebAddress uri = new WebAddress(url);
2285 host = uri.mHost;
2286 } catch (android.net.ParseException ex) {
Grace Kloba4d7880f2009-08-12 09:35:42 -07002287 host = "browser";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002288 }
2289 host = host.replace('.', '_');
Grace Kloba4d7880f2009-08-12 09:35:42 -07002290 host += ".trace";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002291 mInTrace = true;
Grace Kloba4d7880f2009-08-12 09:35:42 -07002292 Debug.startMethodTracing(host, 20 * 1024 * 1024);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002293 }
2294
2295 // Performance probe
2296 if (false) {
2297 mStart = SystemClock.uptimeMillis();
2298 mProcessStart = Process.getElapsedCpuTime();
2299 long[] sysCpu = new long[7];
2300 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2301 sysCpu, null)) {
2302 mUserStart = sysCpu[0] + sysCpu[1];
2303 mSystemStart = sysCpu[2];
2304 mIdleStart = sysCpu[3];
2305 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2306 }
2307 mUiStart = SystemClock.currentThreadTimeMillis();
2308 }
2309
2310 if (!mPageStarted) {
2311 mPageStarted = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002312 // if onResume() has been called, resumeWebViewTimers() does
2313 // nothing.
2314 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002315 }
2316
2317 // reset sync timer to avoid sync starts during loading a page
2318 CookieSyncManager.getInstance().resetSync();
2319
2320 mInLoad = true;
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002321 if (CUSTOM_BROWSER_BAR) {
2322 mTitleBar.setVisibility(View.VISIBLE);
2323 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002324 updateInLoadMenuItems();
2325 if (!mIsNetworkUp) {
2326 if ( mAlertDialog == null) {
2327 mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
2328 .setTitle(R.string.loadSuspendedTitle)
2329 .setMessage(R.string.loadSuspended)
2330 .setPositiveButton(R.string.ok, null)
2331 .show();
2332 }
2333 if (view != null) {
2334 view.setNetworkAvailable(false);
2335 }
2336 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002337 }
2338
2339 @Override
2340 public void onPageFinished(WebView view, String url) {
2341 // Reset the title and icon in case we stopped a provisional
2342 // load.
2343 resetTitleAndIcon(view);
2344
2345 // Update the lock icon image only once we are done loading
2346 updateLockIconImage(mLockIconType);
Leon Scroggins89c6d362009-07-15 16:54:37 -04002347 updateScreenshot(view);
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -04002348
The Android Open Source Project0c908882009-03-03 19:32:16 -08002349 // Performance probe
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002350 if (false) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002351 long[] sysCpu = new long[7];
2352 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2353 sysCpu, null)) {
2354 String uiInfo = "UI thread used "
2355 + (SystemClock.currentThreadTimeMillis() - mUiStart)
2356 + " ms";
Dave Bort31a6d1c2009-04-13 15:56:49 -07002357 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002358 Log.d(LOGTAG, uiInfo);
2359 }
2360 //The string that gets written to the log
2361 String performanceString = "It took total "
2362 + (SystemClock.uptimeMillis() - mStart)
2363 + " ms clock time to load the page."
2364 + "\nbrowser process used "
2365 + (Process.getElapsedCpuTime() - mProcessStart)
2366 + " ms, user processes used "
2367 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2368 + " ms, kernel used "
2369 + (sysCpu[2] - mSystemStart) * 10
2370 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2371 + " ms and irq took "
2372 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2373 * 10 + " ms, " + uiInfo;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002374 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002375 Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2376 }
2377 if (url != null) {
2378 // strip the url to maintain consistency
2379 String newUrl = new String(url);
2380 if (newUrl.startsWith("http://www.")) {
2381 newUrl = newUrl.substring(11);
2382 } else if (newUrl.startsWith("http://")) {
2383 newUrl = newUrl.substring(7);
2384 } else if (newUrl.startsWith("https://www.")) {
2385 newUrl = newUrl.substring(12);
2386 } else if (newUrl.startsWith("https://")) {
2387 newUrl = newUrl.substring(8);
2388 }
Dave Bort31a6d1c2009-04-13 15:56:49 -07002389 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002390 Log.d(LOGTAG, newUrl + " loaded");
2391 }
2392 /*
2393 if (sWhiteList.contains(newUrl)) {
2394 // The string that gets pushed to the statistcs
2395 // service
2396 performanceString = performanceString
2397 + "\nWebpage: "
2398 + newUrl
2399 + "\nCarrier: "
2400 + android.os.SystemProperties
2401 .get("gsm.sim.operator.alpha");
2402 if (mWebView != null
2403 && mWebView.getContext() != null
2404 && mWebView.getContext().getSystemService(
2405 Context.CONNECTIVITY_SERVICE) != null) {
2406 ConnectivityManager cManager =
2407 (ConnectivityManager) mWebView
2408 .getContext().getSystemService(
2409 Context.CONNECTIVITY_SERVICE);
2410 NetworkInfo nInfo = cManager
2411 .getActiveNetworkInfo();
2412 if (nInfo != null) {
2413 performanceString = performanceString
2414 + "\nNetwork Type: "
2415 + nInfo.getType().toString();
2416 }
2417 }
2418 Checkin.logEvent(mResolver,
2419 Checkin.Events.Tag.WEBPAGE_LOAD,
2420 performanceString);
2421 Log.w(LOGTAG, "pushed to the statistics service");
2422 }
2423 */
2424 }
2425 }
2426 }
2427
2428 if (mInTrace) {
2429 mInTrace = false;
2430 Debug.stopMethodTracing();
2431 }
2432
2433 if (mPageStarted) {
2434 mPageStarted = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002435 // pauseWebViewTimers() will do nothing and return false if
2436 // onPause() is not called yet.
2437 if (pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002438 if (mWakeLock.isHeld()) {
2439 mHandler.removeMessages(RELEASE_WAKELOCK);
2440 mWakeLock.release();
2441 }
2442 }
2443 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002444 }
2445
2446 // return true if want to hijack the url to let another app to handle it
2447 @Override
2448 public boolean shouldOverrideUrlLoading(WebView view, String url) {
2449 if (url.startsWith(SCHEME_WTAI)) {
2450 // wtai://wp/mc;number
2451 // number=string(phone-number)
2452 if (url.startsWith(SCHEME_WTAI_MC)) {
2453 Intent intent = new Intent(Intent.ACTION_VIEW,
2454 Uri.parse(WebView.SCHEME_TEL +
2455 url.substring(SCHEME_WTAI_MC.length())));
2456 startActivity(intent);
2457 return true;
2458 }
2459 // wtai://wp/sd;dtmf
2460 // dtmf=string(dialstring)
2461 if (url.startsWith(SCHEME_WTAI_SD)) {
2462 // TODO
2463 // only send when there is active voice connection
2464 return false;
2465 }
2466 // wtai://wp/ap;number;name
2467 // number=string(phone-number)
2468 // name=string
2469 if (url.startsWith(SCHEME_WTAI_AP)) {
2470 // TODO
2471 return false;
2472 }
2473 }
2474
Dianne Hackborn99189432009-06-17 18:06:18 -07002475 // The "about:" schemes are internal to the browser; don't
2476 // want these to be dispatched to other apps.
2477 if (url.startsWith("about:")) {
2478 return false;
2479 }
Ben Murdochbff2d602009-07-01 20:19:05 +01002480
Dianne Hackborn99189432009-06-17 18:06:18 -07002481 Intent intent;
Ben Murdochbff2d602009-07-01 20:19:05 +01002482
Dianne Hackborn99189432009-06-17 18:06:18 -07002483 // perform generic parsing of the URI to turn it into an Intent.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002484 try {
Dianne Hackborn99189432009-06-17 18:06:18 -07002485 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2486 } catch (URISyntaxException ex) {
2487 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
The Android Open Source Project0c908882009-03-03 19:32:16 -08002488 return false;
2489 }
2490
Grace Kloba5b078b52009-06-24 20:23:41 -07002491 // check whether the intent can be resolved. If not, we will see
2492 // whether we can download it from the Market.
2493 if (getPackageManager().resolveActivity(intent, 0) == null) {
2494 String packagename = intent.getPackage();
2495 if (packagename != null) {
2496 intent = new Intent(Intent.ACTION_VIEW, Uri
2497 .parse("market://search?q=pname:" + packagename));
2498 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2499 startActivity(intent);
2500 return true;
2501 } else {
2502 return false;
2503 }
2504 }
2505
Dianne Hackborn99189432009-06-17 18:06:18 -07002506 // sanitize the Intent, ensuring web pages can not bypass browser
2507 // security (only access to BROWSABLE activities).
The Android Open Source Project0c908882009-03-03 19:32:16 -08002508 intent.addCategory(Intent.CATEGORY_BROWSABLE);
Dianne Hackborn99189432009-06-17 18:06:18 -07002509 intent.setComponent(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002510 try {
2511 if (startActivityIfNeeded(intent, -1)) {
2512 return true;
2513 }
2514 } catch (ActivityNotFoundException ex) {
2515 // ignore the error. If no application can handle the URL,
2516 // eg about:blank, assume the browser can handle it.
2517 }
2518
2519 if (mMenuIsDown) {
2520 openTab(url);
2521 closeOptionsMenu();
2522 return true;
2523 }
2524
2525 return false;
2526 }
2527
2528 /**
2529 * Updates the lock icon. This method is called when we discover another
2530 * resource to be loaded for this page (for example, javascript). While
2531 * we update the icon type, we do not update the lock icon itself until
2532 * we are done loading, it is slightly more secure this way.
2533 */
2534 @Override
2535 public void onLoadResource(WebView view, String url) {
2536 if (url != null && url.length() > 0) {
2537 // It is only if the page claims to be secure
2538 // that we may have to update the lock:
2539 if (mLockIconType == LOCK_ICON_SECURE) {
2540 // If NOT a 'safe' url, change the lock to mixed content!
2541 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2542 mLockIconType = LOCK_ICON_MIXED;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002543 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002544 Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2545 " updated lock icon to " + mLockIconType + " due to " + url);
2546 }
2547 }
2548 }
2549 }
2550 }
2551
2552 /**
2553 * Show the dialog, asking the user if they would like to continue after
2554 * an excessive number of HTTP redirects.
2555 */
2556 @Override
2557 public void onTooManyRedirects(WebView view, final Message cancelMsg,
2558 final Message continueMsg) {
2559 new AlertDialog.Builder(BrowserActivity.this)
2560 .setTitle(R.string.browserFrameRedirect)
2561 .setMessage(R.string.browserFrame307Post)
2562 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2563 public void onClick(DialogInterface dialog, int which) {
2564 continueMsg.sendToTarget();
2565 }})
2566 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2567 public void onClick(DialogInterface dialog, int which) {
2568 cancelMsg.sendToTarget();
2569 }})
2570 .setOnCancelListener(new OnCancelListener() {
2571 public void onCancel(DialogInterface dialog) {
2572 cancelMsg.sendToTarget();
2573 }})
2574 .show();
2575 }
2576
Patrick Scott37911c72009-03-24 18:02:58 -07002577 // Container class for the next error dialog that needs to be
2578 // displayed.
2579 class ErrorDialog {
2580 public final int mTitle;
2581 public final String mDescription;
2582 public final int mError;
2583 ErrorDialog(int title, String desc, int error) {
2584 mTitle = title;
2585 mDescription = desc;
2586 mError = error;
2587 }
2588 };
2589
2590 private void processNextError() {
2591 if (mQueuedErrors == null) {
2592 return;
2593 }
2594 // The first one is currently displayed so just remove it.
2595 mQueuedErrors.removeFirst();
2596 if (mQueuedErrors.size() == 0) {
2597 mQueuedErrors = null;
2598 return;
2599 }
2600 showError(mQueuedErrors.getFirst());
2601 }
2602
2603 private DialogInterface.OnDismissListener mDialogListener =
2604 new DialogInterface.OnDismissListener() {
2605 public void onDismiss(DialogInterface d) {
2606 processNextError();
2607 }
2608 };
2609 private LinkedList<ErrorDialog> mQueuedErrors;
2610
2611 private void queueError(int err, String desc) {
2612 if (mQueuedErrors == null) {
2613 mQueuedErrors = new LinkedList<ErrorDialog>();
2614 }
2615 for (ErrorDialog d : mQueuedErrors) {
2616 if (d.mError == err) {
2617 // Already saw a similar error, ignore the new one.
2618 return;
2619 }
2620 }
2621 ErrorDialog errDialog = new ErrorDialog(
Patrick Scott5d61a6c2009-08-25 13:52:46 -04002622 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
Patrick Scott37911c72009-03-24 18:02:58 -07002623 R.string.browserFrameFileErrorLabel :
2624 R.string.browserFrameNetworkErrorLabel,
2625 desc, err);
2626 mQueuedErrors.addLast(errDialog);
2627
2628 // Show the dialog now if the queue was empty.
2629 if (mQueuedErrors.size() == 1) {
2630 showError(errDialog);
2631 }
2632 }
2633
2634 private void showError(ErrorDialog errDialog) {
2635 AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
2636 .setTitle(errDialog.mTitle)
2637 .setMessage(errDialog.mDescription)
2638 .setPositiveButton(R.string.ok, null)
2639 .create();
2640 d.setOnDismissListener(mDialogListener);
2641 d.show();
2642 }
2643
The Android Open Source Project0c908882009-03-03 19:32:16 -08002644 /**
2645 * Show a dialog informing the user of the network error reported by
2646 * WebCore.
2647 */
2648 @Override
2649 public void onReceivedError(WebView view, int errorCode,
2650 String description, String failingUrl) {
Patrick Scott5d61a6c2009-08-25 13:52:46 -04002651 if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
2652 errorCode != WebViewClient.ERROR_CONNECT &&
2653 errorCode != WebViewClient.ERROR_BAD_URL &&
2654 errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
2655 errorCode != WebViewClient.ERROR_FILE) {
Patrick Scott37911c72009-03-24 18:02:58 -07002656 queueError(errorCode, description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002657 }
Patrick Scott37911c72009-03-24 18:02:58 -07002658 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
2659 + " " + description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002660
2661 // We need to reset the title after an error.
2662 resetTitleAndRevertLockIcon();
2663 }
2664
2665 /**
2666 * Check with the user if it is ok to resend POST data as the page they
2667 * are trying to navigate to is the result of a POST.
2668 */
2669 @Override
2670 public void onFormResubmission(WebView view, final Message dontResend,
2671 final Message resend) {
2672 new AlertDialog.Builder(BrowserActivity.this)
2673 .setTitle(R.string.browserFrameFormResubmitLabel)
2674 .setMessage(R.string.browserFrameFormResubmitMessage)
2675 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2676 public void onClick(DialogInterface dialog, int which) {
2677 resend.sendToTarget();
2678 }})
2679 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2680 public void onClick(DialogInterface dialog, int which) {
2681 dontResend.sendToTarget();
2682 }})
2683 .setOnCancelListener(new OnCancelListener() {
2684 public void onCancel(DialogInterface dialog) {
2685 dontResend.sendToTarget();
2686 }})
2687 .show();
2688 }
2689
2690 /**
2691 * Insert the url into the visited history database.
2692 * @param url The url to be inserted.
2693 * @param isReload True if this url is being reloaded.
2694 * FIXME: Not sure what to do when reloading the page.
2695 */
2696 @Override
2697 public void doUpdateVisitedHistory(WebView view, String url,
2698 boolean isReload) {
2699 if (url.regionMatches(true, 0, "about:", 0, 6)) {
2700 return;
2701 }
2702 Browser.updateVisitedHistory(mResolver, url, true);
2703 WebIconDatabase.getInstance().retainIconForPageUrl(url);
2704 }
2705
2706 /**
2707 * Displays SSL error(s) dialog to the user.
2708 */
2709 @Override
2710 public void onReceivedSslError(
2711 final WebView view, final SslErrorHandler handler, final SslError error) {
2712
2713 if (mSettings.showSecurityWarnings()) {
2714 final LayoutInflater factory =
2715 LayoutInflater.from(BrowserActivity.this);
2716 final View warningsView =
2717 factory.inflate(R.layout.ssl_warnings, null);
2718 final LinearLayout placeholder =
2719 (LinearLayout)warningsView.findViewById(R.id.placeholder);
2720
2721 if (error.hasError(SslError.SSL_UNTRUSTED)) {
2722 LinearLayout ll = (LinearLayout)factory
2723 .inflate(R.layout.ssl_warning, null);
2724 ((TextView)ll.findViewById(R.id.warning))
2725 .setText(R.string.ssl_untrusted);
2726 placeholder.addView(ll);
2727 }
2728
2729 if (error.hasError(SslError.SSL_IDMISMATCH)) {
2730 LinearLayout ll = (LinearLayout)factory
2731 .inflate(R.layout.ssl_warning, null);
2732 ((TextView)ll.findViewById(R.id.warning))
2733 .setText(R.string.ssl_mismatch);
2734 placeholder.addView(ll);
2735 }
2736
2737 if (error.hasError(SslError.SSL_EXPIRED)) {
2738 LinearLayout ll = (LinearLayout)factory
2739 .inflate(R.layout.ssl_warning, null);
2740 ((TextView)ll.findViewById(R.id.warning))
2741 .setText(R.string.ssl_expired);
2742 placeholder.addView(ll);
2743 }
2744
2745 if (error.hasError(SslError.SSL_NOTYETVALID)) {
2746 LinearLayout ll = (LinearLayout)factory
2747 .inflate(R.layout.ssl_warning, null);
2748 ((TextView)ll.findViewById(R.id.warning))
2749 .setText(R.string.ssl_not_yet_valid);
2750 placeholder.addView(ll);
2751 }
2752
2753 new AlertDialog.Builder(BrowserActivity.this)
2754 .setTitle(R.string.security_warning)
2755 .setIcon(android.R.drawable.ic_dialog_alert)
2756 .setView(warningsView)
2757 .setPositiveButton(R.string.ssl_continue,
2758 new DialogInterface.OnClickListener() {
2759 public void onClick(DialogInterface dialog, int whichButton) {
2760 handler.proceed();
2761 }
2762 })
2763 .setNeutralButton(R.string.view_certificate,
2764 new DialogInterface.OnClickListener() {
2765 public void onClick(DialogInterface dialog, int whichButton) {
2766 showSSLCertificateOnError(view, handler, error);
2767 }
2768 })
2769 .setNegativeButton(R.string.cancel,
2770 new DialogInterface.OnClickListener() {
2771 public void onClick(DialogInterface dialog, int whichButton) {
2772 handler.cancel();
2773 BrowserActivity.this.resetTitleAndRevertLockIcon();
2774 }
2775 })
2776 .setOnCancelListener(
2777 new DialogInterface.OnCancelListener() {
2778 public void onCancel(DialogInterface dialog) {
2779 handler.cancel();
2780 BrowserActivity.this.resetTitleAndRevertLockIcon();
2781 }
2782 })
2783 .show();
2784 } else {
2785 handler.proceed();
2786 }
2787 }
2788
2789 /**
2790 * Handles an HTTP authentication request.
2791 *
2792 * @param handler The authentication handler
2793 * @param host The host
2794 * @param realm The realm
2795 */
2796 @Override
2797 public void onReceivedHttpAuthRequest(WebView view,
2798 final HttpAuthHandler handler, final String host, final String realm) {
2799 String username = null;
2800 String password = null;
2801
2802 boolean reuseHttpAuthUsernamePassword =
2803 handler.useHttpAuthUsernamePassword();
2804
2805 if (reuseHttpAuthUsernamePassword &&
2806 (mTabControl.getCurrentWebView() != null)) {
2807 String[] credentials =
2808 mTabControl.getCurrentWebView()
2809 .getHttpAuthUsernamePassword(host, realm);
2810 if (credentials != null && credentials.length == 2) {
2811 username = credentials[0];
2812 password = credentials[1];
2813 }
2814 }
2815
2816 if (username != null && password != null) {
2817 handler.proceed(username, password);
2818 } else {
2819 showHttpAuthentication(handler, host, realm, null, null, null, 0);
2820 }
2821 }
2822
2823 @Override
2824 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
2825 if (mMenuIsDown) {
2826 // only check shortcut key when MENU is held
2827 return getWindow().isShortcutKey(event.getKeyCode(), event);
2828 } else {
2829 return false;
2830 }
2831 }
2832
2833 @Override
2834 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
2835 if (view != mTabControl.getCurrentTopWebView()) {
2836 return;
2837 }
2838 if (event.isDown()) {
2839 BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
2840 } else {
2841 BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
2842 }
2843 }
2844 };
2845
2846 //--------------------------------------------------------------------------
2847 // WebChromeClient implementation
2848 //--------------------------------------------------------------------------
2849
2850 /* package */ WebChromeClient getWebChromeClient() {
2851 return mWebChromeClient;
2852 }
2853
2854 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
2855 // Helper method to create a new tab or sub window.
2856 private void createWindow(final boolean dialog, final Message msg) {
2857 if (dialog) {
2858 mTabControl.createSubWindow();
2859 final TabControl.Tab t = mTabControl.getCurrentTab();
2860 attachSubWindow(t);
2861 WebView.WebViewTransport transport =
2862 (WebView.WebViewTransport) msg.obj;
2863 transport.setWebView(t.getSubWebView());
2864 msg.sendToTarget();
2865 } else {
2866 final TabControl.Tab parent = mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04002867 final TabControl.Tab newTab
2868 = openTabAndShow(EMPTY_URL_DATA, false, null);
Grace Klobac9181842009-04-14 08:53:22 -07002869 if (newTab != parent) {
2870 parent.addChildTab(newTab);
2871 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002872 WebView.WebViewTransport transport =
2873 (WebView.WebViewTransport) msg.obj;
2874 transport.setWebView(mTabControl.getCurrentWebView());
Leon Scroggins1f005d32009-08-10 17:36:42 -04002875 msg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002876 }
2877 }
2878
2879 @Override
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002880 public void onChangeViewingMode(WebView view, int newViewingMode) {
2881 if (!CUSTOM_BROWSER_BAR || view != getTopWindow()) {
Leon Scroggins4943a312009-08-07 16:12:57 -04002882 return;
2883 }
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002884 switch (newViewingMode) {
2885 case WebView.NO_VIEWING_MODE:
2886 break;
2887 case WebView.OVERVIEW_MODE:
2888 case WebView.READING_MODE_WITH_TITLE_BAR:
2889 case WebView.TITLE_BAR_DISMISS_MODE:
Leon Scroggins4943a312009-08-07 16:12:57 -04002890 mTitleBar.setVisibility(View.VISIBLE);
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002891 break;
2892 case WebView.READING_MODE:
Leon Scroggins4943a312009-08-07 16:12:57 -04002893 mTitleBar.setVisibility(View.GONE);
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002894 break;
2895 default:
2896 break;
Leon Scroggins4943a312009-08-07 16:12:57 -04002897 }
2898 }
2899
2900 @Override
The Android Open Source Project0c908882009-03-03 19:32:16 -08002901 public boolean onCreateWindow(WebView view, final boolean dialog,
2902 final boolean userGesture, final Message resultMsg) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002903 // Short-circuit if we can't create any more tabs or sub windows.
2904 if (dialog && mTabControl.getCurrentSubWindow() != null) {
2905 new AlertDialog.Builder(BrowserActivity.this)
2906 .setTitle(R.string.too_many_subwindows_dialog_title)
2907 .setIcon(android.R.drawable.ic_dialog_alert)
2908 .setMessage(R.string.too_many_subwindows_dialog_message)
2909 .setPositiveButton(R.string.ok, null)
2910 .show();
2911 return false;
2912 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
2913 new AlertDialog.Builder(BrowserActivity.this)
2914 .setTitle(R.string.too_many_windows_dialog_title)
2915 .setIcon(android.R.drawable.ic_dialog_alert)
2916 .setMessage(R.string.too_many_windows_dialog_message)
2917 .setPositiveButton(R.string.ok, null)
2918 .show();
2919 return false;
2920 }
2921
2922 // Short-circuit if this was a user gesture.
2923 if (userGesture) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002924 createWindow(dialog, resultMsg);
2925 return true;
2926 }
2927
2928 // Allow the popup and create the appropriate window.
2929 final AlertDialog.OnClickListener allowListener =
2930 new AlertDialog.OnClickListener() {
2931 public void onClick(DialogInterface d,
2932 int which) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002933 createWindow(dialog, resultMsg);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002934 }
2935 };
2936
2937 // Block the popup by returning a null WebView.
2938 final AlertDialog.OnClickListener blockListener =
2939 new AlertDialog.OnClickListener() {
2940 public void onClick(DialogInterface d, int which) {
2941 resultMsg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002942 }
2943 };
2944
2945 // Build a confirmation dialog to display to the user.
2946 final AlertDialog d =
2947 new AlertDialog.Builder(BrowserActivity.this)
2948 .setTitle(R.string.attention)
2949 .setIcon(android.R.drawable.ic_dialog_alert)
2950 .setMessage(R.string.popup_window_attempt)
2951 .setPositiveButton(R.string.allow, allowListener)
2952 .setNegativeButton(R.string.block, blockListener)
2953 .setCancelable(false)
2954 .create();
2955
2956 // Show the confirmation dialog.
2957 d.show();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002958 return true;
2959 }
2960
2961 @Override
2962 public void onCloseWindow(WebView window) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002963 final TabControl.Tab current = mTabControl.getCurrentTab();
2964 final TabControl.Tab parent = current.getParentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002965 if (parent != null) {
2966 // JavaScript can only close popup window.
Leon Scroggins1f005d32009-08-10 17:36:42 -04002967 switchToTab(mTabControl.getTabIndex(parent));
2968 // Now we need to close the window
2969 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002970 }
2971 }
2972
2973 @Override
2974 public void onProgressChanged(WebView view, int newProgress) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002975 if (CUSTOM_BROWSER_BAR) {
2976 mTitleBar.setProgress(newProgress, view);
2977 } else {
2978 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
2979 newProgress * 100);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002980 }
2981
2982 if (newProgress == 100) {
2983 // onProgressChanged() is called for sub-frame too while
2984 // onPageFinished() is only called for the main frame. sync
2985 // cookie and cache promptly here.
2986 CookieSyncManager.getInstance().sync();
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002987 if (mInLoad) {
2988 mInLoad = false;
2989 updateInLoadMenuItems();
2990 }
2991 } else {
2992 // onPageFinished may have already been called but a subframe
2993 // is still loading and updating the progress. Reset mInLoad
2994 // and update the menu items.
2995 if (!mInLoad) {
2996 mInLoad = true;
2997 updateInLoadMenuItems();
2998 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002999 }
3000 }
3001
3002 @Override
3003 public void onReceivedTitle(WebView view, String title) {
Patrick Scott598c9cc2009-06-04 11:10:38 -04003004 String url = view.getUrl();
The Android Open Source Project0c908882009-03-03 19:32:16 -08003005
3006 // here, if url is null, we want to reset the title
Leon Scroggins1f005d32009-08-10 17:36:42 -04003007 setUrlTitle(url, title, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003008
3009 if (url == null ||
3010 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
3011 return;
3012 }
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003013 // See if we can find the current url in our history database and
3014 // add the new title to it.
The Android Open Source Project0c908882009-03-03 19:32:16 -08003015 if (url.startsWith("http://www.")) {
3016 url = url.substring(11);
3017 } else if (url.startsWith("http://")) {
3018 url = url.substring(4);
3019 }
3020 try {
3021 url = "%" + url;
3022 String [] selArgs = new String[] { url };
3023
3024 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
3025 + Browser.BookmarkColumns.BOOKMARK + " = 0";
3026 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
3027 Browser.HISTORY_PROJECTION, where, selArgs, null);
3028 if (c.moveToFirst()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003029 // Current implementation of database only has one entry per
3030 // url.
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003031 ContentValues map = new ContentValues();
3032 map.put(Browser.BookmarkColumns.TITLE, title);
3033 mResolver.update(Browser.BOOKMARKS_URI, map,
3034 "_id = " + c.getInt(0), null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003035 }
3036 c.close();
3037 } catch (IllegalStateException e) {
3038 Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3039 } catch (SQLiteException ex) {
3040 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3041 }
3042 }
3043
3044 @Override
3045 public void onReceivedIcon(WebView view, Bitmap icon) {
Patrick Scott3918d442009-08-04 13:22:29 -04003046 updateIcon(view, icon);
3047 }
3048
3049 @Override
3050 public void onReceivedTouchIconUrl(WebView view, String url) {
3051 final ContentResolver cr = getContentResolver();
3052 final Cursor c =
3053 BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04003054 view.getOriginalUrl(), view.getUrl(), true);
Patrick Scott3918d442009-08-04 13:22:29 -04003055 if (c != null) {
3056 if (c.getCount() > 0) {
3057 new DownloadTouchIcon(cr, c, view).execute(url);
3058 } else {
3059 c.close();
3060 }
3061 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003062 }
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003063
Andrei Popescuadc008d2009-06-26 14:11:30 +01003064 @Override
Andrei Popescuc9b55562009-07-07 10:51:15 +01003065 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01003066 if (mCustomView != null)
3067 return;
3068
3069 // Add the custom view to its container.
3070 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
3071 mCustomView = view;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003072 mCustomViewCallback = callback;
Andrei Popescuadc008d2009-06-26 14:11:30 +01003073 // Save the menu state and set it to empty while the custom
3074 // view is showing.
3075 mOldMenuState = mMenuState;
3076 mMenuState = EMPTY_MENU;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003077 // Hide the content view.
3078 mContentView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003079 // Finally show the custom view container.
Andrei Popescuc9b55562009-07-07 10:51:15 +01003080 mCustomViewContainer.setVisibility(View.VISIBLE);
3081 mCustomViewContainer.bringToFront();
Andrei Popescuadc008d2009-06-26 14:11:30 +01003082 }
3083
3084 @Override
3085 public void onHideCustomView() {
3086 if (mCustomView == null)
3087 return;
3088
Andrei Popescuc9b55562009-07-07 10:51:15 +01003089 // Hide the custom view.
3090 mCustomView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003091 // Remove the custom view from its container.
3092 mCustomViewContainer.removeView(mCustomView);
3093 mCustomView = null;
3094 // Reset the old menu state.
3095 mMenuState = mOldMenuState;
3096 mOldMenuState = EMPTY_MENU;
3097 mCustomViewContainer.setVisibility(View.GONE);
Andrei Popescuc9b55562009-07-07 10:51:15 +01003098 mCustomViewCallback.onCustomViewHidden();
3099 // Show the content view.
3100 mContentView.setVisibility(View.VISIBLE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003101 }
3102
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003103 /**
Andrei Popescu79e82b72009-07-27 12:01:59 +01003104 * The origin has exceeded its database quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003105 * @param url the URL that exceeded the quota
3106 * @param databaseIdentifier the identifier of the database on
3107 * which the transaction that caused the quota overflow was run
3108 * @param currentQuota the current quota for the origin.
Ben Murdoch25a15232009-08-25 19:38:07 +01003109 * @param estimatedSize the estimated size of the database.
Andrei Popescu79e82b72009-07-27 12:01:59 +01003110 * @param totalUsedQuota is the sum of all origins' quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003111 * @param quotaUpdater The callback to run when a decision to allow or
3112 * deny quota has been made. Don't forget to call this!
3113 */
3114 @Override
3115 public void onExceededDatabaseQuota(String url,
Ben Murdoch25a15232009-08-25 19:38:07 +01003116 String databaseIdentifier, long currentQuota, long estimatedSize,
3117 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
Andrei Popescu79e82b72009-07-27 12:01:59 +01003118 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
Ben Murdoch25a15232009-08-25 19:38:07 +01003119 url, databaseIdentifier, currentQuota, estimatedSize,
3120 totalUsedQuota, quotaUpdater);
Andrei Popescu79e82b72009-07-27 12:01:59 +01003121 }
3122
3123 /**
3124 * The Application Cache has exceeded its max size.
3125 * @param spaceNeeded is the amount of disk space that would be needed
3126 * in order for the last appcache operation to succeed.
3127 * @param totalUsedQuota is the sum of all origins' quota.
3128 * @param quotaUpdater A callback to inform the WebCore thread that a new
3129 * app cache size is available. This callback must always be executed at
3130 * some point to ensure that the sleeping WebCore thread is woken up.
3131 */
3132 @Override
3133 public void onReachedMaxAppCacheSize(long spaceNeeded,
3134 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3135 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3136 spaceNeeded, totalUsedQuota, quotaUpdater);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003137 }
Ben Murdoch7db26342009-06-03 18:21:19 +01003138
Steve Block2bc69912009-07-30 14:45:13 +01003139 /**
3140 * Instructs the browser to show a prompt to ask the user to set the
3141 * Geolocation permission state for the specified origin.
3142 * @param origin The origin for which Geolocation permissions are
3143 * requested.
3144 * @param callback The callback to call once the user has set the
3145 * Geolocation permission state.
3146 */
3147 @Override
3148 public void onGeolocationPermissionsShowPrompt(String origin,
3149 GeolocationPermissions.Callback callback) {
3150 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3151 origin, callback);
3152 }
3153
3154 /**
3155 * Instructs the browser to hide the Geolocation permissions prompt.
3156 */
3157 @Override
3158 public void onGeolocationPermissionsHidePrompt() {
3159 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3160 }
3161
Ben Murdoch7db26342009-06-03 18:21:19 +01003162 /* Adds a JavaScript error message to the system log.
3163 * @param message The error message to report.
3164 * @param lineNumber The line number of the error.
3165 * @param sourceID The name of the source file that caused the error.
3166 */
3167 @Override
3168 public void addMessageToConsole(String message, int lineNumber, String sourceID) {
Ben Murdochbff2d602009-07-01 20:19:05 +01003169 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3170 errorConsole.addErrorMessage(message, sourceID, lineNumber);
3171 if (mShouldShowErrorConsole &&
3172 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3173 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3174 }
3175 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
Ben Murdoch7db26342009-06-03 18:21:19 +01003176 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003177 };
3178
3179 /**
3180 * Notify the host application a download should be done, or that
3181 * the data should be streamed if a streaming viewer is available.
3182 * @param url The full url to the content that should be downloaded
3183 * @param contentDisposition Content-disposition http header, if
3184 * present.
3185 * @param mimetype The mimetype of the content reported by the server
3186 * @param contentLength The file size reported by the server
3187 */
3188 public void onDownloadStart(String url, String userAgent,
3189 String contentDisposition, String mimetype, long contentLength) {
3190 // if we're dealing wih A/V content that's not explicitly marked
3191 // for download, check if it's streamable.
3192 if (contentDisposition == null
Patrick Scotte1fb9662009-08-31 14:31:52 -04003193 || !contentDisposition.regionMatches(
3194 true, 0, "attachment", 0, 10)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003195 // query the package manager to see if there's a registered handler
3196 // that matches.
3197 Intent intent = new Intent(Intent.ACTION_VIEW);
3198 intent.setDataAndType(Uri.parse(url), mimetype);
Patrick Scotte1fb9662009-08-31 14:31:52 -04003199 ResolveInfo info = getPackageManager().resolveActivity(intent,
3200 PackageManager.MATCH_DEFAULT_ONLY);
3201 if (info != null) {
3202 ComponentName myName = getComponentName();
3203 // If we resolved to ourselves, we don't want to attempt to
3204 // load the url only to try and download it again.
3205 if (!myName.getPackageName().equals(
3206 info.activityInfo.packageName)
3207 || !myName.getClassName().equals(
3208 info.activityInfo.name)) {
3209 // someone (other than us) knows how to handle this mime
3210 // type with this scheme, don't download.
3211 try {
3212 startActivity(intent);
3213 return;
3214 } catch (ActivityNotFoundException ex) {
3215 if (LOGD_ENABLED) {
3216 Log.d(LOGTAG, "activity not found for " + mimetype
3217 + " over " + Uri.parse(url).getScheme(),
3218 ex);
3219 }
3220 // Best behavior is to fall back to a download in this
3221 // case
The Android Open Source Project0c908882009-03-03 19:32:16 -08003222 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003223 }
3224 }
3225 }
3226 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3227 }
3228
3229 /**
3230 * Notify the host application a download should be done, even if there
3231 * is a streaming viewer available for thise type.
3232 * @param url The full url to the content that should be downloaded
3233 * @param contentDisposition Content-disposition http header, if
3234 * present.
3235 * @param mimetype The mimetype of the content reported by the server
3236 * @param contentLength The file size reported by the server
3237 */
3238 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3239 String contentDisposition, String mimetype, long contentLength) {
3240
3241 String filename = URLUtil.guessFileName(url,
3242 contentDisposition, mimetype);
3243
3244 // Check to see if we have an SDCard
3245 String status = Environment.getExternalStorageState();
3246 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3247 int title;
3248 String msg;
3249
3250 // Check to see if the SDCard is busy, same as the music app
3251 if (status.equals(Environment.MEDIA_SHARED)) {
3252 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3253 title = R.string.download_sdcard_busy_dlg_title;
3254 } else {
3255 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3256 title = R.string.download_no_sdcard_dlg_title;
3257 }
3258
3259 new AlertDialog.Builder(this)
3260 .setTitle(title)
3261 .setIcon(android.R.drawable.ic_dialog_alert)
3262 .setMessage(msg)
3263 .setPositiveButton(R.string.ok, null)
3264 .show();
3265 return;
3266 }
3267
3268 // java.net.URI is a lot stricter than KURL so we have to undo
3269 // KURL's percent-encoding and redo the encoding using java.net.URI.
3270 URI uri = null;
3271 try {
3272 // Undo the percent-encoding that KURL may have done.
3273 String newUrl = new String(URLUtil.decode(url.getBytes()));
3274 // Parse the url into pieces
3275 WebAddress w = new WebAddress(newUrl);
3276 String frag = null;
3277 String query = null;
3278 String path = w.mPath;
3279 // Break the path into path, query, and fragment
3280 if (path.length() > 0) {
3281 // Strip the fragment
3282 int idx = path.lastIndexOf('#');
3283 if (idx != -1) {
3284 frag = path.substring(idx + 1);
3285 path = path.substring(0, idx);
3286 }
3287 idx = path.lastIndexOf('?');
3288 if (idx != -1) {
3289 query = path.substring(idx + 1);
3290 path = path.substring(0, idx);
3291 }
3292 }
3293 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3294 query, frag);
3295 } catch (Exception e) {
3296 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3297 return;
3298 }
3299
3300 // XXX: Have to use the old url since the cookies were stored using the
3301 // old percent-encoded url.
3302 String cookies = CookieManager.getInstance().getCookie(url);
3303
3304 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003305 values.put(Downloads.COLUMN_URI, uri.toString());
3306 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3307 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3308 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003309 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003310 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003311 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003312 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3313 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3314 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3315 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003316 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003317 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003318 }
3319 if (mimetype == null) {
3320 // We must have long pressed on a link or image to download it. We
3321 // are not sure of the mimetype in this case, so do a head request
3322 new FetchUrlMimeType(this).execute(values);
3323 } else {
3324 final Uri contentUri =
3325 getContentResolver().insert(Downloads.CONTENT_URI, values);
3326 viewDownloads(contentUri);
3327 }
3328
3329 }
3330
3331 /**
3332 * Resets the lock icon. This method is called when we start a new load and
3333 * know the url to be loaded.
3334 */
3335 private void resetLockIcon(String url) {
3336 // Save the lock-icon state (we revert to it if the load gets cancelled)
3337 saveLockIcon();
3338
3339 mLockIconType = LOCK_ICON_UNSECURE;
3340 if (URLUtil.isHttpsUrl(url)) {
3341 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003342 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003343 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3344 " reset lock icon to " + mLockIconType);
3345 }
3346 }
3347
3348 updateLockIconImage(LOCK_ICON_UNSECURE);
3349 }
3350
3351 /**
3352 * Resets the lock icon. This method is called when the icon needs to be
3353 * reset but we do not know whether we are loading a secure or not secure
3354 * page.
3355 */
3356 private void resetLockIcon() {
3357 // Save the lock-icon state (we revert to it if the load gets cancelled)
3358 saveLockIcon();
3359
3360 mLockIconType = LOCK_ICON_UNSECURE;
3361
Dave Bort31a6d1c2009-04-13 15:56:49 -07003362 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003363 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3364 " reset lock icon to " + mLockIconType);
3365 }
3366
3367 updateLockIconImage(LOCK_ICON_UNSECURE);
3368 }
3369
3370 /**
3371 * Updates the lock-icon image in the title-bar.
3372 */
3373 private void updateLockIconImage(int lockIconType) {
3374 Drawable d = null;
3375 if (lockIconType == LOCK_ICON_SECURE) {
3376 d = mSecLockIcon;
3377 } else if (lockIconType == LOCK_ICON_MIXED) {
3378 d = mMixLockIcon;
3379 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04003380 if (CUSTOM_BROWSER_BAR) {
3381 mTitleBar.setLock(d, getTopWindow());
3382 } else {
3383 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003384 }
3385 }
3386
3387 /**
3388 * Displays a page-info dialog.
3389 * @param tab The tab to show info about
3390 * @param fromShowSSLCertificateOnError The flag that indicates whether
3391 * this dialog was opened from the SSL-certificate-on-error dialog or
3392 * not. This is important, since we need to know whether to return to
3393 * the parent dialog or simply dismiss.
3394 */
3395 private void showPageInfo(final TabControl.Tab tab,
3396 final boolean fromShowSSLCertificateOnError) {
3397 final LayoutInflater factory = LayoutInflater
3398 .from(this);
3399
3400 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3401
3402 final WebView view = tab.getWebView();
3403
3404 String url = null;
3405 String title = null;
3406
3407 if (view == null) {
3408 url = tab.getUrl();
3409 title = tab.getTitle();
3410 } else if (view == mTabControl.getCurrentWebView()) {
3411 // Use the cached title and url if this is the current WebView
3412 url = mUrl;
3413 title = mTitle;
3414 } else {
3415 url = view.getUrl();
3416 title = view.getTitle();
3417 }
3418
3419 if (url == null) {
3420 url = "";
3421 }
3422 if (title == null) {
3423 title = "";
3424 }
3425
3426 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3427 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3428
3429 mPageInfoView = tab;
3430 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3431
3432 AlertDialog.Builder alertDialogBuilder =
3433 new AlertDialog.Builder(this)
3434 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3435 .setView(pageInfoView)
3436 .setPositiveButton(
3437 R.string.ok,
3438 new DialogInterface.OnClickListener() {
3439 public void onClick(DialogInterface dialog,
3440 int whichButton) {
3441 mPageInfoDialog = null;
3442 mPageInfoView = null;
3443 mPageInfoFromShowSSLCertificateOnError = null;
3444
3445 // if we came here from the SSL error dialog
3446 if (fromShowSSLCertificateOnError) {
3447 // go back to the SSL error dialog
3448 showSSLCertificateOnError(
3449 mSSLCertificateOnErrorView,
3450 mSSLCertificateOnErrorHandler,
3451 mSSLCertificateOnErrorError);
3452 }
3453 }
3454 })
3455 .setOnCancelListener(
3456 new DialogInterface.OnCancelListener() {
3457 public void onCancel(DialogInterface dialog) {
3458 mPageInfoDialog = null;
3459 mPageInfoView = null;
3460 mPageInfoFromShowSSLCertificateOnError = null;
3461
3462 // if we came here from the SSL error dialog
3463 if (fromShowSSLCertificateOnError) {
3464 // go back to the SSL error dialog
3465 showSSLCertificateOnError(
3466 mSSLCertificateOnErrorView,
3467 mSSLCertificateOnErrorHandler,
3468 mSSLCertificateOnErrorError);
3469 }
3470 }
3471 });
3472
3473 // if we have a main top-level page SSL certificate set or a certificate
3474 // error
3475 if (fromShowSSLCertificateOnError ||
3476 (view != null && view.getCertificate() != null)) {
3477 // add a 'View Certificate' button
3478 alertDialogBuilder.setNeutralButton(
3479 R.string.view_certificate,
3480 new DialogInterface.OnClickListener() {
3481 public void onClick(DialogInterface dialog,
3482 int whichButton) {
3483 mPageInfoDialog = null;
3484 mPageInfoView = null;
3485 mPageInfoFromShowSSLCertificateOnError = null;
3486
3487 // if we came here from the SSL error dialog
3488 if (fromShowSSLCertificateOnError) {
3489 // go back to the SSL error dialog
3490 showSSLCertificateOnError(
3491 mSSLCertificateOnErrorView,
3492 mSSLCertificateOnErrorHandler,
3493 mSSLCertificateOnErrorError);
3494 } else {
3495 // otherwise, display the top-most certificate from
3496 // the chain
3497 if (view.getCertificate() != null) {
3498 showSSLCertificate(tab);
3499 }
3500 }
3501 }
3502 });
3503 }
3504
3505 mPageInfoDialog = alertDialogBuilder.show();
3506 }
3507
3508 /**
3509 * Displays the main top-level page SSL certificate dialog
3510 * (accessible from the Page-Info dialog).
3511 * @param tab The tab to show certificate for.
3512 */
3513 private void showSSLCertificate(final TabControl.Tab tab) {
3514 final View certificateView =
3515 inflateCertificateView(tab.getWebView().getCertificate());
3516 if (certificateView == null) {
3517 return;
3518 }
3519
3520 LayoutInflater factory = LayoutInflater.from(this);
3521
3522 final LinearLayout placeholder =
3523 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3524
3525 LinearLayout ll = (LinearLayout) factory.inflate(
3526 R.layout.ssl_success, placeholder);
3527 ((TextView)ll.findViewById(R.id.success))
3528 .setText(R.string.ssl_certificate_is_valid);
3529
3530 mSSLCertificateView = tab;
3531 mSSLCertificateDialog =
3532 new AlertDialog.Builder(this)
3533 .setTitle(R.string.ssl_certificate).setIcon(
3534 R.drawable.ic_dialog_browser_certificate_secure)
3535 .setView(certificateView)
3536 .setPositiveButton(R.string.ok,
3537 new DialogInterface.OnClickListener() {
3538 public void onClick(DialogInterface dialog,
3539 int whichButton) {
3540 mSSLCertificateDialog = null;
3541 mSSLCertificateView = null;
3542
3543 showPageInfo(tab, false);
3544 }
3545 })
3546 .setOnCancelListener(
3547 new DialogInterface.OnCancelListener() {
3548 public void onCancel(DialogInterface dialog) {
3549 mSSLCertificateDialog = null;
3550 mSSLCertificateView = null;
3551
3552 showPageInfo(tab, false);
3553 }
3554 })
3555 .show();
3556 }
3557
3558 /**
3559 * Displays the SSL error certificate dialog.
3560 * @param view The target web-view.
3561 * @param handler The SSL error handler responsible for cancelling the
3562 * connection that resulted in an SSL error or proceeding per user request.
3563 * @param error The SSL error object.
3564 */
3565 private void showSSLCertificateOnError(
3566 final WebView view, final SslErrorHandler handler, final SslError error) {
3567
3568 final View certificateView =
3569 inflateCertificateView(error.getCertificate());
3570 if (certificateView == null) {
3571 return;
3572 }
3573
3574 LayoutInflater factory = LayoutInflater.from(this);
3575
3576 final LinearLayout placeholder =
3577 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3578
3579 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3580 LinearLayout ll = (LinearLayout)factory
3581 .inflate(R.layout.ssl_warning, placeholder);
3582 ((TextView)ll.findViewById(R.id.warning))
3583 .setText(R.string.ssl_untrusted);
3584 }
3585
3586 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3587 LinearLayout ll = (LinearLayout)factory
3588 .inflate(R.layout.ssl_warning, placeholder);
3589 ((TextView)ll.findViewById(R.id.warning))
3590 .setText(R.string.ssl_mismatch);
3591 }
3592
3593 if (error.hasError(SslError.SSL_EXPIRED)) {
3594 LinearLayout ll = (LinearLayout)factory
3595 .inflate(R.layout.ssl_warning, placeholder);
3596 ((TextView)ll.findViewById(R.id.warning))
3597 .setText(R.string.ssl_expired);
3598 }
3599
3600 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3601 LinearLayout ll = (LinearLayout)factory
3602 .inflate(R.layout.ssl_warning, placeholder);
3603 ((TextView)ll.findViewById(R.id.warning))
3604 .setText(R.string.ssl_not_yet_valid);
3605 }
3606
3607 mSSLCertificateOnErrorHandler = handler;
3608 mSSLCertificateOnErrorView = view;
3609 mSSLCertificateOnErrorError = error;
3610 mSSLCertificateOnErrorDialog =
3611 new AlertDialog.Builder(this)
3612 .setTitle(R.string.ssl_certificate).setIcon(
3613 R.drawable.ic_dialog_browser_certificate_partially_secure)
3614 .setView(certificateView)
3615 .setPositiveButton(R.string.ok,
3616 new DialogInterface.OnClickListener() {
3617 public void onClick(DialogInterface dialog,
3618 int whichButton) {
3619 mSSLCertificateOnErrorDialog = null;
3620 mSSLCertificateOnErrorView = null;
3621 mSSLCertificateOnErrorHandler = null;
3622 mSSLCertificateOnErrorError = null;
3623
3624 mWebViewClient.onReceivedSslError(
3625 view, handler, error);
3626 }
3627 })
3628 .setNeutralButton(R.string.page_info_view,
3629 new DialogInterface.OnClickListener() {
3630 public void onClick(DialogInterface dialog,
3631 int whichButton) {
3632 mSSLCertificateOnErrorDialog = null;
3633
3634 // do not clear the dialog state: we will
3635 // need to show the dialog again once the
3636 // user is done exploring the page-info details
3637
3638 showPageInfo(mTabControl.getTabFromView(view),
3639 true);
3640 }
3641 })
3642 .setOnCancelListener(
3643 new DialogInterface.OnCancelListener() {
3644 public void onCancel(DialogInterface dialog) {
3645 mSSLCertificateOnErrorDialog = null;
3646 mSSLCertificateOnErrorView = null;
3647 mSSLCertificateOnErrorHandler = null;
3648 mSSLCertificateOnErrorError = null;
3649
3650 mWebViewClient.onReceivedSslError(
3651 view, handler, error);
3652 }
3653 })
3654 .show();
3655 }
3656
3657 /**
3658 * Inflates the SSL certificate view (helper method).
3659 * @param certificate The SSL certificate.
3660 * @return The resultant certificate view with issued-to, issued-by,
3661 * issued-on, expires-on, and possibly other fields set.
3662 * If the input certificate is null, returns null.
3663 */
3664 private View inflateCertificateView(SslCertificate certificate) {
3665 if (certificate == null) {
3666 return null;
3667 }
3668
3669 LayoutInflater factory = LayoutInflater.from(this);
3670
3671 View certificateView = factory.inflate(
3672 R.layout.ssl_certificate, null);
3673
3674 // issued to:
3675 SslCertificate.DName issuedTo = certificate.getIssuedTo();
3676 if (issuedTo != null) {
3677 ((TextView) certificateView.findViewById(R.id.to_common))
3678 .setText(issuedTo.getCName());
3679 ((TextView) certificateView.findViewById(R.id.to_org))
3680 .setText(issuedTo.getOName());
3681 ((TextView) certificateView.findViewById(R.id.to_org_unit))
3682 .setText(issuedTo.getUName());
3683 }
3684
3685 // issued by:
3686 SslCertificate.DName issuedBy = certificate.getIssuedBy();
3687 if (issuedBy != null) {
3688 ((TextView) certificateView.findViewById(R.id.by_common))
3689 .setText(issuedBy.getCName());
3690 ((TextView) certificateView.findViewById(R.id.by_org))
3691 .setText(issuedBy.getOName());
3692 ((TextView) certificateView.findViewById(R.id.by_org_unit))
3693 .setText(issuedBy.getUName());
3694 }
3695
3696 // issued on:
3697 String issuedOn = reformatCertificateDate(
3698 certificate.getValidNotBefore());
3699 ((TextView) certificateView.findViewById(R.id.issued_on))
3700 .setText(issuedOn);
3701
3702 // expires on:
3703 String expiresOn = reformatCertificateDate(
3704 certificate.getValidNotAfter());
3705 ((TextView) certificateView.findViewById(R.id.expires_on))
3706 .setText(expiresOn);
3707
3708 return certificateView;
3709 }
3710
3711 /**
3712 * Re-formats the certificate date (Date.toString()) string to
3713 * a properly localized date string.
3714 * @return Properly localized version of the certificate date string and
3715 * the original certificate date string if fails to localize.
3716 * If the original string is null, returns an empty string "".
3717 */
3718 private String reformatCertificateDate(String certificateDate) {
3719 String reformattedDate = null;
3720
3721 if (certificateDate != null) {
3722 Date date = null;
3723 try {
3724 date = java.text.DateFormat.getInstance().parse(certificateDate);
3725 } catch (ParseException e) {
3726 date = null;
3727 }
3728
3729 if (date != null) {
3730 reformattedDate =
3731 DateFormat.getDateFormat(this).format(date);
3732 }
3733 }
3734
3735 return reformattedDate != null ? reformattedDate :
3736 (certificateDate != null ? certificateDate : "");
3737 }
3738
3739 /**
3740 * Displays an http-authentication dialog.
3741 */
3742 private void showHttpAuthentication(final HttpAuthHandler handler,
3743 final String host, final String realm, final String title,
3744 final String name, final String password, int focusId) {
3745 LayoutInflater factory = LayoutInflater.from(this);
3746 final View v = factory
3747 .inflate(R.layout.http_authentication, null);
3748 if (name != null) {
3749 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3750 }
3751 if (password != null) {
3752 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3753 }
3754
3755 String titleText = title;
3756 if (titleText == null) {
3757 titleText = getText(R.string.sign_in_to).toString().replace(
3758 "%s1", host).replace("%s2", realm);
3759 }
3760
3761 mHttpAuthHandler = handler;
3762 AlertDialog dialog = new AlertDialog.Builder(this)
3763 .setTitle(titleText)
3764 .setIcon(android.R.drawable.ic_dialog_alert)
3765 .setView(v)
3766 .setPositiveButton(R.string.action,
3767 new DialogInterface.OnClickListener() {
3768 public void onClick(DialogInterface dialog,
3769 int whichButton) {
3770 String nm = ((EditText) v
3771 .findViewById(R.id.username_edit))
3772 .getText().toString();
3773 String pw = ((EditText) v
3774 .findViewById(R.id.password_edit))
3775 .getText().toString();
3776 BrowserActivity.this.setHttpAuthUsernamePassword
3777 (host, realm, nm, pw);
3778 handler.proceed(nm, pw);
3779 mHttpAuthenticationDialog = null;
3780 mHttpAuthHandler = null;
3781 }})
3782 .setNegativeButton(R.string.cancel,
3783 new DialogInterface.OnClickListener() {
3784 public void onClick(DialogInterface dialog,
3785 int whichButton) {
3786 handler.cancel();
3787 BrowserActivity.this.resetTitleAndRevertLockIcon();
3788 mHttpAuthenticationDialog = null;
3789 mHttpAuthHandler = null;
3790 }})
3791 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3792 public void onCancel(DialogInterface dialog) {
3793 handler.cancel();
3794 BrowserActivity.this.resetTitleAndRevertLockIcon();
3795 mHttpAuthenticationDialog = null;
3796 mHttpAuthHandler = null;
3797 }})
3798 .create();
3799 // Make the IME appear when the dialog is displayed if applicable.
3800 dialog.getWindow().setSoftInputMode(
3801 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3802 dialog.show();
3803 if (focusId != 0) {
3804 dialog.findViewById(focusId).requestFocus();
3805 } else {
3806 v.findViewById(R.id.username_edit).requestFocus();
3807 }
3808 mHttpAuthenticationDialog = dialog;
3809 }
3810
3811 public int getProgress() {
3812 WebView w = mTabControl.getCurrentWebView();
3813 if (w != null) {
3814 return w.getProgress();
3815 } else {
3816 return 100;
3817 }
3818 }
3819
3820 /**
3821 * Set HTTP authentication password.
3822 *
3823 * @param host The host for the password
3824 * @param realm The realm for the password
3825 * @param username The username for the password. If it is null, it means
3826 * password can't be saved.
3827 * @param password The password
3828 */
3829 public void setHttpAuthUsernamePassword(String host, String realm,
3830 String username,
3831 String password) {
3832 WebView w = mTabControl.getCurrentWebView();
3833 if (w != null) {
3834 w.setHttpAuthUsernamePassword(host, realm, username, password);
3835 }
3836 }
3837
3838 /**
3839 * connectivity manager says net has come or gone... inform the user
3840 * @param up true if net has come up, false if net has gone down
3841 */
3842 public void onNetworkToggle(boolean up) {
3843 if (up == mIsNetworkUp) {
3844 return;
3845 } else if (up) {
3846 mIsNetworkUp = true;
3847 if (mAlertDialog != null) {
3848 mAlertDialog.cancel();
3849 mAlertDialog = null;
3850 }
3851 } else {
3852 mIsNetworkUp = false;
3853 if (mInLoad && mAlertDialog == null) {
3854 mAlertDialog = new AlertDialog.Builder(this)
3855 .setTitle(R.string.loadSuspendedTitle)
3856 .setMessage(R.string.loadSuspended)
3857 .setPositiveButton(R.string.ok, null)
3858 .show();
3859 }
3860 }
3861 WebView w = mTabControl.getCurrentWebView();
3862 if (w != null) {
3863 w.setNetworkAvailable(up);
3864 }
3865 }
3866
3867 @Override
3868 protected void onActivityResult(int requestCode, int resultCode,
3869 Intent intent) {
3870 switch (requestCode) {
3871 case COMBO_PAGE:
3872 if (resultCode == RESULT_OK && intent != null) {
3873 String data = intent.getAction();
3874 Bundle extras = intent.getExtras();
3875 if (extras != null && extras.getBoolean("new_window", false)) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003876 final TabControl.Tab newTab = openTab(data);
3877 if (mSettings.openInBackground() &&
Leon Scroggins1f005d32009-08-10 17:36:42 -04003878 newTab != null) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003879 mTabControl.populatePickerData(newTab);
3880 mTabControl.setCurrentTab(newTab);
Leon Scroggins1f005d32009-08-10 17:36:42 -04003881 int newIndex = mTabControl.getCurrentIndex();
3882 if (CUSTOM_BROWSER_BAR) {
3883 mTitleBar.setCurrentTab(newIndex);
3884 }
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003885 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003886 } else {
3887 final TabControl.Tab currentTab =
3888 mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04003889 dismissSubWindow(currentTab);
3890 if (data != null && data.length() != 0) {
3891 getTopWindow().loadUrl(data);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003892 }
3893 }
Leon Scroggins39362092009-08-18 19:47:09 -04003894/*
3895 FIXME: Removing this breaks the behavior of pressing BACK from
3896 the Go page resulting in the window being closed. However, it
3897 needs to be removed so that the user can use the Search bar to
3898 enter a URL. Further, the Go behavior is going to change
3899 drastically, so this behavior may not last anyway.
Leon Scroggins160a7e72009-08-14 18:28:01 -04003900 } else if (resultCode == RESULT_CANCELED
3901 && mCancelGoPageMeansClose) {
3902 if (mTabControl.getTabCount() == 1) {
3903 // finish the Browser. When the Browser opens up again,
3904 // we will go through onCreate and once again open up
3905 // the Go page.
3906 finish();
3907 return;
3908 }
3909 closeCurrentWindow();
Leon Scroggins39362092009-08-18 19:47:09 -04003910*/
The Android Open Source Project0c908882009-03-03 19:32:16 -08003911 }
3912 break;
3913 default:
3914 break;
3915 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003916 mCancelGoPageMeansClose = false;
3917 if (getTopWindow() != null) {
3918 getTopWindow().requestFocus();
3919 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003920 }
3921
3922 /*
3923 * This method is called as a result of the user selecting the options
3924 * menu to see the download window, or when a download changes state. It
3925 * shows the download window ontop of the current window.
3926 */
3927 /* package */ void viewDownloads(Uri downloadRecord) {
3928 Intent intent = new Intent(this,
3929 BrowserDownloadPage.class);
3930 intent.setData(downloadRecord);
3931 startActivityForResult(intent, this.DOWNLOAD_PAGE);
3932
3933 }
3934
Leon Scroggins160a7e72009-08-14 18:28:01 -04003935 // True if canceling the "Go" screen should result in closing the current
3936 // window/browser.
3937 private boolean mCancelGoPageMeansClose;
3938
3939 /**
3940 * Open the Go page.
3941 * @param startWithHistory If true, open starting on the history tab.
3942 * Otherwise, start with the bookmarks tab.
3943 * @param cancelGoPageMeansClose Set to true if this came from a new tab, or
3944 * from the only tab, and canceling means to
3945 * close the tab (and possibly the browser)
3946 */
3947 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory,
3948 boolean cancelGoPageMeansClose) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003949 WebView current = mTabControl.getCurrentWebView();
3950 if (current == null) {
3951 return;
3952 }
3953 Intent intent = new Intent(this,
3954 CombinedBookmarkHistoryActivity.class);
3955 String title = current.getTitle();
3956 String url = current.getUrl();
3957 // Just in case the user opens bookmarks before a page finishes loading
3958 // so the current history item, and therefore the page, is null.
3959 if (null == url) {
3960 url = mLastEnteredUrl;
3961 // This can happen.
3962 if (null == url) {
3963 url = mSettings.getHomePage();
3964 }
3965 }
3966 // In case the web page has not yet received its associated title.
3967 if (title == null) {
3968 title = url;
3969 }
3970 intent.putExtra("title", title);
3971 intent.putExtra("url", url);
Leon Scroggins190095d2009-08-17 17:01:38 -04003972 // If this is opening in a new window, then disable opening in a
3973 // (different) new window. Also disable it if we have maxed out the
3974 // windows.
3975 intent.putExtra("disable_new_window", cancelGoPageMeansClose
3976 || mTabControl.getTabCount() >= TabControl.MAX_TABS);
Patrick Scott3918d442009-08-04 13:22:29 -04003977 intent.putExtra("touch_icon_url", current.getTouchIconUrl());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003978 if (startWithHistory) {
3979 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
3980 CombinedBookmarkHistoryActivity.HISTORY_TAB);
3981 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003982 mCancelGoPageMeansClose = cancelGoPageMeansClose;
The Android Open Source Project0c908882009-03-03 19:32:16 -08003983 startActivityForResult(intent, COMBO_PAGE);
3984 }
3985
3986 // Called when loading from context menu or LOAD_URL message
3987 private void loadURL(WebView view, String url) {
3988 // In case the user enters nothing.
3989 if (url != null && url.length() != 0 && view != null) {
3990 url = smartUrlFilter(url);
3991 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
3992 view.loadUrl(url);
3993 }
3994 }
3995 }
3996
The Android Open Source Project0c908882009-03-03 19:32:16 -08003997 private String smartUrlFilter(Uri inUri) {
3998 if (inUri != null) {
3999 return smartUrlFilter(inUri.toString());
4000 }
4001 return null;
4002 }
4003
4004
4005 // get window count
4006
4007 int getWindowCount(){
4008 if(mTabControl != null){
4009 return mTabControl.getTabCount();
4010 }
4011 return 0;
4012 }
4013
Feng Qianb34f87a2009-03-24 21:27:26 -07004014 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08004015 "(?i)" + // switch on case insensitive matching
4016 "(" + // begin group for schema
4017 "(?:http|https|file):\\/\\/" +
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004018 "|(?:inline|data|about|content|javascript):" +
The Android Open Source Project0c908882009-03-03 19:32:16 -08004019 ")" +
4020 "(.*)" );
4021
4022 /**
4023 * Attempts to determine whether user input is a URL or search
4024 * terms. Anything with a space is passed to search.
4025 *
4026 * Converts to lowercase any mistakenly uppercased schema (i.e.,
4027 * "Http://" converts to "http://"
4028 *
4029 * @return Original or modified URL
4030 *
4031 */
4032 String smartUrlFilter(String url) {
4033
4034 String inUrl = url.trim();
4035 boolean hasSpace = inUrl.indexOf(' ') != -1;
4036
4037 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4038 if (matcher.matches()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004039 // force scheme to lowercase
4040 String scheme = matcher.group(1);
4041 String lcScheme = scheme.toLowerCase();
4042 if (!lcScheme.equals(scheme)) {
Mitsuru Oshima123ecfb2009-05-18 19:11:14 -07004043 inUrl = lcScheme + matcher.group(2);
4044 }
4045 if (hasSpace) {
4046 inUrl = inUrl.replace(" ", "%20");
The Android Open Source Project0c908882009-03-03 19:32:16 -08004047 }
4048 return inUrl;
4049 }
4050 if (hasSpace) {
Satish Sampath565505b2009-05-29 15:37:27 +01004051 // FIXME: Is this the correct place to add to searches?
4052 // what if someone else calls this function?
4053 int shortcut = parseUrlShortcut(inUrl);
4054 if (shortcut != SHORTCUT_INVALID) {
4055 Browser.addSearchUrl(mResolver, inUrl);
4056 String query = inUrl.substring(2);
4057 switch (shortcut) {
4058 case SHORTCUT_GOOGLE_SEARCH:
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004059 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
Satish Sampath565505b2009-05-29 15:37:27 +01004060 case SHORTCUT_WIKIPEDIA_SEARCH:
4061 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4062 case SHORTCUT_DICTIONARY_SEARCH:
4063 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4064 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
The Android Open Source Project0c908882009-03-03 19:32:16 -08004065 // FIXME: we need location in this case
Satish Sampath565505b2009-05-29 15:37:27 +01004066 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004067 }
4068 }
4069 } else {
4070 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4071 return URLUtil.guessUrl(inUrl);
4072 }
4073 }
4074
4075 Browser.addSearchUrl(mResolver, inUrl);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004076 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004077 }
4078
Ben Murdochbff2d602009-07-01 20:19:05 +01004079 /* package */ void setShouldShowErrorConsole(boolean flag) {
4080 if (flag == mShouldShowErrorConsole) {
4081 // Nothing to do.
4082 return;
4083 }
4084
4085 mShouldShowErrorConsole = flag;
4086
4087 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4088
4089 if (flag) {
4090 // Setting the show state of the console will cause it's the layout to be inflated.
4091 if (errorConsole.numberOfErrors() > 0) {
4092 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4093 } else {
4094 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4095 }
4096
4097 // Now we can add it to the main view.
4098 mErrorConsoleContainer.addView(errorConsole,
4099 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4100 ViewGroup.LayoutParams.WRAP_CONTENT));
4101 } else {
4102 mErrorConsoleContainer.removeView(errorConsole);
4103 }
4104
4105 }
4106
The Android Open Source Project0c908882009-03-03 19:32:16 -08004107 private final static int LOCK_ICON_UNSECURE = 0;
4108 private final static int LOCK_ICON_SECURE = 1;
4109 private final static int LOCK_ICON_MIXED = 2;
4110
4111 private int mLockIconType = LOCK_ICON_UNSECURE;
4112 private int mPrevLockType = LOCK_ICON_UNSECURE;
4113
4114 private BrowserSettings mSettings;
4115 private TabControl mTabControl;
4116 private ContentResolver mResolver;
4117 private FrameLayout mContentView;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004118 private View mCustomView;
4119 private FrameLayout mCustomViewContainer;
Andrei Popescuc9b55562009-07-07 10:51:15 +01004120 private WebChromeClient.CustomViewCallback mCustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004121
4122 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4123 // view, we should rewrite this.
4124 private int mCurrentMenuState = 0;
4125 private int mMenuState = R.id.MAIN_MENU;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004126 private int mOldMenuState = EMPTY_MENU;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004127 private static final int EMPTY_MENU = -1;
4128 private Menu mMenu;
4129
4130 private FindDialog mFindDialog;
4131 // Used to prevent chording to result in firing two shortcuts immediately
4132 // one after another. Fixes bug 1211714.
4133 boolean mCanChord;
4134
4135 private boolean mInLoad;
4136 private boolean mIsNetworkUp;
4137
4138 private boolean mPageStarted;
4139 private boolean mActivityInPause = true;
4140
4141 private boolean mMenuIsDown;
4142
4143 private final KeyTracker mKeyTracker = new KeyTracker(this);
4144
4145 // As trackball doesn't send repeat down, we have to track it ourselves
4146 private boolean mTrackTrackball;
4147
4148 private static boolean mInTrace;
4149
4150 // Performance probe
4151 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4152 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4153 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4154 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4155 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4156 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4157 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4158 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4159 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4160 };
4161
4162 private long mStart;
4163 private long mProcessStart;
4164 private long mUserStart;
4165 private long mSystemStart;
4166 private long mIdleStart;
4167 private long mIrqStart;
4168
4169 private long mUiStart;
4170
4171 private Drawable mMixLockIcon;
4172 private Drawable mSecLockIcon;
4173 private Drawable mGenericFavicon;
4174
4175 /* hold a ref so we can auto-cancel if necessary */
4176 private AlertDialog mAlertDialog;
4177
4178 // Wait for credentials before loading google.com
4179 private ProgressDialog mCredsDlg;
4180
4181 // The up-to-date URL and title (these can be different from those stored
4182 // in WebView, since it takes some time for the information in WebView to
4183 // get updated)
4184 private String mUrl;
4185 private String mTitle;
4186
4187 // As PageInfo has different style for landscape / portrait, we have
4188 // to re-open it when configuration changed
4189 private AlertDialog mPageInfoDialog;
4190 private TabControl.Tab mPageInfoView;
4191 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4192 // dialog, we should not just dismiss it, but should get back to the
4193 // SSL-certificate-on-error dialog. This flag is used to store this state
4194 private Boolean mPageInfoFromShowSSLCertificateOnError;
4195
4196 // as SSLCertificateOnError has different style for landscape / portrait,
4197 // we have to re-open it when configuration changed
4198 private AlertDialog mSSLCertificateOnErrorDialog;
4199 private WebView mSSLCertificateOnErrorView;
4200 private SslErrorHandler mSSLCertificateOnErrorHandler;
4201 private SslError mSSLCertificateOnErrorError;
4202
4203 // as SSLCertificate has different style for landscape / portrait, we
4204 // have to re-open it when configuration changed
4205 private AlertDialog mSSLCertificateDialog;
4206 private TabControl.Tab mSSLCertificateView;
4207
4208 // as HttpAuthentication has different style for landscape / portrait, we
4209 // have to re-open it when configuration changed
4210 private AlertDialog mHttpAuthenticationDialog;
4211 private HttpAuthHandler mHttpAuthHandler;
4212
4213 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4214 new FrameLayout.LayoutParams(
4215 ViewGroup.LayoutParams.FILL_PARENT,
4216 ViewGroup.LayoutParams.FILL_PARENT);
Andrei Popescuadc008d2009-06-26 14:11:30 +01004217 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4218 new FrameLayout.LayoutParams(
4219 ViewGroup.LayoutParams.FILL_PARENT,
4220 ViewGroup.LayoutParams.FILL_PARENT,
4221 Gravity.CENTER);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004222 // Google search
4223 final static String QuickSearch_G = "http://www.google.com/m?q=%s";
The Android Open Source Project0c908882009-03-03 19:32:16 -08004224 // Wikipedia search
4225 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4226 // Dictionary search
4227 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4228 // Google Mobile Local search
4229 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4230
4231 final static String QUERY_PLACE_HOLDER = "%s";
4232
4233 // "source" parameter for Google search through search key
4234 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4235 // "source" parameter for Google search through goto menu
4236 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4237 // "source" parameter for Google search through simplily type
4238 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4239 // "source" parameter for Google search suggested by the browser
4240 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4241 // "source" parameter for Google search from unknown source
4242 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4243
4244 private final static String LOGTAG = "browser";
4245
The Android Open Source Project0c908882009-03-03 19:32:16 -08004246 private String mLastEnteredUrl;
4247
4248 private PowerManager.WakeLock mWakeLock;
4249 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4250
4251 private Toast mStopToast;
4252
Leon Scroggins1f005d32009-08-10 17:36:42 -04004253 private TitleBarSet mTitleBar;
Leon Scroggins81db3662009-06-04 17:45:11 -04004254
Ben Murdochbff2d602009-07-01 20:19:05 +01004255 private LinearLayout mErrorConsoleContainer = null;
4256 private boolean mShouldShowErrorConsole = false;
4257
The Android Open Source Project0c908882009-03-03 19:32:16 -08004258 // As the ids are dynamically created, we can't guarantee that they will
4259 // be in sequence, so this static array maps ids to a window number.
4260 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4261 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4262 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4263 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4264
4265 // monitor platform changes
4266 private IntentFilter mNetworkStateChangedFilter;
4267 private BroadcastReceiver mNetworkStateIntentReceiver;
4268
Grace Klobab4da0ad2009-05-14 14:45:40 -07004269 private BroadcastReceiver mPackageInstallationReceiver;
4270
The Android Open Source Project0c908882009-03-03 19:32:16 -08004271 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004272 final static int COMBO_PAGE = 1;
4273 final static int DOWNLOAD_PAGE = 2;
4274 final static int PREFERENCES_PAGE = 3;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004275
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004276 /**
4277 * A UrlData class to abstract how the content will be set to WebView.
4278 * This base class uses loadUrl to show the content.
4279 */
4280 private static class UrlData {
4281 String mUrl;
Grace Kloba60e095c2009-06-16 11:50:55 -07004282 byte[] mPostData;
4283
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004284 UrlData(String url) {
4285 this.mUrl = url;
4286 }
Grace Kloba60e095c2009-06-16 11:50:55 -07004287
4288 void setPostData(byte[] postData) {
4289 mPostData = postData;
4290 }
4291
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004292 boolean isEmpty() {
4293 return mUrl == null || mUrl.length() == 0;
4294 }
4295
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004296 public void loadIn(WebView webView) {
Grace Kloba60e095c2009-06-16 11:50:55 -07004297 if (mPostData != null) {
4298 webView.postUrl(mUrl, mPostData);
4299 } else {
4300 webView.loadUrl(mUrl);
4301 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004302 }
4303 };
4304
4305 /**
4306 * A subclass of UrlData class that can display inlined content using
4307 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4308 */
4309 private static class InlinedUrlData extends UrlData {
4310 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4311 super(failUrl);
4312 mInlined = inlined;
4313 mMimeType = mimeType;
4314 mEncoding = encoding;
4315 }
4316 String mMimeType;
4317 String mInlined;
4318 String mEncoding;
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004319 @Override
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004320 boolean isEmpty() {
Ben Murdochbff2d602009-07-01 20:19:05 +01004321 return mInlined == null || mInlined.length() == 0 || super.isEmpty();
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004322 }
4323
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004324 @Override
4325 public void loadIn(WebView webView) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004326 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4327 }
4328 }
4329
Leon Scroggins1f005d32009-08-10 17:36:42 -04004330 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004331}