blob: d7c53b0170e377f35f1e64d629b311cd9407d1cb [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);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100339 mContentView = (FrameLayout) browserFrameLayout.findViewById(
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400340 R.id.main_content);
Ben Murdochbff2d602009-07-01 20:19:05 +0100341 mErrorConsoleContainer = (LinearLayout) browserFrameLayout.findViewById(
342 R.id.error_console);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100343 mCustomViewContainer = (FrameLayout) browserFrameLayout
344 .findViewById(R.id.fullscreen_custom_content);
345 frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS);
Leon Scroggins39ab28e2009-09-02 21:20:30 -0400346 mTitleBar = new TitleBarSet(this);
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);
1246 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001247 return true;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001248 }
1249
1250 /* package */ void closeCurrentWindow() {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001251 final TabControl.Tab current = mTabControl.getCurrentTab();
Leon Scroggins160a7e72009-08-14 18:28:01 -04001252 if (mTabControl.getTabCount() == 1) {
1253 // This is the last tab. Open a new one, as well as the history
1254 // picker, and close the current one.
1255 TabControl.Tab newTab = openTabAndShow(
1256 BrowserActivity.EMPTY_URL_DATA, false, null);
1257 bookmarksOrHistoryPicker(false, true);
1258 closeTab(current);
1259 mTabControl.setCurrentTab(newTab);
1260 return;
1261 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001262 final TabControl.Tab parent = current.getParentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04001263 int indexToShow = -1;
1264 if (parent != null) {
1265 indexToShow = mTabControl.getTabIndex(parent);
1266 } else {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001267 final int currentIndex = mTabControl.getCurrentIndex();
1268 // Try to move to the tab to the right
1269 indexToShow = currentIndex + 1;
1270 if (indexToShow > mTabControl.getTabCount() - 1) {
1271 // Try to move to the tab to the left
1272 indexToShow = currentIndex - 1;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001273 }
1274 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001275 if (switchToTab(indexToShow)) {
1276 // Close window
1277 closeTab(current);
1278 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001279 }
1280
The Android Open Source Project0c908882009-03-03 19:32:16 -08001281 @Override
1282 public boolean onOptionsItemSelected(MenuItem item) {
1283 if (!mCanChord) {
1284 // The user has already fired a shortcut with this hold down of the
1285 // menu key.
1286 return false;
1287 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001288 if (null == getTopWindow()) {
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001289 return false;
1290 }
Grace Kloba6ee9c492009-07-13 10:04:34 -07001291 if (mMenuIsDown) {
1292 // The shortcut action consumes the MENU. Even if it is still down,
1293 // it won't trigger the next shortcut action. In the case of the
1294 // shortcut action triggering a new activity, like Bookmarks, we
1295 // won't get onKeyUp for MENU. So it is important to reset it here.
1296 mMenuIsDown = false;
1297 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001298 switch (item.getItemId()) {
1299 // -- Main menu
Leon Scrogginsa81a7642009-08-31 17:05:41 -04001300 case R.id.new_tab_menu_id:
1301 openTabAndShow(EMPTY_URL_DATA, false, null);
1302 bookmarksOrHistoryPicker(false, true);
1303 break;
1304
Leon Scroggins64b80f32009-08-07 12:03:34 -04001305 case R.id.goto_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001306 bookmarksOrHistoryPicker(false, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001307 break;
1308
Leon Scroggins1f005d32009-08-10 17:36:42 -04001309 case R.id.add_bookmark_menu_id:
1310 Intent i = new Intent(BrowserActivity.this,
1311 AddBookmarkPage.class);
1312 WebView w = getTopWindow();
1313 i.putExtra("url", w.getUrl());
1314 i.putExtra("title", w.getTitle());
1315 startActivity(i);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001316 break;
1317
1318 case R.id.stop_reload_menu_id:
1319 if (mInLoad) {
1320 stopLoading();
1321 } else {
1322 getTopWindow().reload();
1323 }
1324 break;
1325
1326 case R.id.back_menu_id:
1327 getTopWindow().goBack();
1328 break;
1329
1330 case R.id.forward_menu_id:
1331 getTopWindow().goForward();
1332 break;
1333
1334 case R.id.close_menu_id:
1335 // Close the subwindow if it exists.
1336 if (mTabControl.getCurrentSubWindow() != null) {
1337 dismissSubWindow(mTabControl.getCurrentTab());
1338 break;
1339 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001340 closeCurrentWindow();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001341 break;
1342
1343 case R.id.homepage_menu_id:
1344 TabControl.Tab current = mTabControl.getCurrentTab();
1345 if (current != null) {
1346 dismissSubWindow(current);
1347 current.getWebView().loadUrl(mSettings.getHomePage());
1348 }
1349 break;
1350
1351 case R.id.preferences_menu_id:
1352 Intent intent = new Intent(this,
1353 BrowserPreferencesPage.class);
1354 startActivityForResult(intent, PREFERENCES_PAGE);
1355 break;
1356
1357 case R.id.find_menu_id:
1358 if (null == mFindDialog) {
1359 mFindDialog = new FindDialog(this);
1360 }
1361 mFindDialog.setWebView(getTopWindow());
1362 mFindDialog.show();
1363 mMenuState = EMPTY_MENU;
1364 break;
1365
1366 case R.id.select_text_id:
1367 getTopWindow().emulateShiftHeld();
1368 break;
1369 case R.id.page_info_menu_id:
1370 showPageInfo(mTabControl.getCurrentTab(), false);
1371 break;
1372
1373 case R.id.classic_history_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001374 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001375 break;
1376
1377 case R.id.share_page_menu_id:
1378 Browser.sendString(this, getTopWindow().getUrl());
1379 break;
1380
1381 case R.id.dump_nav_menu_id:
1382 getTopWindow().debugDump();
1383 break;
1384
1385 case R.id.zoom_in_menu_id:
1386 getTopWindow().zoomIn();
1387 break;
1388
1389 case R.id.zoom_out_menu_id:
1390 getTopWindow().zoomOut();
1391 break;
1392
1393 case R.id.view_downloads_menu_id:
1394 viewDownloads(null);
1395 break;
1396
The Android Open Source Project0c908882009-03-03 19:32:16 -08001397 case R.id.window_one_menu_id:
1398 case R.id.window_two_menu_id:
1399 case R.id.window_three_menu_id:
1400 case R.id.window_four_menu_id:
1401 case R.id.window_five_menu_id:
1402 case R.id.window_six_menu_id:
1403 case R.id.window_seven_menu_id:
1404 case R.id.window_eight_menu_id:
1405 {
1406 int menuid = item.getItemId();
1407 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1408 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1409 TabControl.Tab desiredTab = mTabControl.getTab(id);
1410 if (desiredTab != null &&
1411 desiredTab != mTabControl.getCurrentTab()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001412 switchToTab(id);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001413 }
1414 break;
1415 }
1416 }
1417 }
1418 break;
1419
1420 default:
1421 if (!super.onOptionsItemSelected(item)) {
1422 return false;
1423 }
1424 // Otherwise fall through.
1425 }
1426 mCanChord = false;
1427 return true;
1428 }
1429
1430 public void closeFind() {
1431 mMenuState = R.id.MAIN_MENU;
1432 }
1433
1434 @Override public boolean onPrepareOptionsMenu(Menu menu)
1435 {
1436 // This happens when the user begins to hold down the menu key, so
1437 // allow them to chord to get a shortcut.
1438 mCanChord = true;
1439 // Note: setVisible will decide whether an item is visible; while
1440 // setEnabled() will decide whether an item is enabled, which also means
1441 // whether the matching shortcut key will function.
1442 super.onPrepareOptionsMenu(menu);
1443 switch (mMenuState) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001444 case EMPTY_MENU:
1445 if (mCurrentMenuState != mMenuState) {
1446 menu.setGroupVisible(R.id.MAIN_MENU, false);
1447 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1448 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001449 }
1450 break;
1451 default:
1452 if (mCurrentMenuState != mMenuState) {
1453 menu.setGroupVisible(R.id.MAIN_MENU, true);
1454 menu.setGroupEnabled(R.id.MAIN_MENU, true);
1455 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001456 }
1457 final WebView w = getTopWindow();
1458 boolean canGoBack = false;
1459 boolean canGoForward = false;
1460 boolean isHome = false;
1461 if (w != null) {
1462 canGoBack = w.canGoBack();
1463 canGoForward = w.canGoForward();
1464 isHome = mSettings.getHomePage().equals(w.getUrl());
1465 }
1466 final MenuItem back = menu.findItem(R.id.back_menu_id);
1467 back.setEnabled(canGoBack);
1468
1469 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1470 home.setEnabled(!isHome);
1471
1472 menu.findItem(R.id.forward_menu_id)
1473 .setEnabled(canGoForward);
1474
Leon Scrogginsa81a7642009-08-31 17:05:41 -04001475 menu.findItem(R.id.new_tab_menu_id).setEnabled(
1476 mTabControl.getTabCount() < TabControl.MAX_TABS);
1477
The Android Open Source Project0c908882009-03-03 19:32:16 -08001478 // decide whether to show the share link option
1479 PackageManager pm = getPackageManager();
1480 Intent send = new Intent(Intent.ACTION_SEND);
1481 send.setType("text/plain");
1482 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1483 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1484
The Android Open Source Project0c908882009-03-03 19:32:16 -08001485 boolean isNavDump = mSettings.isNavDump();
1486 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1487 nav.setVisible(isNavDump);
1488 nav.setEnabled(isNavDump);
1489 break;
1490 }
1491 mCurrentMenuState = mMenuState;
1492 return true;
1493 }
1494
1495 @Override
1496 public void onCreateContextMenu(ContextMenu menu, View v,
1497 ContextMenuInfo menuInfo) {
1498 WebView webview = (WebView) v;
1499 WebView.HitTestResult result = webview.getHitTestResult();
1500 if (result == null) {
1501 return;
1502 }
1503
1504 int type = result.getType();
1505 if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1506 Log.w(LOGTAG,
1507 "We should not show context menu when nothing is touched");
1508 return;
1509 }
1510 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1511 // let TextView handles context menu
1512 return;
1513 }
1514
1515 // Note, http://b/issue?id=1106666 is requesting that
1516 // an inflated menu can be used again. This is not available
1517 // yet, so inflate each time (yuk!)
1518 MenuInflater inflater = getMenuInflater();
1519 inflater.inflate(R.menu.browsercontext, menu);
1520
1521 // Show the correct menu group
1522 String extra = result.getExtra();
1523 menu.setGroupVisible(R.id.PHONE_MENU,
1524 type == WebView.HitTestResult.PHONE_TYPE);
1525 menu.setGroupVisible(R.id.EMAIL_MENU,
1526 type == WebView.HitTestResult.EMAIL_TYPE);
1527 menu.setGroupVisible(R.id.GEO_MENU,
1528 type == WebView.HitTestResult.GEO_TYPE);
1529 menu.setGroupVisible(R.id.IMAGE_MENU,
1530 type == WebView.HitTestResult.IMAGE_TYPE
1531 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1532 menu.setGroupVisible(R.id.ANCHOR_MENU,
1533 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1534 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1535
1536 // Setup custom handling depending on the type
1537 switch (type) {
1538 case WebView.HitTestResult.PHONE_TYPE:
1539 menu.setHeaderTitle(Uri.decode(extra));
1540 menu.findItem(R.id.dial_context_menu_id).setIntent(
1541 new Intent(Intent.ACTION_VIEW, Uri
1542 .parse(WebView.SCHEME_TEL + extra)));
1543 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1544 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1545 addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1546 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1547 addIntent);
1548 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1549 new Copy(extra));
1550 break;
1551
1552 case WebView.HitTestResult.EMAIL_TYPE:
1553 menu.setHeaderTitle(extra);
1554 menu.findItem(R.id.email_context_menu_id).setIntent(
1555 new Intent(Intent.ACTION_VIEW, Uri
1556 .parse(WebView.SCHEME_MAILTO + extra)));
1557 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1558 new Copy(extra));
1559 break;
1560
1561 case WebView.HitTestResult.GEO_TYPE:
1562 menu.setHeaderTitle(extra);
1563 menu.findItem(R.id.map_context_menu_id).setIntent(
1564 new Intent(Intent.ACTION_VIEW, Uri
1565 .parse(WebView.SCHEME_GEO
1566 + URLEncoder.encode(extra))));
1567 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1568 new Copy(extra));
1569 break;
1570
1571 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1572 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1573 TextView titleView = (TextView) LayoutInflater.from(this)
1574 .inflate(android.R.layout.browser_link_context_header,
1575 null);
1576 titleView.setText(extra);
1577 menu.setHeaderView(titleView);
1578 // decide whether to show the open link in new tab option
1579 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1580 mTabControl.getTabCount() < TabControl.MAX_TABS);
1581 PackageManager pm = getPackageManager();
1582 Intent send = new Intent(Intent.ACTION_SEND);
1583 send.setType("text/plain");
1584 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1585 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1586 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1587 break;
1588 }
1589 // otherwise fall through to handle image part
1590 case WebView.HitTestResult.IMAGE_TYPE:
1591 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1592 menu.setHeaderTitle(extra);
1593 }
1594 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1595 new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1596 menu.findItem(R.id.download_context_menu_id).
1597 setOnMenuItemClickListener(new Download(extra));
1598 break;
1599
1600 default:
1601 Log.w(LOGTAG, "We should not get here.");
1602 break;
1603 }
1604 }
1605
The Android Open Source Project0c908882009-03-03 19:32:16 -08001606 // Attach the given tab to the content view.
1607 private void attachTabToContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001608 // Attach the container that contains the main WebView and any other UI
1609 // associated with the tab.
1610 mContentView.addView(t.getContainer(), COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +01001611
1612 if (mShouldShowErrorConsole) {
1613 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
1614 if (errorConsole.numberOfErrors() == 0) {
1615 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1616 } else {
1617 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1618 }
1619
1620 mErrorConsoleContainer.addView(errorConsole,
1621 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
1622 ViewGroup.LayoutParams.WRAP_CONTENT));
1623 }
1624
Leon Scroggins39ab28e2009-09-02 21:20:30 -04001625 WebView view = t.getWebView();
Leon Scroggins55a5bc22009-09-04 17:00:08 -04001626 view.setEmbeddedTitleBar(mTitleBar);
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
Leon Scroggins39ab28e2009-09-02 21:20:30 -04001653 WebView view = t.getWebView();
Leon Scroggins55a5bc22009-09-04 17:00:08 -04001654 view.setEmbeddedTitleBar(null);
Leon Scroggins39ab28e2009-09-02 21:20:30 -04001655
The Android Open Source Project0c908882009-03-03 19:32:16 -08001656 // Remove the sub window if it exists.
1657 if (t.getSubWebView() != null) {
1658 mContentView.removeView(t.getSubWebViewContainer());
1659 }
1660 }
1661
1662 // Remove the sub window if it exists. Also called by TabControl when the
1663 // user clicks the 'X' to dismiss a sub window.
1664 /* package */ void dismissSubWindow(TabControl.Tab t) {
1665 final WebView mainView = t.getWebView();
1666 if (t.getSubWebView() != null) {
1667 // Remove the container view and request focus on the main WebView.
1668 mContentView.removeView(t.getSubWebViewContainer());
1669 mainView.requestFocus();
1670 // Tell the TabControl to dismiss the subwindow. This will destroy
1671 // the WebView.
1672 mTabControl.dismissSubWindow(t);
1673 }
1674 }
1675
Leon Scroggins1f005d32009-08-10 17:36:42 -04001676 // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001677 // that accepts url as string.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001678 private TabControl.Tab openTabAndShow(String url, boolean closeOnExit,
1679 String appId) {
1680 return openTabAndShow(new UrlData(url), closeOnExit, appId);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001681 }
1682
1683 // This method does a ton of stuff. It will attempt to create a new tab
1684 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
Leon Scroggins1f005d32009-08-10 17:36:42 -04001685 // url isn't null, it will load the given url.
1686 /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001687 boolean closeOnExit, String appId) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001688 final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1689 final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1690 if (newTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001691 final TabControl.Tab tab = mTabControl.createNewTab(
1692 closeOnExit, appId, urlData.mUrl);
1693 WebView webview = tab.getWebView();
1694 if (CUSTOM_BROWSER_BAR) {
1695 mTitleBar.addTab(webview, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001696 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001697 removeTabFromContentView(currentTab);
1698 attachTabToContentView(tab);
Patrick Scott8bbd69f2009-08-14 13:35:53 -04001699 // We must set the new tab as the current tab to reflect the old
1700 // animation behavior.
1701 mTabControl.setCurrentTab(tab);
Leon Scroggins160a7e72009-08-14 18:28:01 -04001702 if (!urlData.isEmpty()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001703 urlData.loadIn(webview);
1704 }
1705 return tab;
1706 } else {
1707 // Get rid of the subwindow if it exists
1708 dismissSubWindow(currentTab);
1709 if (!urlData.isEmpty()) {
1710 // Load the given url.
1711 urlData.loadIn(currentTab.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001712 }
1713 }
Grace Klobac9181842009-04-14 08:53:22 -07001714 return currentTab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001715 }
1716
Grace Klobac9181842009-04-14 08:53:22 -07001717 private TabControl.Tab openTab(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001718 if (mSettings.openInBackground()) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001719 TabControl.Tab t = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001720 if (t != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001721 WebView view = t.getWebView();
1722 if (CUSTOM_BROWSER_BAR) {
1723 mTitleBar.addTab(view, false);
1724 }
1725 view.loadUrl(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001726 }
Grace Klobac9181842009-04-14 08:53:22 -07001727 return t;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001728 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001729 return openTabAndShow(url, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001730 }
1731 }
1732
1733 private class Copy implements OnMenuItemClickListener {
1734 private CharSequence mText;
1735
1736 public boolean onMenuItemClick(MenuItem item) {
1737 copy(mText);
1738 return true;
1739 }
1740
1741 public Copy(CharSequence toCopy) {
1742 mText = toCopy;
1743 }
1744 }
1745
1746 private class Download implements OnMenuItemClickListener {
1747 private String mText;
1748
1749 public boolean onMenuItemClick(MenuItem item) {
1750 onDownloadStartNoStream(mText, null, null, null, -1);
1751 return true;
1752 }
1753
1754 public Download(String toDownload) {
1755 mText = toDownload;
1756 }
1757 }
1758
1759 private void copy(CharSequence text) {
1760 try {
1761 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
1762 if (clip != null) {
1763 clip.setClipboardText(text);
1764 }
1765 } catch (android.os.RemoteException e) {
1766 Log.e(LOGTAG, "Copy failed", e);
1767 }
1768 }
1769
1770 /**
1771 * Resets the browser title-view to whatever it must be (for example, if we
1772 * load a page from history).
1773 */
1774 private void resetTitle() {
1775 resetLockIcon();
1776 resetTitleIconAndProgress();
1777 }
1778
1779 /**
1780 * Resets the browser title-view to whatever it must be
1781 * (for example, if we had a loading error)
1782 * When we have a new page, we call resetTitle, when we
1783 * have to reset the titlebar to whatever it used to be
1784 * (for example, if the user chose to stop loading), we
1785 * call resetTitleAndRevertLockIcon.
1786 */
1787 /* package */ void resetTitleAndRevertLockIcon() {
1788 revertLockIcon();
1789 resetTitleIconAndProgress();
1790 }
1791
1792 /**
1793 * Reset the title, favicon, and progress.
1794 */
1795 private void resetTitleIconAndProgress() {
1796 WebView current = mTabControl.getCurrentWebView();
1797 if (current == null) {
1798 return;
1799 }
1800 resetTitleAndIcon(current);
1801 int progress = current.getProgress();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001802 mWebChromeClient.onProgressChanged(current, progress);
1803 }
1804
1805 // Reset the title and the icon based on the given item.
1806 private void resetTitleAndIcon(WebView view) {
1807 WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
1808 if (item != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001809 setUrlTitle(item.getUrl(), item.getTitle(), view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001810 setFavicon(item.getFavicon());
1811 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001812 setUrlTitle(null, null, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001813 setFavicon(null);
1814 }
1815 }
1816
1817 /**
1818 * Sets a title composed of the URL and the title string.
1819 * @param url The URL of the site being loaded.
1820 * @param title The title of the site being loaded.
1821 */
Leon Scroggins1f005d32009-08-10 17:36:42 -04001822 private void setUrlTitle(String url, String title, WebView view) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001823 mUrl = url;
1824 mTitle = title;
1825
Leon Scroggins1f005d32009-08-10 17:36:42 -04001826 if (CUSTOM_BROWSER_BAR) {
1827 mTitleBar.setTitleAndUrl(title, url, view);
1828 } else {
1829 setTitle(buildUrlTitle(url, title));
The Android Open Source Project0c908882009-03-03 19:32:16 -08001830 }
1831 }
1832
1833 /**
1834 * Builds and returns the page title, which is some
1835 * combination of the page URL and title.
1836 * @param url The URL of the site being loaded.
1837 * @param title The title of the site being loaded.
1838 * @return The page title.
1839 */
1840 private String buildUrlTitle(String url, String title) {
1841 String urlTitle = "";
1842
1843 if (url != null) {
1844 String titleUrl = buildTitleUrl(url);
1845
1846 if (title != null && 0 < title.length()) {
1847 if (titleUrl != null && 0 < titleUrl.length()) {
1848 urlTitle = titleUrl + ": " + title;
1849 } else {
1850 urlTitle = title;
1851 }
1852 } else {
1853 if (titleUrl != null) {
1854 urlTitle = titleUrl;
1855 }
1856 }
1857 }
1858
1859 return urlTitle;
1860 }
1861
1862 /**
1863 * @param url The URL to build a title version of the URL from.
1864 * @return The title version of the URL or null if fails.
1865 * The title version of the URL can be either the URL hostname,
1866 * or the hostname with an "https://" prefix (for secure URLs),
1867 * or an empty string if, for example, the URL in question is a
1868 * file:// URL with no hostname.
1869 */
Leon Scroggins32e14a62009-06-11 10:26:34 -04001870 /* package */ static String buildTitleUrl(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001871 String titleUrl = null;
1872
1873 if (url != null) {
1874 try {
1875 // parse the url string
1876 URL urlObj = new URL(url);
1877 if (urlObj != null) {
1878 titleUrl = "";
1879
1880 String protocol = urlObj.getProtocol();
1881 String host = urlObj.getHost();
1882
1883 if (host != null && 0 < host.length()) {
1884 titleUrl = host;
1885 if (protocol != null) {
1886 // if a secure site, add an "https://" prefix!
1887 if (protocol.equalsIgnoreCase("https")) {
1888 titleUrl = protocol + "://" + host;
1889 }
1890 }
1891 }
1892 }
1893 } catch (MalformedURLException e) {}
1894 }
1895
1896 return titleUrl;
1897 }
1898
1899 // Set the favicon in the title bar.
1900 private void setFavicon(Bitmap icon) {
Leon Scroggins81db3662009-06-04 17:45:11 -04001901 if (CUSTOM_BROWSER_BAR) {
1902 Drawable[] array = new Drawable[3];
1903 array[0] = new PaintDrawable(Color.BLACK);
1904 PaintDrawable p = new PaintDrawable(Color.WHITE);
1905 array[1] = p;
1906 if (icon == null) {
1907 array[2] = mGenericFavicon;
1908 } else {
1909 array[2] = new BitmapDrawable(icon);
1910 }
1911 LayerDrawable d = new LayerDrawable(array);
1912 d.setLayerInset(1, 1, 1, 1, 1);
1913 d.setLayerInset(2, 2, 2, 2, 2);
Leon Scroggins1f005d32009-08-10 17:36:42 -04001914 mTitleBar.setFavicon(d, getTopWindow());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001915 } else {
Leon Scroggins81db3662009-06-04 17:45:11 -04001916 Drawable[] array = new Drawable[2];
1917 PaintDrawable p = new PaintDrawable(Color.WHITE);
1918 p.setCornerRadius(3f);
1919 array[0] = p;
1920 if (icon == null) {
1921 array[1] = mGenericFavicon;
1922 } else {
1923 array[1] = new BitmapDrawable(icon);
1924 }
1925 LayerDrawable d = new LayerDrawable(array);
1926 d.setLayerInset(1, 2, 2, 2, 2);
1927 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001928 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001929 }
1930
1931 /**
1932 * Saves the current lock-icon state before resetting
1933 * the lock icon. If we have an error, we may need to
1934 * roll back to the previous state.
1935 */
1936 private void saveLockIcon() {
1937 mPrevLockType = mLockIconType;
1938 }
1939
1940 /**
1941 * Reverts the lock-icon state to the last saved state,
1942 * for example, if we had an error, and need to cancel
1943 * the load.
1944 */
1945 private void revertLockIcon() {
1946 mLockIconType = mPrevLockType;
1947
Dave Bort31a6d1c2009-04-13 15:56:49 -07001948 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001949 Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
1950 " revert lock icon to " + mLockIconType);
1951 }
1952
1953 updateLockIconImage(mLockIconType);
1954 }
1955
Leon Scroggins1f005d32009-08-10 17:36:42 -04001956 /**
1957 * Close the tab after removing its associated title bar.
1958 */
1959 private void closeTab(TabControl.Tab t) {
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001960 if (CUSTOM_BROWSER_BAR) {
1961 mTitleBar.removeTab(mTabControl.getTabIndex(t));
1962 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001963 mTabControl.removeTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001964 }
1965
1966 private void goBackOnePageOrQuit() {
1967 TabControl.Tab current = mTabControl.getCurrentTab();
1968 if (current == null) {
1969 /*
1970 * Instead of finishing the activity, simply push this to the back
1971 * of the stack and let ActivityManager to choose the foreground
1972 * activity. As BrowserActivity is singleTask, it will be always the
1973 * root of the task. So we can use either true or false for
1974 * moveTaskToBack().
1975 */
1976 moveTaskToBack(true);
1977 }
1978 WebView w = current.getWebView();
1979 if (w.canGoBack()) {
1980 w.goBack();
1981 } else {
1982 // Check to see if we are closing a window that was created by
1983 // another window. If so, we switch back to that window.
1984 TabControl.Tab parent = current.getParentTab();
1985 if (parent != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001986 switchToTab(mTabControl.getTabIndex(parent));
1987 // Now we close the other tab
1988 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001989 } else {
1990 if (current.closeOnExit()) {
Grace Klobabb0af5c2009-09-01 00:56:09 -07001991 // force mPageStarted to be false as we are going to either
1992 // finish the activity or remove the tab. This will ensure
1993 // pauseWebView() taking action.
1994 mPageStarted = false;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001995 if (mTabControl.getTabCount() == 1) {
1996 finish();
1997 return;
1998 }
Mike Reed7bfa63b2009-05-28 11:08:32 -04001999 // call pauseWebViewTimers() now, we won't be able to call
2000 // it in onPause() as the WebView won't be valid.
Grace Klobaec1b5ad2009-08-18 08:42:32 -07002001 // Temporarily change mActivityInPause to be true as
2002 // pauseWebViewTimers() will do nothing if mActivityInPause
2003 // is false.
Grace Kloba918e1d72009-08-13 14:55:06 -07002004 boolean savedState = mActivityInPause;
2005 if (savedState) {
Grace Klobaec1b5ad2009-08-18 08:42:32 -07002006 Log.e(LOGTAG, "BrowserActivity is already paused "
2007 + "while handing goBackOnePageOrQuit.");
Grace Kloba918e1d72009-08-13 14:55:06 -07002008 }
2009 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002010 pauseWebViewTimers();
Grace Kloba918e1d72009-08-13 14:55:06 -07002011 mActivityInPause = savedState;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002012 removeTabFromContentView(current);
2013 mTabControl.removeTab(current);
2014 }
2015 /*
2016 * Instead of finishing the activity, simply push this to the back
2017 * of the stack and let ActivityManager to choose the foreground
2018 * activity. As BrowserActivity is singleTask, it will be always the
2019 * root of the task. So we can use either true or false for
2020 * moveTaskToBack().
2021 */
2022 moveTaskToBack(true);
2023 }
2024 }
2025 }
2026
2027 public KeyTracker.State onKeyTracker(int keyCode,
2028 KeyEvent event,
2029 KeyTracker.Stage stage,
2030 int duration) {
2031 // if onKeyTracker() is called after activity onStop()
2032 // because of accumulated key events,
2033 // we should ignore it as browser is not active any more.
2034 WebView topWindow = getTopWindow();
Andrei Popescuadc008d2009-06-26 14:11:30 +01002035 if (topWindow == null && mCustomView == null)
The Android Open Source Project0c908882009-03-03 19:32:16 -08002036 return KeyTracker.State.NOT_TRACKING;
2037
2038 if (keyCode == KeyEvent.KEYCODE_BACK) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01002039 // Check if a custom view is currently showing and, if it is, hide it.
2040 if (mCustomView != null) {
2041 mWebChromeClient.onHideCustomView();
2042 return KeyTracker.State.DONE_TRACKING;
2043 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002044 if (stage == KeyTracker.Stage.LONG_REPEAT) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04002045 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002046 return KeyTracker.State.DONE_TRACKING;
2047 } else if (stage == KeyTracker.Stage.UP) {
2048 // FIXME: Currently, we do not have a notion of the
2049 // history picker for the subwindow, but maybe we
2050 // should?
2051 WebView subwindow = mTabControl.getCurrentSubWindow();
2052 if (subwindow != null) {
2053 if (subwindow.canGoBack()) {
2054 subwindow.goBack();
2055 } else {
2056 dismissSubWindow(mTabControl.getCurrentTab());
2057 }
2058 } else {
2059 goBackOnePageOrQuit();
2060 }
2061 return KeyTracker.State.DONE_TRACKING;
2062 }
2063 return KeyTracker.State.KEEP_TRACKING;
2064 }
2065 return KeyTracker.State.NOT_TRACKING;
2066 }
2067
2068 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2069 if (keyCode == KeyEvent.KEYCODE_MENU) {
2070 mMenuIsDown = true;
Grace Kloba6ee9c492009-07-13 10:04:34 -07002071 } else if (mMenuIsDown) {
2072 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2073 // still down, we don't want to trigger the search. Pretend to
2074 // consume the key and do nothing.
2075 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002076 }
2077 boolean handled = mKeyTracker.doKeyDown(keyCode, event);
2078 if (!handled) {
2079 switch (keyCode) {
2080 case KeyEvent.KEYCODE_SPACE:
2081 if (event.isShiftPressed()) {
2082 getTopWindow().pageUp(false);
2083 } else {
2084 getTopWindow().pageDown(false);
2085 }
2086 handled = true;
2087 break;
2088
2089 default:
2090 break;
2091 }
2092 }
2093 return handled || super.onKeyDown(keyCode, event);
2094 }
2095
2096 @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2097 if (keyCode == KeyEvent.KEYCODE_MENU) {
2098 mMenuIsDown = false;
2099 }
2100 return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2101 }
2102
2103 private void stopLoading() {
2104 resetTitleAndRevertLockIcon();
2105 WebView w = getTopWindow();
2106 w.stopLoading();
2107 mWebViewClient.onPageFinished(w, w.getUrl());
2108
2109 cancelStopToast();
2110 mStopToast = Toast
2111 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2112 mStopToast.show();
2113 }
2114
2115 private void cancelStopToast() {
2116 if (mStopToast != null) {
2117 mStopToast.cancel();
2118 mStopToast = null;
2119 }
2120 }
2121
2122 // called by a non-UI thread to post the message
2123 public void postMessage(int what, int arg1, int arg2, Object obj) {
2124 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2125 }
2126
2127 // public message ids
2128 public final static int LOAD_URL = 1001;
2129 public final static int STOP_LOAD = 1002;
2130
2131 // Message Ids
2132 private static final int FOCUS_NODE_HREF = 102;
2133 private static final int CANCEL_CREDS_REQUEST = 103;
Grace Kloba92c18a52009-07-31 23:48:32 -07002134 private static final int RELEASE_WAKELOCK = 107;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002135
2136 // Private handler for handling javascript and saving passwords
2137 private Handler mHandler = new Handler() {
2138
2139 public void handleMessage(Message msg) {
2140 switch (msg.what) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002141 case FOCUS_NODE_HREF:
2142 String url = (String) msg.getData().get("url");
2143 if (url == null || url.length() == 0) {
2144 break;
2145 }
2146 HashMap focusNodeMap = (HashMap) msg.obj;
2147 WebView view = (WebView) focusNodeMap.get("webview");
2148 // Only apply the action if the top window did not change.
2149 if (getTopWindow() != view) {
2150 break;
2151 }
2152 switch (msg.arg1) {
2153 case R.id.open_context_menu_id:
2154 case R.id.view_image_context_menu_id:
2155 loadURL(getTopWindow(), url);
2156 break;
2157 case R.id.open_newtab_context_menu_id:
Grace Klobac9181842009-04-14 08:53:22 -07002158 final TabControl.Tab parent = mTabControl
2159 .getCurrentTab();
2160 final TabControl.Tab newTab = openTab(url);
2161 if (newTab != parent) {
2162 parent.addChildTab(newTab);
2163 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002164 break;
2165 case R.id.bookmark_context_menu_id:
2166 Intent intent = new Intent(BrowserActivity.this,
2167 AddBookmarkPage.class);
2168 intent.putExtra("url", url);
2169 startActivity(intent);
2170 break;
2171 case R.id.share_link_context_menu_id:
2172 Browser.sendString(BrowserActivity.this, url);
2173 break;
2174 case R.id.copy_link_context_menu_id:
2175 copy(url);
2176 break;
2177 case R.id.save_link_context_menu_id:
2178 case R.id.download_context_menu_id:
2179 onDownloadStartNoStream(url, null, null, null, -1);
2180 break;
2181 }
2182 break;
2183
2184 case LOAD_URL:
2185 loadURL(getTopWindow(), (String) msg.obj);
2186 break;
2187
2188 case STOP_LOAD:
2189 stopLoading();
2190 break;
2191
2192 case CANCEL_CREDS_REQUEST:
2193 resumeAfterCredentials();
2194 break;
2195
The Android Open Source Project0c908882009-03-03 19:32:16 -08002196 case RELEASE_WAKELOCK:
2197 if (mWakeLock.isHeld()) {
2198 mWakeLock.release();
2199 }
2200 break;
2201 }
2202 }
2203 };
2204
Leon Scroggins89c6d362009-07-15 16:54:37 -04002205 private void updateScreenshot(WebView view) {
2206 // If this is a bookmarked site, add a screenshot to the database.
2207 // FIXME: When should we update? Every time?
2208 // FIXME: Would like to make sure there is actually something to
2209 // draw, but the API for that (WebViewCore.pictureReady()) is not
2210 // currently accessible here.
Patrick Scott3918d442009-08-04 13:22:29 -04002211 ContentResolver cr = getContentResolver();
2212 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04002213 cr, view.getOriginalUrl(), view.getUrl(), false);
Patrick Scott3918d442009-08-04 13:22:29 -04002214 if (c != null) {
Leon Scroggins89c6d362009-07-15 16:54:37 -04002215 boolean succeed = c.moveToFirst();
2216 ContentValues values = null;
2217 while (succeed) {
2218 if (values == null) {
2219 final ByteArrayOutputStream os
2220 = new ByteArrayOutputStream();
2221 Picture thumbnail = view.capturePicture();
2222 // Keep width and height in sync with BrowserBookmarksPage
2223 // and bookmark_thumb
2224 Bitmap bm = Bitmap.createBitmap(100, 80,
2225 Bitmap.Config.ARGB_4444);
2226 Canvas canvas = new Canvas(bm);
2227 // May need to tweak these values to determine what is the
2228 // best scale factor
2229 canvas.scale(.5f, .5f);
2230 thumbnail.draw(canvas);
2231 bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2232 values = new ContentValues();
2233 values.put(Browser.BookmarkColumns.THUMBNAIL,
2234 os.toByteArray());
2235 }
2236 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2237 c.getInt(0)), values, null, null);
2238 succeed = c.moveToNext();
2239 }
2240 c.close();
2241 }
2242 }
2243
The Android Open Source Project0c908882009-03-03 19:32:16 -08002244 // -------------------------------------------------------------------------
2245 // WebViewClient implementation.
2246 //-------------------------------------------------------------------------
2247
2248 // Use in overrideUrlLoading
2249 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2250 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2251 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2252 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2253
2254 /* package */ WebViewClient getWebViewClient() {
2255 return mWebViewClient;
2256 }
2257
Patrick Scott3918d442009-08-04 13:22:29 -04002258 private void updateIcon(WebView view, Bitmap icon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002259 if (icon != null) {
2260 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
Patrick Scott3918d442009-08-04 13:22:29 -04002261 view, icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002262 }
2263 setFavicon(icon);
2264 }
2265
2266 private final WebViewClient mWebViewClient = new WebViewClient() {
2267 @Override
2268 public void onPageStarted(WebView view, String url, Bitmap favicon) {
2269 resetLockIcon(url);
Leon Scroggins1f005d32009-08-10 17:36:42 -04002270 setUrlTitle(url, null, view);
Ben Murdochbff2d602009-07-01 20:19:05 +01002271
2272 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2273 if (errorConsole != null) {
2274 errorConsole.clearErrorMessages();
2275 if (mShouldShowErrorConsole) {
2276 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2277 }
2278 }
2279
The Android Open Source Project0c908882009-03-03 19:32:16 -08002280 // Call updateIcon instead of setFavicon so the bookmark
2281 // database can be updated.
Patrick Scott3918d442009-08-04 13:22:29 -04002282 updateIcon(view, favicon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002283
Grace Kloba4d7880f2009-08-12 09:35:42 -07002284 if (mSettings.isTracing()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002285 String host;
2286 try {
2287 WebAddress uri = new WebAddress(url);
2288 host = uri.mHost;
2289 } catch (android.net.ParseException ex) {
Grace Kloba4d7880f2009-08-12 09:35:42 -07002290 host = "browser";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002291 }
2292 host = host.replace('.', '_');
Grace Kloba4d7880f2009-08-12 09:35:42 -07002293 host += ".trace";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002294 mInTrace = true;
Grace Kloba4d7880f2009-08-12 09:35:42 -07002295 Debug.startMethodTracing(host, 20 * 1024 * 1024);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002296 }
2297
2298 // Performance probe
2299 if (false) {
2300 mStart = SystemClock.uptimeMillis();
2301 mProcessStart = Process.getElapsedCpuTime();
2302 long[] sysCpu = new long[7];
2303 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2304 sysCpu, null)) {
2305 mUserStart = sysCpu[0] + sysCpu[1];
2306 mSystemStart = sysCpu[2];
2307 mIdleStart = sysCpu[3];
2308 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2309 }
2310 mUiStart = SystemClock.currentThreadTimeMillis();
2311 }
2312
2313 if (!mPageStarted) {
2314 mPageStarted = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002315 // if onResume() has been called, resumeWebViewTimers() does
2316 // nothing.
2317 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002318 }
2319
2320 // reset sync timer to avoid sync starts during loading a page
2321 CookieSyncManager.getInstance().resetSync();
2322
2323 mInLoad = true;
2324 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
2880 public boolean onCreateWindow(WebView view, final boolean dialog,
2881 final boolean userGesture, final Message resultMsg) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002882 // Short-circuit if we can't create any more tabs or sub windows.
2883 if (dialog && mTabControl.getCurrentSubWindow() != null) {
2884 new AlertDialog.Builder(BrowserActivity.this)
2885 .setTitle(R.string.too_many_subwindows_dialog_title)
2886 .setIcon(android.R.drawable.ic_dialog_alert)
2887 .setMessage(R.string.too_many_subwindows_dialog_message)
2888 .setPositiveButton(R.string.ok, null)
2889 .show();
2890 return false;
2891 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
2892 new AlertDialog.Builder(BrowserActivity.this)
2893 .setTitle(R.string.too_many_windows_dialog_title)
2894 .setIcon(android.R.drawable.ic_dialog_alert)
2895 .setMessage(R.string.too_many_windows_dialog_message)
2896 .setPositiveButton(R.string.ok, null)
2897 .show();
2898 return false;
2899 }
2900
2901 // Short-circuit if this was a user gesture.
2902 if (userGesture) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002903 createWindow(dialog, resultMsg);
2904 return true;
2905 }
2906
2907 // Allow the popup and create the appropriate window.
2908 final AlertDialog.OnClickListener allowListener =
2909 new AlertDialog.OnClickListener() {
2910 public void onClick(DialogInterface d,
2911 int which) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002912 createWindow(dialog, resultMsg);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002913 }
2914 };
2915
2916 // Block the popup by returning a null WebView.
2917 final AlertDialog.OnClickListener blockListener =
2918 new AlertDialog.OnClickListener() {
2919 public void onClick(DialogInterface d, int which) {
2920 resultMsg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002921 }
2922 };
2923
2924 // Build a confirmation dialog to display to the user.
2925 final AlertDialog d =
2926 new AlertDialog.Builder(BrowserActivity.this)
2927 .setTitle(R.string.attention)
2928 .setIcon(android.R.drawable.ic_dialog_alert)
2929 .setMessage(R.string.popup_window_attempt)
2930 .setPositiveButton(R.string.allow, allowListener)
2931 .setNegativeButton(R.string.block, blockListener)
2932 .setCancelable(false)
2933 .create();
2934
2935 // Show the confirmation dialog.
2936 d.show();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002937 return true;
2938 }
2939
2940 @Override
2941 public void onCloseWindow(WebView window) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002942 final TabControl.Tab current = mTabControl.getCurrentTab();
2943 final TabControl.Tab parent = current.getParentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002944 if (parent != null) {
2945 // JavaScript can only close popup window.
Leon Scroggins1f005d32009-08-10 17:36:42 -04002946 switchToTab(mTabControl.getTabIndex(parent));
2947 // Now we need to close the window
2948 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002949 }
2950 }
2951
2952 @Override
2953 public void onProgressChanged(WebView view, int newProgress) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002954 if (CUSTOM_BROWSER_BAR) {
2955 mTitleBar.setProgress(newProgress, view);
2956 } else {
2957 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
2958 newProgress * 100);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002959 }
2960
2961 if (newProgress == 100) {
2962 // onProgressChanged() is called for sub-frame too while
2963 // onPageFinished() is only called for the main frame. sync
2964 // cookie and cache promptly here.
2965 CookieSyncManager.getInstance().sync();
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002966 if (mInLoad) {
2967 mInLoad = false;
2968 updateInLoadMenuItems();
2969 }
2970 } else {
2971 // onPageFinished may have already been called but a subframe
2972 // is still loading and updating the progress. Reset mInLoad
2973 // and update the menu items.
2974 if (!mInLoad) {
2975 mInLoad = true;
2976 updateInLoadMenuItems();
2977 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002978 }
2979 }
2980
2981 @Override
2982 public void onReceivedTitle(WebView view, String title) {
Patrick Scott598c9cc2009-06-04 11:10:38 -04002983 String url = view.getUrl();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002984
2985 // here, if url is null, we want to reset the title
Leon Scroggins1f005d32009-08-10 17:36:42 -04002986 setUrlTitle(url, title, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002987
2988 if (url == null ||
2989 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
2990 return;
2991 }
Leon Scrogginsfce182b2009-05-08 13:54:52 -04002992 // See if we can find the current url in our history database and
2993 // add the new title to it.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002994 if (url.startsWith("http://www.")) {
2995 url = url.substring(11);
2996 } else if (url.startsWith("http://")) {
2997 url = url.substring(4);
2998 }
2999 try {
3000 url = "%" + url;
3001 String [] selArgs = new String[] { url };
3002
3003 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
3004 + Browser.BookmarkColumns.BOOKMARK + " = 0";
3005 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
3006 Browser.HISTORY_PROJECTION, where, selArgs, null);
3007 if (c.moveToFirst()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003008 // Current implementation of database only has one entry per
3009 // url.
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003010 ContentValues map = new ContentValues();
3011 map.put(Browser.BookmarkColumns.TITLE, title);
3012 mResolver.update(Browser.BOOKMARKS_URI, map,
3013 "_id = " + c.getInt(0), null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003014 }
3015 c.close();
3016 } catch (IllegalStateException e) {
3017 Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3018 } catch (SQLiteException ex) {
3019 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3020 }
3021 }
3022
3023 @Override
3024 public void onReceivedIcon(WebView view, Bitmap icon) {
Patrick Scott3918d442009-08-04 13:22:29 -04003025 updateIcon(view, icon);
3026 }
3027
3028 @Override
3029 public void onReceivedTouchIconUrl(WebView view, String url) {
3030 final ContentResolver cr = getContentResolver();
3031 final Cursor c =
3032 BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04003033 view.getOriginalUrl(), view.getUrl(), true);
Patrick Scott3918d442009-08-04 13:22:29 -04003034 if (c != null) {
3035 if (c.getCount() > 0) {
3036 new DownloadTouchIcon(cr, c, view).execute(url);
3037 } else {
3038 c.close();
3039 }
3040 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003041 }
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003042
Andrei Popescuadc008d2009-06-26 14:11:30 +01003043 @Override
Andrei Popescuc9b55562009-07-07 10:51:15 +01003044 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01003045 if (mCustomView != null)
3046 return;
3047
3048 // Add the custom view to its container.
3049 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
3050 mCustomView = view;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003051 mCustomViewCallback = callback;
Andrei Popescuadc008d2009-06-26 14:11:30 +01003052 // Save the menu state and set it to empty while the custom
3053 // view is showing.
3054 mOldMenuState = mMenuState;
3055 mMenuState = EMPTY_MENU;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003056 // Hide the content view.
3057 mContentView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003058 // Finally show the custom view container.
Andrei Popescuc9b55562009-07-07 10:51:15 +01003059 mCustomViewContainer.setVisibility(View.VISIBLE);
3060 mCustomViewContainer.bringToFront();
Andrei Popescuadc008d2009-06-26 14:11:30 +01003061 }
3062
3063 @Override
3064 public void onHideCustomView() {
3065 if (mCustomView == null)
3066 return;
3067
Andrei Popescuc9b55562009-07-07 10:51:15 +01003068 // Hide the custom view.
3069 mCustomView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003070 // Remove the custom view from its container.
3071 mCustomViewContainer.removeView(mCustomView);
3072 mCustomView = null;
3073 // Reset the old menu state.
3074 mMenuState = mOldMenuState;
3075 mOldMenuState = EMPTY_MENU;
3076 mCustomViewContainer.setVisibility(View.GONE);
Andrei Popescuc9b55562009-07-07 10:51:15 +01003077 mCustomViewCallback.onCustomViewHidden();
3078 // Show the content view.
3079 mContentView.setVisibility(View.VISIBLE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003080 }
3081
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003082 /**
Andrei Popescu79e82b72009-07-27 12:01:59 +01003083 * The origin has exceeded its database quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003084 * @param url the URL that exceeded the quota
3085 * @param databaseIdentifier the identifier of the database on
3086 * which the transaction that caused the quota overflow was run
3087 * @param currentQuota the current quota for the origin.
Ben Murdoch25a15232009-08-25 19:38:07 +01003088 * @param estimatedSize the estimated size of the database.
Andrei Popescu79e82b72009-07-27 12:01:59 +01003089 * @param totalUsedQuota is the sum of all origins' quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003090 * @param quotaUpdater The callback to run when a decision to allow or
3091 * deny quota has been made. Don't forget to call this!
3092 */
3093 @Override
3094 public void onExceededDatabaseQuota(String url,
Ben Murdoch25a15232009-08-25 19:38:07 +01003095 String databaseIdentifier, long currentQuota, long estimatedSize,
3096 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
Andrei Popescu79e82b72009-07-27 12:01:59 +01003097 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
Ben Murdoch25a15232009-08-25 19:38:07 +01003098 url, databaseIdentifier, currentQuota, estimatedSize,
3099 totalUsedQuota, quotaUpdater);
Andrei Popescu79e82b72009-07-27 12:01:59 +01003100 }
3101
3102 /**
3103 * The Application Cache has exceeded its max size.
3104 * @param spaceNeeded is the amount of disk space that would be needed
3105 * in order for the last appcache operation to succeed.
3106 * @param totalUsedQuota is the sum of all origins' quota.
3107 * @param quotaUpdater A callback to inform the WebCore thread that a new
3108 * app cache size is available. This callback must always be executed at
3109 * some point to ensure that the sleeping WebCore thread is woken up.
3110 */
3111 @Override
3112 public void onReachedMaxAppCacheSize(long spaceNeeded,
3113 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3114 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3115 spaceNeeded, totalUsedQuota, quotaUpdater);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003116 }
Ben Murdoch7db26342009-06-03 18:21:19 +01003117
Steve Block2bc69912009-07-30 14:45:13 +01003118 /**
3119 * Instructs the browser to show a prompt to ask the user to set the
3120 * Geolocation permission state for the specified origin.
3121 * @param origin The origin for which Geolocation permissions are
3122 * requested.
3123 * @param callback The callback to call once the user has set the
3124 * Geolocation permission state.
3125 */
3126 @Override
3127 public void onGeolocationPermissionsShowPrompt(String origin,
3128 GeolocationPermissions.Callback callback) {
3129 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3130 origin, callback);
3131 }
3132
3133 /**
3134 * Instructs the browser to hide the Geolocation permissions prompt.
3135 */
3136 @Override
3137 public void onGeolocationPermissionsHidePrompt() {
3138 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3139 }
3140
Ben Murdoch7db26342009-06-03 18:21:19 +01003141 /* Adds a JavaScript error message to the system log.
3142 * @param message The error message to report.
3143 * @param lineNumber The line number of the error.
3144 * @param sourceID The name of the source file that caused the error.
3145 */
3146 @Override
3147 public void addMessageToConsole(String message, int lineNumber, String sourceID) {
Ben Murdochbff2d602009-07-01 20:19:05 +01003148 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3149 errorConsole.addErrorMessage(message, sourceID, lineNumber);
3150 if (mShouldShowErrorConsole &&
3151 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3152 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3153 }
3154 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
Ben Murdoch7db26342009-06-03 18:21:19 +01003155 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003156 };
3157
3158 /**
3159 * Notify the host application a download should be done, or that
3160 * the data should be streamed if a streaming viewer is available.
3161 * @param url The full url to the content that should be downloaded
3162 * @param contentDisposition Content-disposition http header, if
3163 * present.
3164 * @param mimetype The mimetype of the content reported by the server
3165 * @param contentLength The file size reported by the server
3166 */
3167 public void onDownloadStart(String url, String userAgent,
3168 String contentDisposition, String mimetype, long contentLength) {
3169 // if we're dealing wih A/V content that's not explicitly marked
3170 // for download, check if it's streamable.
3171 if (contentDisposition == null
Patrick Scotte1fb9662009-08-31 14:31:52 -04003172 || !contentDisposition.regionMatches(
3173 true, 0, "attachment", 0, 10)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003174 // query the package manager to see if there's a registered handler
3175 // that matches.
3176 Intent intent = new Intent(Intent.ACTION_VIEW);
3177 intent.setDataAndType(Uri.parse(url), mimetype);
Patrick Scotte1fb9662009-08-31 14:31:52 -04003178 ResolveInfo info = getPackageManager().resolveActivity(intent,
3179 PackageManager.MATCH_DEFAULT_ONLY);
3180 if (info != null) {
3181 ComponentName myName = getComponentName();
3182 // If we resolved to ourselves, we don't want to attempt to
3183 // load the url only to try and download it again.
3184 if (!myName.getPackageName().equals(
3185 info.activityInfo.packageName)
3186 || !myName.getClassName().equals(
3187 info.activityInfo.name)) {
3188 // someone (other than us) knows how to handle this mime
3189 // type with this scheme, don't download.
3190 try {
3191 startActivity(intent);
3192 return;
3193 } catch (ActivityNotFoundException ex) {
3194 if (LOGD_ENABLED) {
3195 Log.d(LOGTAG, "activity not found for " + mimetype
3196 + " over " + Uri.parse(url).getScheme(),
3197 ex);
3198 }
3199 // Best behavior is to fall back to a download in this
3200 // case
The Android Open Source Project0c908882009-03-03 19:32:16 -08003201 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003202 }
3203 }
3204 }
3205 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3206 }
3207
3208 /**
3209 * Notify the host application a download should be done, even if there
3210 * is a streaming viewer available for thise type.
3211 * @param url The full url to the content that should be downloaded
3212 * @param contentDisposition Content-disposition http header, if
3213 * present.
3214 * @param mimetype The mimetype of the content reported by the server
3215 * @param contentLength The file size reported by the server
3216 */
3217 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3218 String contentDisposition, String mimetype, long contentLength) {
3219
3220 String filename = URLUtil.guessFileName(url,
3221 contentDisposition, mimetype);
3222
3223 // Check to see if we have an SDCard
3224 String status = Environment.getExternalStorageState();
3225 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3226 int title;
3227 String msg;
3228
3229 // Check to see if the SDCard is busy, same as the music app
3230 if (status.equals(Environment.MEDIA_SHARED)) {
3231 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3232 title = R.string.download_sdcard_busy_dlg_title;
3233 } else {
3234 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3235 title = R.string.download_no_sdcard_dlg_title;
3236 }
3237
3238 new AlertDialog.Builder(this)
3239 .setTitle(title)
3240 .setIcon(android.R.drawable.ic_dialog_alert)
3241 .setMessage(msg)
3242 .setPositiveButton(R.string.ok, null)
3243 .show();
3244 return;
3245 }
3246
3247 // java.net.URI is a lot stricter than KURL so we have to undo
3248 // KURL's percent-encoding and redo the encoding using java.net.URI.
3249 URI uri = null;
3250 try {
3251 // Undo the percent-encoding that KURL may have done.
3252 String newUrl = new String(URLUtil.decode(url.getBytes()));
3253 // Parse the url into pieces
3254 WebAddress w = new WebAddress(newUrl);
3255 String frag = null;
3256 String query = null;
3257 String path = w.mPath;
3258 // Break the path into path, query, and fragment
3259 if (path.length() > 0) {
3260 // Strip the fragment
3261 int idx = path.lastIndexOf('#');
3262 if (idx != -1) {
3263 frag = path.substring(idx + 1);
3264 path = path.substring(0, idx);
3265 }
3266 idx = path.lastIndexOf('?');
3267 if (idx != -1) {
3268 query = path.substring(idx + 1);
3269 path = path.substring(0, idx);
3270 }
3271 }
3272 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3273 query, frag);
3274 } catch (Exception e) {
3275 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3276 return;
3277 }
3278
3279 // XXX: Have to use the old url since the cookies were stored using the
3280 // old percent-encoded url.
3281 String cookies = CookieManager.getInstance().getCookie(url);
3282
3283 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003284 values.put(Downloads.COLUMN_URI, uri.toString());
3285 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3286 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3287 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003288 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003289 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003290 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003291 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3292 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3293 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3294 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003295 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003296 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003297 }
3298 if (mimetype == null) {
3299 // We must have long pressed on a link or image to download it. We
3300 // are not sure of the mimetype in this case, so do a head request
3301 new FetchUrlMimeType(this).execute(values);
3302 } else {
3303 final Uri contentUri =
3304 getContentResolver().insert(Downloads.CONTENT_URI, values);
3305 viewDownloads(contentUri);
3306 }
3307
3308 }
3309
3310 /**
3311 * Resets the lock icon. This method is called when we start a new load and
3312 * know the url to be loaded.
3313 */
3314 private void resetLockIcon(String url) {
3315 // Save the lock-icon state (we revert to it if the load gets cancelled)
3316 saveLockIcon();
3317
3318 mLockIconType = LOCK_ICON_UNSECURE;
3319 if (URLUtil.isHttpsUrl(url)) {
3320 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003321 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003322 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3323 " reset lock icon to " + mLockIconType);
3324 }
3325 }
3326
3327 updateLockIconImage(LOCK_ICON_UNSECURE);
3328 }
3329
3330 /**
3331 * Resets the lock icon. This method is called when the icon needs to be
3332 * reset but we do not know whether we are loading a secure or not secure
3333 * page.
3334 */
3335 private void resetLockIcon() {
3336 // Save the lock-icon state (we revert to it if the load gets cancelled)
3337 saveLockIcon();
3338
3339 mLockIconType = LOCK_ICON_UNSECURE;
3340
Dave Bort31a6d1c2009-04-13 15:56:49 -07003341 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003342 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3343 " reset lock icon to " + mLockIconType);
3344 }
3345
3346 updateLockIconImage(LOCK_ICON_UNSECURE);
3347 }
3348
3349 /**
3350 * Updates the lock-icon image in the title-bar.
3351 */
3352 private void updateLockIconImage(int lockIconType) {
3353 Drawable d = null;
3354 if (lockIconType == LOCK_ICON_SECURE) {
3355 d = mSecLockIcon;
3356 } else if (lockIconType == LOCK_ICON_MIXED) {
3357 d = mMixLockIcon;
3358 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04003359 if (CUSTOM_BROWSER_BAR) {
3360 mTitleBar.setLock(d, getTopWindow());
3361 } else {
3362 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003363 }
3364 }
3365
3366 /**
3367 * Displays a page-info dialog.
3368 * @param tab The tab to show info about
3369 * @param fromShowSSLCertificateOnError The flag that indicates whether
3370 * this dialog was opened from the SSL-certificate-on-error dialog or
3371 * not. This is important, since we need to know whether to return to
3372 * the parent dialog or simply dismiss.
3373 */
3374 private void showPageInfo(final TabControl.Tab tab,
3375 final boolean fromShowSSLCertificateOnError) {
3376 final LayoutInflater factory = LayoutInflater
3377 .from(this);
3378
3379 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3380
3381 final WebView view = tab.getWebView();
3382
3383 String url = null;
3384 String title = null;
3385
3386 if (view == null) {
3387 url = tab.getUrl();
3388 title = tab.getTitle();
3389 } else if (view == mTabControl.getCurrentWebView()) {
3390 // Use the cached title and url if this is the current WebView
3391 url = mUrl;
3392 title = mTitle;
3393 } else {
3394 url = view.getUrl();
3395 title = view.getTitle();
3396 }
3397
3398 if (url == null) {
3399 url = "";
3400 }
3401 if (title == null) {
3402 title = "";
3403 }
3404
3405 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3406 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3407
3408 mPageInfoView = tab;
3409 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3410
3411 AlertDialog.Builder alertDialogBuilder =
3412 new AlertDialog.Builder(this)
3413 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3414 .setView(pageInfoView)
3415 .setPositiveButton(
3416 R.string.ok,
3417 new DialogInterface.OnClickListener() {
3418 public void onClick(DialogInterface dialog,
3419 int whichButton) {
3420 mPageInfoDialog = null;
3421 mPageInfoView = null;
3422 mPageInfoFromShowSSLCertificateOnError = null;
3423
3424 // if we came here from the SSL error dialog
3425 if (fromShowSSLCertificateOnError) {
3426 // go back to the SSL error dialog
3427 showSSLCertificateOnError(
3428 mSSLCertificateOnErrorView,
3429 mSSLCertificateOnErrorHandler,
3430 mSSLCertificateOnErrorError);
3431 }
3432 }
3433 })
3434 .setOnCancelListener(
3435 new DialogInterface.OnCancelListener() {
3436 public void onCancel(DialogInterface dialog) {
3437 mPageInfoDialog = null;
3438 mPageInfoView = null;
3439 mPageInfoFromShowSSLCertificateOnError = null;
3440
3441 // if we came here from the SSL error dialog
3442 if (fromShowSSLCertificateOnError) {
3443 // go back to the SSL error dialog
3444 showSSLCertificateOnError(
3445 mSSLCertificateOnErrorView,
3446 mSSLCertificateOnErrorHandler,
3447 mSSLCertificateOnErrorError);
3448 }
3449 }
3450 });
3451
3452 // if we have a main top-level page SSL certificate set or a certificate
3453 // error
3454 if (fromShowSSLCertificateOnError ||
3455 (view != null && view.getCertificate() != null)) {
3456 // add a 'View Certificate' button
3457 alertDialogBuilder.setNeutralButton(
3458 R.string.view_certificate,
3459 new DialogInterface.OnClickListener() {
3460 public void onClick(DialogInterface dialog,
3461 int whichButton) {
3462 mPageInfoDialog = null;
3463 mPageInfoView = null;
3464 mPageInfoFromShowSSLCertificateOnError = null;
3465
3466 // if we came here from the SSL error dialog
3467 if (fromShowSSLCertificateOnError) {
3468 // go back to the SSL error dialog
3469 showSSLCertificateOnError(
3470 mSSLCertificateOnErrorView,
3471 mSSLCertificateOnErrorHandler,
3472 mSSLCertificateOnErrorError);
3473 } else {
3474 // otherwise, display the top-most certificate from
3475 // the chain
3476 if (view.getCertificate() != null) {
3477 showSSLCertificate(tab);
3478 }
3479 }
3480 }
3481 });
3482 }
3483
3484 mPageInfoDialog = alertDialogBuilder.show();
3485 }
3486
3487 /**
3488 * Displays the main top-level page SSL certificate dialog
3489 * (accessible from the Page-Info dialog).
3490 * @param tab The tab to show certificate for.
3491 */
3492 private void showSSLCertificate(final TabControl.Tab tab) {
3493 final View certificateView =
3494 inflateCertificateView(tab.getWebView().getCertificate());
3495 if (certificateView == null) {
3496 return;
3497 }
3498
3499 LayoutInflater factory = LayoutInflater.from(this);
3500
3501 final LinearLayout placeholder =
3502 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3503
3504 LinearLayout ll = (LinearLayout) factory.inflate(
3505 R.layout.ssl_success, placeholder);
3506 ((TextView)ll.findViewById(R.id.success))
3507 .setText(R.string.ssl_certificate_is_valid);
3508
3509 mSSLCertificateView = tab;
3510 mSSLCertificateDialog =
3511 new AlertDialog.Builder(this)
3512 .setTitle(R.string.ssl_certificate).setIcon(
3513 R.drawable.ic_dialog_browser_certificate_secure)
3514 .setView(certificateView)
3515 .setPositiveButton(R.string.ok,
3516 new DialogInterface.OnClickListener() {
3517 public void onClick(DialogInterface dialog,
3518 int whichButton) {
3519 mSSLCertificateDialog = null;
3520 mSSLCertificateView = null;
3521
3522 showPageInfo(tab, false);
3523 }
3524 })
3525 .setOnCancelListener(
3526 new DialogInterface.OnCancelListener() {
3527 public void onCancel(DialogInterface dialog) {
3528 mSSLCertificateDialog = null;
3529 mSSLCertificateView = null;
3530
3531 showPageInfo(tab, false);
3532 }
3533 })
3534 .show();
3535 }
3536
3537 /**
3538 * Displays the SSL error certificate dialog.
3539 * @param view The target web-view.
3540 * @param handler The SSL error handler responsible for cancelling the
3541 * connection that resulted in an SSL error or proceeding per user request.
3542 * @param error The SSL error object.
3543 */
3544 private void showSSLCertificateOnError(
3545 final WebView view, final SslErrorHandler handler, final SslError error) {
3546
3547 final View certificateView =
3548 inflateCertificateView(error.getCertificate());
3549 if (certificateView == null) {
3550 return;
3551 }
3552
3553 LayoutInflater factory = LayoutInflater.from(this);
3554
3555 final LinearLayout placeholder =
3556 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3557
3558 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3559 LinearLayout ll = (LinearLayout)factory
3560 .inflate(R.layout.ssl_warning, placeholder);
3561 ((TextView)ll.findViewById(R.id.warning))
3562 .setText(R.string.ssl_untrusted);
3563 }
3564
3565 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3566 LinearLayout ll = (LinearLayout)factory
3567 .inflate(R.layout.ssl_warning, placeholder);
3568 ((TextView)ll.findViewById(R.id.warning))
3569 .setText(R.string.ssl_mismatch);
3570 }
3571
3572 if (error.hasError(SslError.SSL_EXPIRED)) {
3573 LinearLayout ll = (LinearLayout)factory
3574 .inflate(R.layout.ssl_warning, placeholder);
3575 ((TextView)ll.findViewById(R.id.warning))
3576 .setText(R.string.ssl_expired);
3577 }
3578
3579 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3580 LinearLayout ll = (LinearLayout)factory
3581 .inflate(R.layout.ssl_warning, placeholder);
3582 ((TextView)ll.findViewById(R.id.warning))
3583 .setText(R.string.ssl_not_yet_valid);
3584 }
3585
3586 mSSLCertificateOnErrorHandler = handler;
3587 mSSLCertificateOnErrorView = view;
3588 mSSLCertificateOnErrorError = error;
3589 mSSLCertificateOnErrorDialog =
3590 new AlertDialog.Builder(this)
3591 .setTitle(R.string.ssl_certificate).setIcon(
3592 R.drawable.ic_dialog_browser_certificate_partially_secure)
3593 .setView(certificateView)
3594 .setPositiveButton(R.string.ok,
3595 new DialogInterface.OnClickListener() {
3596 public void onClick(DialogInterface dialog,
3597 int whichButton) {
3598 mSSLCertificateOnErrorDialog = null;
3599 mSSLCertificateOnErrorView = null;
3600 mSSLCertificateOnErrorHandler = null;
3601 mSSLCertificateOnErrorError = null;
3602
3603 mWebViewClient.onReceivedSslError(
3604 view, handler, error);
3605 }
3606 })
3607 .setNeutralButton(R.string.page_info_view,
3608 new DialogInterface.OnClickListener() {
3609 public void onClick(DialogInterface dialog,
3610 int whichButton) {
3611 mSSLCertificateOnErrorDialog = null;
3612
3613 // do not clear the dialog state: we will
3614 // need to show the dialog again once the
3615 // user is done exploring the page-info details
3616
3617 showPageInfo(mTabControl.getTabFromView(view),
3618 true);
3619 }
3620 })
3621 .setOnCancelListener(
3622 new DialogInterface.OnCancelListener() {
3623 public void onCancel(DialogInterface dialog) {
3624 mSSLCertificateOnErrorDialog = null;
3625 mSSLCertificateOnErrorView = null;
3626 mSSLCertificateOnErrorHandler = null;
3627 mSSLCertificateOnErrorError = null;
3628
3629 mWebViewClient.onReceivedSslError(
3630 view, handler, error);
3631 }
3632 })
3633 .show();
3634 }
3635
3636 /**
3637 * Inflates the SSL certificate view (helper method).
3638 * @param certificate The SSL certificate.
3639 * @return The resultant certificate view with issued-to, issued-by,
3640 * issued-on, expires-on, and possibly other fields set.
3641 * If the input certificate is null, returns null.
3642 */
3643 private View inflateCertificateView(SslCertificate certificate) {
3644 if (certificate == null) {
3645 return null;
3646 }
3647
3648 LayoutInflater factory = LayoutInflater.from(this);
3649
3650 View certificateView = factory.inflate(
3651 R.layout.ssl_certificate, null);
3652
3653 // issued to:
3654 SslCertificate.DName issuedTo = certificate.getIssuedTo();
3655 if (issuedTo != null) {
3656 ((TextView) certificateView.findViewById(R.id.to_common))
3657 .setText(issuedTo.getCName());
3658 ((TextView) certificateView.findViewById(R.id.to_org))
3659 .setText(issuedTo.getOName());
3660 ((TextView) certificateView.findViewById(R.id.to_org_unit))
3661 .setText(issuedTo.getUName());
3662 }
3663
3664 // issued by:
3665 SslCertificate.DName issuedBy = certificate.getIssuedBy();
3666 if (issuedBy != null) {
3667 ((TextView) certificateView.findViewById(R.id.by_common))
3668 .setText(issuedBy.getCName());
3669 ((TextView) certificateView.findViewById(R.id.by_org))
3670 .setText(issuedBy.getOName());
3671 ((TextView) certificateView.findViewById(R.id.by_org_unit))
3672 .setText(issuedBy.getUName());
3673 }
3674
3675 // issued on:
3676 String issuedOn = reformatCertificateDate(
3677 certificate.getValidNotBefore());
3678 ((TextView) certificateView.findViewById(R.id.issued_on))
3679 .setText(issuedOn);
3680
3681 // expires on:
3682 String expiresOn = reformatCertificateDate(
3683 certificate.getValidNotAfter());
3684 ((TextView) certificateView.findViewById(R.id.expires_on))
3685 .setText(expiresOn);
3686
3687 return certificateView;
3688 }
3689
3690 /**
3691 * Re-formats the certificate date (Date.toString()) string to
3692 * a properly localized date string.
3693 * @return Properly localized version of the certificate date string and
3694 * the original certificate date string if fails to localize.
3695 * If the original string is null, returns an empty string "".
3696 */
3697 private String reformatCertificateDate(String certificateDate) {
3698 String reformattedDate = null;
3699
3700 if (certificateDate != null) {
3701 Date date = null;
3702 try {
3703 date = java.text.DateFormat.getInstance().parse(certificateDate);
3704 } catch (ParseException e) {
3705 date = null;
3706 }
3707
3708 if (date != null) {
3709 reformattedDate =
3710 DateFormat.getDateFormat(this).format(date);
3711 }
3712 }
3713
3714 return reformattedDate != null ? reformattedDate :
3715 (certificateDate != null ? certificateDate : "");
3716 }
3717
3718 /**
3719 * Displays an http-authentication dialog.
3720 */
3721 private void showHttpAuthentication(final HttpAuthHandler handler,
3722 final String host, final String realm, final String title,
3723 final String name, final String password, int focusId) {
3724 LayoutInflater factory = LayoutInflater.from(this);
3725 final View v = factory
3726 .inflate(R.layout.http_authentication, null);
3727 if (name != null) {
3728 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3729 }
3730 if (password != null) {
3731 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3732 }
3733
3734 String titleText = title;
3735 if (titleText == null) {
3736 titleText = getText(R.string.sign_in_to).toString().replace(
3737 "%s1", host).replace("%s2", realm);
3738 }
3739
3740 mHttpAuthHandler = handler;
3741 AlertDialog dialog = new AlertDialog.Builder(this)
3742 .setTitle(titleText)
3743 .setIcon(android.R.drawable.ic_dialog_alert)
3744 .setView(v)
3745 .setPositiveButton(R.string.action,
3746 new DialogInterface.OnClickListener() {
3747 public void onClick(DialogInterface dialog,
3748 int whichButton) {
3749 String nm = ((EditText) v
3750 .findViewById(R.id.username_edit))
3751 .getText().toString();
3752 String pw = ((EditText) v
3753 .findViewById(R.id.password_edit))
3754 .getText().toString();
3755 BrowserActivity.this.setHttpAuthUsernamePassword
3756 (host, realm, nm, pw);
3757 handler.proceed(nm, pw);
3758 mHttpAuthenticationDialog = null;
3759 mHttpAuthHandler = null;
3760 }})
3761 .setNegativeButton(R.string.cancel,
3762 new DialogInterface.OnClickListener() {
3763 public void onClick(DialogInterface dialog,
3764 int whichButton) {
3765 handler.cancel();
3766 BrowserActivity.this.resetTitleAndRevertLockIcon();
3767 mHttpAuthenticationDialog = null;
3768 mHttpAuthHandler = null;
3769 }})
3770 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3771 public void onCancel(DialogInterface dialog) {
3772 handler.cancel();
3773 BrowserActivity.this.resetTitleAndRevertLockIcon();
3774 mHttpAuthenticationDialog = null;
3775 mHttpAuthHandler = null;
3776 }})
3777 .create();
3778 // Make the IME appear when the dialog is displayed if applicable.
3779 dialog.getWindow().setSoftInputMode(
3780 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3781 dialog.show();
3782 if (focusId != 0) {
3783 dialog.findViewById(focusId).requestFocus();
3784 } else {
3785 v.findViewById(R.id.username_edit).requestFocus();
3786 }
3787 mHttpAuthenticationDialog = dialog;
3788 }
3789
3790 public int getProgress() {
3791 WebView w = mTabControl.getCurrentWebView();
3792 if (w != null) {
3793 return w.getProgress();
3794 } else {
3795 return 100;
3796 }
3797 }
3798
3799 /**
3800 * Set HTTP authentication password.
3801 *
3802 * @param host The host for the password
3803 * @param realm The realm for the password
3804 * @param username The username for the password. If it is null, it means
3805 * password can't be saved.
3806 * @param password The password
3807 */
3808 public void setHttpAuthUsernamePassword(String host, String realm,
3809 String username,
3810 String password) {
3811 WebView w = mTabControl.getCurrentWebView();
3812 if (w != null) {
3813 w.setHttpAuthUsernamePassword(host, realm, username, password);
3814 }
3815 }
3816
3817 /**
3818 * connectivity manager says net has come or gone... inform the user
3819 * @param up true if net has come up, false if net has gone down
3820 */
3821 public void onNetworkToggle(boolean up) {
3822 if (up == mIsNetworkUp) {
3823 return;
3824 } else if (up) {
3825 mIsNetworkUp = true;
3826 if (mAlertDialog != null) {
3827 mAlertDialog.cancel();
3828 mAlertDialog = null;
3829 }
3830 } else {
3831 mIsNetworkUp = false;
3832 if (mInLoad && mAlertDialog == null) {
3833 mAlertDialog = new AlertDialog.Builder(this)
3834 .setTitle(R.string.loadSuspendedTitle)
3835 .setMessage(R.string.loadSuspended)
3836 .setPositiveButton(R.string.ok, null)
3837 .show();
3838 }
3839 }
3840 WebView w = mTabControl.getCurrentWebView();
3841 if (w != null) {
3842 w.setNetworkAvailable(up);
3843 }
3844 }
3845
3846 @Override
3847 protected void onActivityResult(int requestCode, int resultCode,
3848 Intent intent) {
3849 switch (requestCode) {
3850 case COMBO_PAGE:
3851 if (resultCode == RESULT_OK && intent != null) {
3852 String data = intent.getAction();
3853 Bundle extras = intent.getExtras();
3854 if (extras != null && extras.getBoolean("new_window", false)) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003855 final TabControl.Tab newTab = openTab(data);
3856 if (mSettings.openInBackground() &&
Leon Scroggins1f005d32009-08-10 17:36:42 -04003857 newTab != null) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003858 mTabControl.populatePickerData(newTab);
3859 mTabControl.setCurrentTab(newTab);
Leon Scroggins1f005d32009-08-10 17:36:42 -04003860 int newIndex = mTabControl.getCurrentIndex();
3861 if (CUSTOM_BROWSER_BAR) {
3862 mTitleBar.setCurrentTab(newIndex);
3863 }
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003864 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003865 } else {
3866 final TabControl.Tab currentTab =
3867 mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04003868 dismissSubWindow(currentTab);
3869 if (data != null && data.length() != 0) {
3870 getTopWindow().loadUrl(data);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003871 }
3872 }
Leon Scroggins39362092009-08-18 19:47:09 -04003873/*
3874 FIXME: Removing this breaks the behavior of pressing BACK from
3875 the Go page resulting in the window being closed. However, it
3876 needs to be removed so that the user can use the Search bar to
3877 enter a URL. Further, the Go behavior is going to change
3878 drastically, so this behavior may not last anyway.
Leon Scroggins160a7e72009-08-14 18:28:01 -04003879 } else if (resultCode == RESULT_CANCELED
3880 && mCancelGoPageMeansClose) {
3881 if (mTabControl.getTabCount() == 1) {
3882 // finish the Browser. When the Browser opens up again,
3883 // we will go through onCreate and once again open up
3884 // the Go page.
3885 finish();
3886 return;
3887 }
3888 closeCurrentWindow();
Leon Scroggins39362092009-08-18 19:47:09 -04003889*/
The Android Open Source Project0c908882009-03-03 19:32:16 -08003890 }
3891 break;
3892 default:
3893 break;
3894 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003895 mCancelGoPageMeansClose = false;
3896 if (getTopWindow() != null) {
3897 getTopWindow().requestFocus();
3898 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003899 }
3900
3901 /*
3902 * This method is called as a result of the user selecting the options
3903 * menu to see the download window, or when a download changes state. It
3904 * shows the download window ontop of the current window.
3905 */
3906 /* package */ void viewDownloads(Uri downloadRecord) {
3907 Intent intent = new Intent(this,
3908 BrowserDownloadPage.class);
3909 intent.setData(downloadRecord);
3910 startActivityForResult(intent, this.DOWNLOAD_PAGE);
3911
3912 }
3913
Leon Scroggins160a7e72009-08-14 18:28:01 -04003914 // True if canceling the "Go" screen should result in closing the current
3915 // window/browser.
3916 private boolean mCancelGoPageMeansClose;
3917
3918 /**
3919 * Open the Go page.
3920 * @param startWithHistory If true, open starting on the history tab.
3921 * Otherwise, start with the bookmarks tab.
3922 * @param cancelGoPageMeansClose Set to true if this came from a new tab, or
3923 * from the only tab, and canceling means to
3924 * close the tab (and possibly the browser)
3925 */
3926 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory,
3927 boolean cancelGoPageMeansClose) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003928 WebView current = mTabControl.getCurrentWebView();
3929 if (current == null) {
3930 return;
3931 }
3932 Intent intent = new Intent(this,
3933 CombinedBookmarkHistoryActivity.class);
3934 String title = current.getTitle();
3935 String url = current.getUrl();
3936 // Just in case the user opens bookmarks before a page finishes loading
3937 // so the current history item, and therefore the page, is null.
3938 if (null == url) {
3939 url = mLastEnteredUrl;
3940 // This can happen.
3941 if (null == url) {
3942 url = mSettings.getHomePage();
3943 }
3944 }
3945 // In case the web page has not yet received its associated title.
3946 if (title == null) {
3947 title = url;
3948 }
3949 intent.putExtra("title", title);
3950 intent.putExtra("url", url);
Leon Scroggins190095d2009-08-17 17:01:38 -04003951 // If this is opening in a new window, then disable opening in a
3952 // (different) new window. Also disable it if we have maxed out the
3953 // windows.
3954 intent.putExtra("disable_new_window", cancelGoPageMeansClose
3955 || mTabControl.getTabCount() >= TabControl.MAX_TABS);
Patrick Scott3918d442009-08-04 13:22:29 -04003956 intent.putExtra("touch_icon_url", current.getTouchIconUrl());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003957 if (startWithHistory) {
3958 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
3959 CombinedBookmarkHistoryActivity.HISTORY_TAB);
3960 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003961 mCancelGoPageMeansClose = cancelGoPageMeansClose;
The Android Open Source Project0c908882009-03-03 19:32:16 -08003962 startActivityForResult(intent, COMBO_PAGE);
3963 }
3964
3965 // Called when loading from context menu or LOAD_URL message
3966 private void loadURL(WebView view, String url) {
3967 // In case the user enters nothing.
3968 if (url != null && url.length() != 0 && view != null) {
3969 url = smartUrlFilter(url);
3970 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
3971 view.loadUrl(url);
3972 }
3973 }
3974 }
3975
The Android Open Source Project0c908882009-03-03 19:32:16 -08003976 private String smartUrlFilter(Uri inUri) {
3977 if (inUri != null) {
3978 return smartUrlFilter(inUri.toString());
3979 }
3980 return null;
3981 }
3982
3983
3984 // get window count
3985
3986 int getWindowCount(){
3987 if(mTabControl != null){
3988 return mTabControl.getTabCount();
3989 }
3990 return 0;
3991 }
3992
Feng Qianb34f87a2009-03-24 21:27:26 -07003993 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08003994 "(?i)" + // switch on case insensitive matching
3995 "(" + // begin group for schema
3996 "(?:http|https|file):\\/\\/" +
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07003997 "|(?:inline|data|about|content|javascript):" +
The Android Open Source Project0c908882009-03-03 19:32:16 -08003998 ")" +
3999 "(.*)" );
4000
4001 /**
4002 * Attempts to determine whether user input is a URL or search
4003 * terms. Anything with a space is passed to search.
4004 *
4005 * Converts to lowercase any mistakenly uppercased schema (i.e.,
4006 * "Http://" converts to "http://"
4007 *
4008 * @return Original or modified URL
4009 *
4010 */
4011 String smartUrlFilter(String url) {
4012
4013 String inUrl = url.trim();
4014 boolean hasSpace = inUrl.indexOf(' ') != -1;
4015
4016 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4017 if (matcher.matches()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004018 // force scheme to lowercase
4019 String scheme = matcher.group(1);
4020 String lcScheme = scheme.toLowerCase();
4021 if (!lcScheme.equals(scheme)) {
Mitsuru Oshima123ecfb2009-05-18 19:11:14 -07004022 inUrl = lcScheme + matcher.group(2);
4023 }
4024 if (hasSpace) {
4025 inUrl = inUrl.replace(" ", "%20");
The Android Open Source Project0c908882009-03-03 19:32:16 -08004026 }
4027 return inUrl;
4028 }
4029 if (hasSpace) {
Satish Sampath565505b2009-05-29 15:37:27 +01004030 // FIXME: Is this the correct place to add to searches?
4031 // what if someone else calls this function?
4032 int shortcut = parseUrlShortcut(inUrl);
4033 if (shortcut != SHORTCUT_INVALID) {
4034 Browser.addSearchUrl(mResolver, inUrl);
4035 String query = inUrl.substring(2);
4036 switch (shortcut) {
4037 case SHORTCUT_GOOGLE_SEARCH:
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004038 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
Satish Sampath565505b2009-05-29 15:37:27 +01004039 case SHORTCUT_WIKIPEDIA_SEARCH:
4040 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4041 case SHORTCUT_DICTIONARY_SEARCH:
4042 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4043 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
The Android Open Source Project0c908882009-03-03 19:32:16 -08004044 // FIXME: we need location in this case
Satish Sampath565505b2009-05-29 15:37:27 +01004045 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004046 }
4047 }
4048 } else {
4049 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4050 return URLUtil.guessUrl(inUrl);
4051 }
4052 }
4053
4054 Browser.addSearchUrl(mResolver, inUrl);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004055 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004056 }
4057
Ben Murdochbff2d602009-07-01 20:19:05 +01004058 /* package */ void setShouldShowErrorConsole(boolean flag) {
4059 if (flag == mShouldShowErrorConsole) {
4060 // Nothing to do.
4061 return;
4062 }
4063
4064 mShouldShowErrorConsole = flag;
4065
4066 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4067
4068 if (flag) {
4069 // Setting the show state of the console will cause it's the layout to be inflated.
4070 if (errorConsole.numberOfErrors() > 0) {
4071 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4072 } else {
4073 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4074 }
4075
4076 // Now we can add it to the main view.
4077 mErrorConsoleContainer.addView(errorConsole,
4078 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4079 ViewGroup.LayoutParams.WRAP_CONTENT));
4080 } else {
4081 mErrorConsoleContainer.removeView(errorConsole);
4082 }
4083
4084 }
4085
The Android Open Source Project0c908882009-03-03 19:32:16 -08004086 private final static int LOCK_ICON_UNSECURE = 0;
4087 private final static int LOCK_ICON_SECURE = 1;
4088 private final static int LOCK_ICON_MIXED = 2;
4089
4090 private int mLockIconType = LOCK_ICON_UNSECURE;
4091 private int mPrevLockType = LOCK_ICON_UNSECURE;
4092
4093 private BrowserSettings mSettings;
4094 private TabControl mTabControl;
4095 private ContentResolver mResolver;
4096 private FrameLayout mContentView;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004097 private View mCustomView;
4098 private FrameLayout mCustomViewContainer;
Andrei Popescuc9b55562009-07-07 10:51:15 +01004099 private WebChromeClient.CustomViewCallback mCustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004100
4101 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4102 // view, we should rewrite this.
4103 private int mCurrentMenuState = 0;
4104 private int mMenuState = R.id.MAIN_MENU;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004105 private int mOldMenuState = EMPTY_MENU;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004106 private static final int EMPTY_MENU = -1;
4107 private Menu mMenu;
4108
4109 private FindDialog mFindDialog;
4110 // Used to prevent chording to result in firing two shortcuts immediately
4111 // one after another. Fixes bug 1211714.
4112 boolean mCanChord;
4113
4114 private boolean mInLoad;
4115 private boolean mIsNetworkUp;
4116
4117 private boolean mPageStarted;
4118 private boolean mActivityInPause = true;
4119
4120 private boolean mMenuIsDown;
4121
4122 private final KeyTracker mKeyTracker = new KeyTracker(this);
4123
4124 // As trackball doesn't send repeat down, we have to track it ourselves
4125 private boolean mTrackTrackball;
4126
4127 private static boolean mInTrace;
4128
4129 // Performance probe
4130 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4131 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4132 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4133 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4134 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4135 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4136 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4137 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4138 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4139 };
4140
4141 private long mStart;
4142 private long mProcessStart;
4143 private long mUserStart;
4144 private long mSystemStart;
4145 private long mIdleStart;
4146 private long mIrqStart;
4147
4148 private long mUiStart;
4149
4150 private Drawable mMixLockIcon;
4151 private Drawable mSecLockIcon;
4152 private Drawable mGenericFavicon;
4153
4154 /* hold a ref so we can auto-cancel if necessary */
4155 private AlertDialog mAlertDialog;
4156
4157 // Wait for credentials before loading google.com
4158 private ProgressDialog mCredsDlg;
4159
4160 // The up-to-date URL and title (these can be different from those stored
4161 // in WebView, since it takes some time for the information in WebView to
4162 // get updated)
4163 private String mUrl;
4164 private String mTitle;
4165
4166 // As PageInfo has different style for landscape / portrait, we have
4167 // to re-open it when configuration changed
4168 private AlertDialog mPageInfoDialog;
4169 private TabControl.Tab mPageInfoView;
4170 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4171 // dialog, we should not just dismiss it, but should get back to the
4172 // SSL-certificate-on-error dialog. This flag is used to store this state
4173 private Boolean mPageInfoFromShowSSLCertificateOnError;
4174
4175 // as SSLCertificateOnError has different style for landscape / portrait,
4176 // we have to re-open it when configuration changed
4177 private AlertDialog mSSLCertificateOnErrorDialog;
4178 private WebView mSSLCertificateOnErrorView;
4179 private SslErrorHandler mSSLCertificateOnErrorHandler;
4180 private SslError mSSLCertificateOnErrorError;
4181
4182 // as SSLCertificate has different style for landscape / portrait, we
4183 // have to re-open it when configuration changed
4184 private AlertDialog mSSLCertificateDialog;
4185 private TabControl.Tab mSSLCertificateView;
4186
4187 // as HttpAuthentication has different style for landscape / portrait, we
4188 // have to re-open it when configuration changed
4189 private AlertDialog mHttpAuthenticationDialog;
4190 private HttpAuthHandler mHttpAuthHandler;
4191
4192 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4193 new FrameLayout.LayoutParams(
4194 ViewGroup.LayoutParams.FILL_PARENT,
4195 ViewGroup.LayoutParams.FILL_PARENT);
Andrei Popescuadc008d2009-06-26 14:11:30 +01004196 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4197 new FrameLayout.LayoutParams(
4198 ViewGroup.LayoutParams.FILL_PARENT,
4199 ViewGroup.LayoutParams.FILL_PARENT,
4200 Gravity.CENTER);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004201 // Google search
4202 final static String QuickSearch_G = "http://www.google.com/m?q=%s";
The Android Open Source Project0c908882009-03-03 19:32:16 -08004203 // Wikipedia search
4204 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4205 // Dictionary search
4206 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4207 // Google Mobile Local search
4208 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4209
4210 final static String QUERY_PLACE_HOLDER = "%s";
4211
4212 // "source" parameter for Google search through search key
4213 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4214 // "source" parameter for Google search through goto menu
4215 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4216 // "source" parameter for Google search through simplily type
4217 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4218 // "source" parameter for Google search suggested by the browser
4219 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4220 // "source" parameter for Google search from unknown source
4221 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4222
4223 private final static String LOGTAG = "browser";
4224
The Android Open Source Project0c908882009-03-03 19:32:16 -08004225 private String mLastEnteredUrl;
4226
4227 private PowerManager.WakeLock mWakeLock;
4228 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4229
4230 private Toast mStopToast;
4231
Leon Scroggins1f005d32009-08-10 17:36:42 -04004232 private TitleBarSet mTitleBar;
Leon Scroggins81db3662009-06-04 17:45:11 -04004233
Ben Murdochbff2d602009-07-01 20:19:05 +01004234 private LinearLayout mErrorConsoleContainer = null;
4235 private boolean mShouldShowErrorConsole = false;
4236
The Android Open Source Project0c908882009-03-03 19:32:16 -08004237 // As the ids are dynamically created, we can't guarantee that they will
4238 // be in sequence, so this static array maps ids to a window number.
4239 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4240 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4241 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4242 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4243
4244 // monitor platform changes
4245 private IntentFilter mNetworkStateChangedFilter;
4246 private BroadcastReceiver mNetworkStateIntentReceiver;
4247
Grace Klobab4da0ad2009-05-14 14:45:40 -07004248 private BroadcastReceiver mPackageInstallationReceiver;
4249
The Android Open Source Project0c908882009-03-03 19:32:16 -08004250 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004251 final static int COMBO_PAGE = 1;
4252 final static int DOWNLOAD_PAGE = 2;
4253 final static int PREFERENCES_PAGE = 3;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004254
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004255 /**
4256 * A UrlData class to abstract how the content will be set to WebView.
4257 * This base class uses loadUrl to show the content.
4258 */
4259 private static class UrlData {
4260 String mUrl;
Grace Kloba60e095c2009-06-16 11:50:55 -07004261 byte[] mPostData;
4262
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004263 UrlData(String url) {
4264 this.mUrl = url;
4265 }
Grace Kloba60e095c2009-06-16 11:50:55 -07004266
4267 void setPostData(byte[] postData) {
4268 mPostData = postData;
4269 }
4270
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004271 boolean isEmpty() {
4272 return mUrl == null || mUrl.length() == 0;
4273 }
4274
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004275 public void loadIn(WebView webView) {
Grace Kloba60e095c2009-06-16 11:50:55 -07004276 if (mPostData != null) {
4277 webView.postUrl(mUrl, mPostData);
4278 } else {
4279 webView.loadUrl(mUrl);
4280 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004281 }
4282 };
4283
4284 /**
4285 * A subclass of UrlData class that can display inlined content using
4286 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4287 */
4288 private static class InlinedUrlData extends UrlData {
4289 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4290 super(failUrl);
4291 mInlined = inlined;
4292 mMimeType = mimeType;
4293 mEncoding = encoding;
4294 }
4295 String mMimeType;
4296 String mInlined;
4297 String mEncoding;
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004298 @Override
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004299 boolean isEmpty() {
Ben Murdochbff2d602009-07-01 20:19:05 +01004300 return mInlined == null || mInlined.length() == 0 || super.isEmpty();
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004301 }
4302
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004303 @Override
4304 public void loadIn(WebView webView) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004305 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4306 }
4307 }
4308
Leon Scroggins1f005d32009-08-10 17:36:42 -04004309 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004310}