blob: c766c74c940b101da1ce5fbe4ccb18541e9ee571 [file] [log] [blame]
The Android Open Source Project0c908882009-03-03 19:32:16 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import com.google.android.googleapps.IGoogleLoginService;
20import com.google.android.googlelogin.GoogleLoginServiceConstants;
21
22import android.app.Activity;
The Android Open Source Project0c908882009-03-03 19:32:16 -080023import android.app.AlertDialog;
24import android.app.ProgressDialog;
25import android.app.SearchManager;
26import android.content.ActivityNotFoundException;
27import android.content.BroadcastReceiver;
28import android.content.ComponentName;
29import android.content.ContentResolver;
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -040030import android.content.ContentUris;
The Android Open Source Project0c908882009-03-03 19:32:16 -080031import android.content.ContentValues;
32import android.content.Context;
33import android.content.DialogInterface;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.ServiceConnection;
37import android.content.DialogInterface.OnCancelListener;
Grace Klobab4da0ad2009-05-14 14:45:40 -070038import android.content.pm.PackageInfo;
The Android Open Source Project0c908882009-03-03 19:32:16 -080039import android.content.pm.PackageManager;
40import android.content.pm.ResolveInfo;
41import android.content.res.AssetManager;
42import android.content.res.Configuration;
43import android.content.res.Resources;
44import android.database.Cursor;
45import android.database.sqlite.SQLiteDatabase;
46import android.database.sqlite.SQLiteException;
47import android.graphics.Bitmap;
48import android.graphics.Canvas;
49import android.graphics.Color;
50import android.graphics.DrawFilter;
51import android.graphics.Paint;
52import android.graphics.PaintFlagsDrawFilter;
53import android.graphics.Picture;
54import android.graphics.drawable.BitmapDrawable;
55import android.graphics.drawable.Drawable;
56import android.graphics.drawable.LayerDrawable;
57import android.graphics.drawable.PaintDrawable;
58import android.hardware.SensorListener;
59import android.hardware.SensorManager;
60import android.net.ConnectivityManager;
61import android.net.Uri;
62import android.net.WebAddress;
63import android.net.http.EventHandler;
64import android.net.http.SslCertificate;
65import android.net.http.SslError;
66import android.os.AsyncTask;
67import android.os.Bundle;
68import android.os.Debug;
69import android.os.Environment;
70import android.os.Handler;
71import android.os.IBinder;
72import android.os.Message;
73import android.os.PowerManager;
74import android.os.Process;
75import android.os.RemoteException;
76import android.os.ServiceManager;
77import android.os.SystemClock;
The Android Open Source Project0c908882009-03-03 19:32:16 -080078import android.provider.Browser;
79import android.provider.Contacts;
80import android.provider.Downloads;
81import android.provider.MediaStore;
82import android.provider.Contacts.Intents.Insert;
83import android.text.IClipboard;
84import android.text.TextUtils;
85import android.text.format.DateFormat;
86import android.text.util.Regex;
The Android Open Source Project0c908882009-03-03 19:32:16 -080087import android.util.Log;
88import android.view.ContextMenu;
89import android.view.Gravity;
90import android.view.KeyEvent;
91import android.view.LayoutInflater;
92import android.view.Menu;
93import android.view.MenuInflater;
94import android.view.MenuItem;
95import android.view.View;
96import android.view.ViewGroup;
97import android.view.Window;
98import android.view.WindowManager;
99import android.view.ContextMenu.ContextMenuInfo;
100import android.view.MenuItem.OnMenuItemClickListener;
101import android.view.animation.AlphaAnimation;
102import android.view.animation.Animation;
103import android.view.animation.AnimationSet;
104import android.view.animation.DecelerateInterpolator;
105import android.view.animation.ScaleAnimation;
106import android.view.animation.TranslateAnimation;
107import android.webkit.CookieManager;
108import android.webkit.CookieSyncManager;
109import android.webkit.DownloadListener;
Steve Block2bc69912009-07-30 14:45:13 +0100110import android.webkit.GeolocationPermissions;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800111import android.webkit.HttpAuthHandler;
Grace Klobab4da0ad2009-05-14 14:45:40 -0700112import android.webkit.PluginManager;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800113import android.webkit.SslErrorHandler;
114import android.webkit.URLUtil;
115import android.webkit.WebChromeClient;
Andrei Popescuc9b55562009-07-07 10:51:15 +0100116import android.webkit.WebChromeClient.CustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800117import android.webkit.WebHistoryItem;
118import android.webkit.WebIconDatabase;
Ben Murdoch092dd5d2009-04-22 12:34:12 +0100119import android.webkit.WebStorage;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800120import android.webkit.WebView;
121import android.webkit.WebViewClient;
122import android.widget.EditText;
123import android.widget.FrameLayout;
124import android.widget.LinearLayout;
125import android.widget.TextView;
126import android.widget.Toast;
127
128import java.io.BufferedOutputStream;
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -0400129import java.io.ByteArrayOutputStream;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800130import java.io.File;
131import java.io.FileInputStream;
132import java.io.FileOutputStream;
133import java.io.IOException;
134import java.io.InputStream;
135import java.net.MalformedURLException;
136import java.net.URI;
Dianne Hackborn99189432009-06-17 18:06:18 -0700137import java.net.URISyntaxException;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800138import java.net.URL;
139import java.net.URLEncoder;
140import java.text.ParseException;
141import java.util.Date;
142import java.util.Enumeration;
143import java.util.HashMap;
Patrick Scott37911c72009-03-24 18:02:58 -0700144import java.util.LinkedList;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800145import java.util.Vector;
146import java.util.regex.Matcher;
147import java.util.regex.Pattern;
148import java.util.zip.ZipEntry;
149import java.util.zip.ZipFile;
150
151public class BrowserActivity extends Activity
152 implements KeyTracker.OnKeyTracker,
153 View.OnCreateContextMenuListener,
154 DownloadListener {
155
Dave Bort31a6d1c2009-04-13 15:56:49 -0700156 /* Define some aliases to make these debugging flags easier to refer to.
157 * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
158 */
159 private final static boolean DEBUG = com.android.browser.Browser.DEBUG;
160 private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
161 private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
162
The Android Open Source Project0c908882009-03-03 19:32:16 -0800163 private IGoogleLoginService mGls = null;
164 private ServiceConnection mGlsConnection = null;
165
166 private SensorManager mSensorManager = null;
167
Satish Sampath565505b2009-05-29 15:37:27 +0100168 // These are single-character shortcuts for searching popular sources.
169 private static final int SHORTCUT_INVALID = 0;
170 private static final int SHORTCUT_GOOGLE_SEARCH = 1;
171 private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2;
172 private static final int SHORTCUT_DICTIONARY_SEARCH = 3;
173 private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4;
174
The Android Open Source Project0c908882009-03-03 19:32:16 -0800175 /* Whitelisted webpages
176 private static HashSet<String> sWhiteList;
177
178 static {
179 sWhiteList = new HashSet<String>();
180 sWhiteList.add("cnn.com/");
181 sWhiteList.add("espn.go.com/");
182 sWhiteList.add("nytimes.com/");
183 sWhiteList.add("engadget.com/");
184 sWhiteList.add("yahoo.com/");
185 sWhiteList.add("msn.com/");
186 sWhiteList.add("amazon.com/");
187 sWhiteList.add("consumerist.com/");
188 sWhiteList.add("google.com/m/news");
189 }
190 */
191
192 private void setupHomePage() {
193 final Runnable getAccount = new Runnable() {
194 public void run() {
195 // Lower priority
196 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
197 // get the default home page
198 String homepage = mSettings.getHomePage();
199
200 try {
201 if (mGls == null) return;
202
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700203 if (!homepage.startsWith("http://www.google.")) return;
204 if (homepage.indexOf('?') == -1) return;
205
The Android Open Source Project0c908882009-03-03 19:32:16 -0800206 String hostedUser = mGls.getAccount(GoogleLoginServiceConstants.PREFER_HOSTED);
207 String googleUser = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE);
208
209 // three cases:
210 //
211 // hostedUser == googleUser
212 // The device has only a google account
213 //
214 // hostedUser != googleUser
215 // The device has a hosted account and a google account
216 //
217 // hostedUser != null, googleUser == null
218 // The device has only a hosted account (so far)
219
220 // developers might have no accounts at all
221 if (hostedUser == null) return;
222
223 if (googleUser == null || !hostedUser.equals(googleUser)) {
224 String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1);
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700225 homepage = homepage.replace("?", "/a/" + domain + "?");
The Android Open Source Project0c908882009-03-03 19:32:16 -0800226 }
227 } catch (RemoteException ignore) {
228 // Login service died; carry on
229 } catch (RuntimeException ignore) {
230 // Login service died; carry on
231 } finally {
232 finish(homepage);
233 }
234 }
235
236 private void finish(final String homepage) {
237 mHandler.post(new Runnable() {
238 public void run() {
239 mSettings.setHomePage(BrowserActivity.this, homepage);
240 resumeAfterCredentials();
241
242 // as this is running in a separate thread,
243 // BrowserActivity's onDestroy() may have been called,
244 // which also calls unbindService().
245 if (mGlsConnection != null) {
246 // we no longer need to keep GLS open
247 unbindService(mGlsConnection);
248 mGlsConnection = null;
249 }
250 } });
251 } };
252
253 final boolean[] done = { false };
254
255 // Open a connection to the Google Login Service. The first
256 // time the connection is established, set up the homepage depending on
257 // the account in a background thread.
258 mGlsConnection = new ServiceConnection() {
259 public void onServiceConnected(ComponentName className, IBinder service) {
260 mGls = IGoogleLoginService.Stub.asInterface(service);
261 if (done[0] == false) {
262 done[0] = true;
263 Thread account = new Thread(getAccount);
264 account.setName("GLSAccount");
265 account.start();
266 }
267 }
268 public void onServiceDisconnected(ComponentName className) {
269 mGls = null;
270 }
271 };
272
273 bindService(GoogleLoginServiceConstants.SERVICE_INTENT,
274 mGlsConnection, Context.BIND_AUTO_CREATE);
275 }
276
Cary Clarka9771242009-08-11 16:42:26 -0400277 private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800278 @Override
279 public Void doInBackground(File... files) {
280 if (files != null) {
281 for (File f : files) {
Cary Clarkd6be1752009-08-12 12:56:42 -0400282 if (!f.delete()) {
283 Log.e(LOGTAG, f.getPath() + " was not deleted");
284 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800285 }
286 }
287 return null;
288 }
289 }
290
Leon Scroggins81db3662009-06-04 17:45:11 -0400291 // Flag to enable the touchable browser bar with buttons
292 private final boolean CUSTOM_BROWSER_BAR = true;
293
The Android Open Source Project0c908882009-03-03 19:32:16 -0800294 @Override public void onCreate(Bundle icicle) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700295 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800296 Log.v(LOGTAG, this + " onStart");
297 }
298 super.onCreate(icicle);
Leon Scroggins81db3662009-06-04 17:45:11 -0400299 if (CUSTOM_BROWSER_BAR) {
300 this.requestWindowFeature(Window.FEATURE_NO_TITLE);
Leon Scroggins81db3662009-06-04 17:45:11 -0400301 } else {
302 this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
303 this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
304 this.requestWindowFeature(Window.FEATURE_PROGRESS);
305 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
306 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800307 // test the browser in OpenGL
308 // requestWindowFeature(Window.FEATURE_OPENGL);
309
310 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
311
312 mResolver = getContentResolver();
313
The Android Open Source Project0c908882009-03-03 19:32:16 -0800314 //
315 // start MASF proxy service
316 //
317 //Intent proxyServiceIntent = new Intent();
318 //proxyServiceIntent.setComponent
319 // (new ComponentName(
320 // "com.android.masfproxyservice",
321 // "com.android.masfproxyservice.MasfProxyService"));
322 //startService(proxyServiceIntent, null);
323
324 mSecLockIcon = Resources.getSystem().getDrawable(
325 android.R.drawable.ic_secure);
326 mMixLockIcon = Resources.getSystem().getDrawable(
327 android.R.drawable.ic_partial_secure);
328 mGenericFavicon = getResources().getDrawable(
329 R.drawable.app_web_browser_sm);
330
Leon Scroggins81db3662009-06-04 17:45:11 -0400331 FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
332 .findViewById(com.android.internal.R.id.content);
333 if (CUSTOM_BROWSER_BAR) {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100334 // This FrameLayout will hold the custom FrameLayout and a LinearLayout
335 // that contains the title bar and a FrameLayout, which
Leon Scroggins81db3662009-06-04 17:45:11 -0400336 // holds everything else.
Andrei Popescuadc008d2009-06-26 14:11:30 +0100337 FrameLayout browserFrameLayout = (FrameLayout) LayoutInflater.from(this)
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400338 .inflate(R.layout.custom_screen, null);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400339 mTitleBar = (TitleBarSet) browserFrameLayout.findViewById(R.id.title_bar);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100340 mContentView = (FrameLayout) browserFrameLayout.findViewById(
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400341 R.id.main_content);
Ben Murdochbff2d602009-07-01 20:19:05 +0100342 mErrorConsoleContainer = (LinearLayout) browserFrameLayout.findViewById(
343 R.id.error_console);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100344 mCustomViewContainer = (FrameLayout) browserFrameLayout
345 .findViewById(R.id.fullscreen_custom_content);
346 frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400347 } else {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100348 mCustomViewContainer = new FrameLayout(this);
Andrei Popescu78f75702009-06-26 16:50:04 +0100349 mCustomViewContainer.setBackgroundColor(Color.BLACK);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100350 mContentView = new FrameLayout(this);
Ben Murdochbff2d602009-07-01 20:19:05 +0100351
352 LinearLayout linearLayout = new LinearLayout(this);
353 linearLayout.setOrientation(LinearLayout.VERTICAL);
354 mErrorConsoleContainer = new LinearLayout(this);
355 linearLayout.addView(mErrorConsoleContainer, new LinearLayout.LayoutParams(
356 ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
357 linearLayout.addView(mContentView, COVER_SCREEN_PARAMS);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100358 frameLayout.addView(mCustomViewContainer, COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +0100359 frameLayout.addView(linearLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400360 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800361
362 // Create the tab control and our initial tab
363 mTabControl = new TabControl(this);
364
365 // Open the icon database and retain all the bookmark urls for favicons
366 retainIconsOnStartup();
367
368 // Keep a settings instance handy.
369 mSettings = BrowserSettings.getInstance();
370 mSettings.setTabControl(mTabControl);
371 mSettings.loadFromDb(this);
372
373 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
374 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
375
Grace Klobaa34f6862009-07-31 16:28:17 -0700376 /* enables registration for changes in network status from
377 http stack */
378 mNetworkStateChangedFilter = new IntentFilter();
379 mNetworkStateChangedFilter.addAction(
380 ConnectivityManager.CONNECTIVITY_ACTION);
381 mNetworkStateIntentReceiver = new BroadcastReceiver() {
382 @Override
383 public void onReceive(Context context, Intent intent) {
384 if (intent.getAction().equals(
385 ConnectivityManager.CONNECTIVITY_ACTION)) {
386 boolean down = intent.getBooleanExtra(
387 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
388 onNetworkToggle(!down);
389 }
390 }
391 };
392
Grace Kloba615c6c92009-08-03 10:22:44 -0700393 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
394 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
395 filter.addDataScheme("package");
396 mPackageInstallationReceiver = new BroadcastReceiver() {
397 @Override
398 public void onReceive(Context context, Intent intent) {
399 final String action = intent.getAction();
400 final String packageName = intent.getData()
401 .getSchemeSpecificPart();
402 final boolean replacing = intent.getBooleanExtra(
403 Intent.EXTRA_REPLACING, false);
404 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
405 // if it is replacing, refreshPlugins() when adding
406 return;
407 }
408 PackageManager pm = BrowserActivity.this.getPackageManager();
409 PackageInfo pkgInfo = null;
410 try {
411 pkgInfo = pm.getPackageInfo(packageName,
412 PackageManager.GET_PERMISSIONS);
413 } catch (PackageManager.NameNotFoundException e) {
414 return;
415 }
416 if (pkgInfo != null) {
417 String permissions[] = pkgInfo.requestedPermissions;
418 if (permissions == null) {
419 return;
420 }
421 boolean permissionOk = false;
422 for (String permit : permissions) {
423 if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
424 permissionOk = true;
425 break;
426 }
427 }
428 if (permissionOk) {
429 PluginManager.getInstance(BrowserActivity.this)
430 .refreshPlugins(
431 Intent.ACTION_PACKAGE_ADDED
432 .equals(action));
433 }
434 }
435 }
436 };
437 registerReceiver(mPackageInstallationReceiver, filter);
438
Satish Sampath565505b2009-05-29 15:37:27 +0100439 // If this was a web search request, pass it on to the default web search provider.
440 if (handleWebSearchIntent(getIntent())) {
441 moveTaskToBack(true);
442 return;
443 }
444
The Android Open Source Project0c908882009-03-03 19:32:16 -0800445 if (!mTabControl.restoreState(icicle)) {
446 // clear up the thumbnail directory if we can't restore the state as
447 // none of the files in the directory are referenced any more.
448 new ClearThumbnails().execute(
449 mTabControl.getThumbnailDir().listFiles());
Grace Klobaaab3f092009-07-30 12:29:51 -0700450 // there is no quit on Android. But if we can't restore the state,
451 // we can treat it as a new Browser, remove the old session cookies.
452 CookieManager.getInstance().removeSessionCookie();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800453 final Intent intent = getIntent();
454 final Bundle extra = intent.getExtras();
455 // Create an initial tab.
456 // If the intent is ACTION_VIEW and data is not null, the Browser is
457 // invoked to view the content by another application. In this case,
458 // the tab will be close when exit.
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700459 UrlData urlData = getUrlDataFromIntent(intent);
460
The Android Open Source Project0c908882009-03-03 19:32:16 -0800461 final TabControl.Tab t = mTabControl.createNewTab(
462 Intent.ACTION_VIEW.equals(intent.getAction()) &&
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700463 intent.getData() != null,
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700464 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800465 mTabControl.setCurrentTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800466 attachTabToContentView(t);
467 WebView webView = t.getWebView();
468 if (extra != null) {
469 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
470 if (scale > 0 && scale <= 1000) {
471 webView.setInitialScale(scale);
472 }
473 }
474 // If we are not restoring from an icicle, then there is a high
475 // likely hood this is the first run. So, check to see if the
476 // homepage needs to be configured and copy any plugins from our
477 // asset directory to the data partition.
478 if ((extra == null || !extra.getBoolean("testing"))
479 && !mSettings.isLoginInitialized()) {
480 setupHomePage();
481 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800482
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700483 if (urlData.isEmpty()) {
Leon Scroggins160a7e72009-08-14 18:28:01 -0400484 bookmarksOrHistoryPicker(false, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800485 } else {
Grace Kloba81678d92009-06-30 07:09:56 -0700486 if (extra != null) {
487 urlData.setPostData(extra
488 .getByteArray(Browser.EXTRA_POST_DATA));
489 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700490 urlData.loadIn(webView);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800491 }
492 } else {
493 // TabControl.restoreState() will create a new tab even if
Leon Scroggins1f005d32009-08-10 17:36:42 -0400494 // restoring the state fails.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800495 attachTabToContentView(mTabControl.getCurrentTab());
496 }
Grace Kloba615c6c92009-08-03 10:22:44 -0700497
Leon Scroggins1f005d32009-08-10 17:36:42 -0400498 if (CUSTOM_BROWSER_BAR) {
499 // Create title bars for all of the tabs that have been created
500 for (int i = 0; i < mTabControl.getTabCount(); i ++) {
501 WebView view = mTabControl.getTab(i).getWebView();
502 mTitleBar.addTab(view, false);
503 }
504
505 mTitleBar.setBrowserActivity(this);
506 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
894 /**
895 * onSaveInstanceState(Bundle map)
896 * onSaveInstanceState is called right before onStop(). The map contains
897 * the saved state.
898 */
899 @Override protected void onSaveInstanceState(Bundle outState) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700900 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800901 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
902 }
903 // the default implementation requires each view to have an id. As the
904 // browser handles the state itself and it doesn't use id for the views,
905 // don't call the default implementation. Otherwise it will trigger the
906 // warning like this, "couldn't save which view has focus because the
907 // focused view XXX has no id".
908
909 // Save all the tabs
910 mTabControl.saveState(outState);
911 }
912
913 @Override protected void onPause() {
914 super.onPause();
915
916 if (mActivityInPause) {
917 Log.e(LOGTAG, "BrowserActivity is already paused.");
918 return;
919 }
920
Mike Reed7bfa63b2009-05-28 11:08:32 -0400921 mTabControl.pauseCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800922 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400923 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800924 mWakeLock.acquire();
925 mHandler.sendMessageDelayed(mHandler
926 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
927 }
928
929 // Clear the credentials toast if it is up
930 if (mCredsDlg != null && mCredsDlg.isShowing()) {
931 mCredsDlg.dismiss();
932 }
933 mCredsDlg = null;
934
935 cancelStopToast();
936
937 // unregister network state listener
938 unregisterReceiver(mNetworkStateIntentReceiver);
939 WebView.disablePlatformNotifications();
940
941 if (mSensorManager != null) {
942 mSensorManager.unregisterListener(mSensorListener);
943 }
944 }
945
946 @Override protected void onDestroy() {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700947 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800948 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
949 }
950 super.onDestroy();
951 // Remove the current tab and sub window
952 TabControl.Tab t = mTabControl.getCurrentTab();
Patrick Scottfb5e77f2009-04-08 19:17:37 -0700953 if (t != null) {
954 dismissSubWindow(t);
955 removeTabFromContentView(t);
956 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800957 // Destroy all the tabs
958 mTabControl.destroy();
959 WebIconDatabase.getInstance().close();
960 if (mGlsConnection != null) {
961 unbindService(mGlsConnection);
962 mGlsConnection = null;
963 }
964
965 //
966 // stop MASF proxy service
967 //
968 //Intent proxyServiceIntent = new Intent();
969 //proxyServiceIntent.setComponent
970 // (new ComponentName(
971 // "com.android.masfproxyservice",
972 // "com.android.masfproxyservice.MasfProxyService"));
973 //stopService(proxyServiceIntent);
Grace Klobab4da0ad2009-05-14 14:45:40 -0700974
975 unregisterReceiver(mPackageInstallationReceiver);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800976 }
977
978 @Override
979 public void onConfigurationChanged(Configuration newConfig) {
980 super.onConfigurationChanged(newConfig);
981
982 if (mPageInfoDialog != null) {
983 mPageInfoDialog.dismiss();
984 showPageInfo(
985 mPageInfoView,
986 mPageInfoFromShowSSLCertificateOnError.booleanValue());
987 }
988 if (mSSLCertificateDialog != null) {
989 mSSLCertificateDialog.dismiss();
990 showSSLCertificate(
991 mSSLCertificateView);
992 }
993 if (mSSLCertificateOnErrorDialog != null) {
994 mSSLCertificateOnErrorDialog.dismiss();
995 showSSLCertificateOnError(
996 mSSLCertificateOnErrorView,
997 mSSLCertificateOnErrorHandler,
998 mSSLCertificateOnErrorError);
999 }
1000 if (mHttpAuthenticationDialog != null) {
1001 String title = ((TextView) mHttpAuthenticationDialog
1002 .findViewById(com.android.internal.R.id.alertTitle)).getText()
1003 .toString();
1004 String name = ((TextView) mHttpAuthenticationDialog
1005 .findViewById(R.id.username_edit)).getText().toString();
1006 String password = ((TextView) mHttpAuthenticationDialog
1007 .findViewById(R.id.password_edit)).getText().toString();
1008 int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1009 .getId();
1010 mHttpAuthenticationDialog.dismiss();
1011 showHttpAuthentication(mHttpAuthHandler, null, null, title,
1012 name, password, focusId);
1013 }
1014 if (mFindDialog != null && mFindDialog.isShowing()) {
1015 mFindDialog.onConfigurationChanged(newConfig);
1016 }
1017 }
1018
1019 @Override public void onLowMemory() {
1020 super.onLowMemory();
1021 mTabControl.freeMemory();
1022 }
1023
Mike Reed7bfa63b2009-05-28 11:08:32 -04001024 private boolean resumeWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001025 if ((!mActivityInPause && !mPageStarted) ||
1026 (mActivityInPause && mPageStarted)) {
1027 CookieSyncManager.getInstance().startSync();
1028 WebView w = mTabControl.getCurrentWebView();
1029 if (w != null) {
1030 w.resumeTimers();
1031 }
1032 return true;
1033 } else {
1034 return false;
1035 }
1036 }
1037
Mike Reed7bfa63b2009-05-28 11:08:32 -04001038 private boolean pauseWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001039 if (mActivityInPause && !mPageStarted) {
1040 CookieSyncManager.getInstance().stopSync();
1041 WebView w = mTabControl.getCurrentWebView();
1042 if (w != null) {
1043 w.pauseTimers();
1044 }
1045 return true;
1046 } else {
1047 return false;
1048 }
1049 }
1050
Leon Scroggins1f005d32009-08-10 17:36:42 -04001051 // FIXME: Do we want to call this when loading google for the first time?
The Android Open Source Project0c908882009-03-03 19:32:16 -08001052 /*
1053 * This function is called when we are launching for the first time. We
1054 * are waiting for the login credentials before loading Google home
1055 * pages. This way the user will be logged in straight away.
1056 */
1057 private void waitForCredentials() {
1058 // Show a toast
1059 mCredsDlg = new ProgressDialog(this);
1060 mCredsDlg.setIndeterminate(true);
1061 mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1062 // If the user cancels the operation, then cancel the Google
1063 // Credentials request.
1064 mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1065 mCredsDlg.show();
1066
1067 // We set a timeout for the retrieval of credentials in onResume()
1068 // as that is when we have freed up some CPU time to get
1069 // the login credentials.
1070 }
1071
1072 /*
1073 * If we have received the credentials or we have timed out and we are
1074 * showing the credentials dialog, then it is time to move on.
1075 */
1076 private void resumeAfterCredentials() {
1077 if (mCredsDlg == null) {
1078 return;
1079 }
1080
1081 // Clear the toast
1082 if (mCredsDlg.isShowing()) {
1083 mCredsDlg.dismiss();
1084 }
1085 mCredsDlg = null;
1086
1087 // Clear any pending timeout
1088 mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1089
1090 // Load the page
1091 WebView w = mTabControl.getCurrentWebView();
1092 if (w != null) {
1093 w.loadUrl(mSettings.getHomePage());
1094 }
1095
1096 // Update the settings, need to do this last as it can take a moment
1097 // to persist the settings. In the mean time we could be loading
1098 // content.
1099 mSettings.setLoginInitialized(this);
1100 }
1101
1102 // Open the icon database and retain all the icons for visited sites.
1103 private void retainIconsOnStartup() {
1104 final WebIconDatabase db = WebIconDatabase.getInstance();
1105 db.open(getDir("icons", 0).getPath());
1106 try {
1107 Cursor c = Browser.getAllBookmarks(mResolver);
1108 if (!c.moveToFirst()) {
1109 c.deactivate();
1110 return;
1111 }
1112 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1113 do {
1114 String url = c.getString(urlIndex);
1115 db.retainIconForPageUrl(url);
1116 } while (c.moveToNext());
1117 c.deactivate();
1118 } catch (IllegalStateException e) {
1119 Log.e(LOGTAG, "retainIconsOnStartup", e);
1120 }
1121 }
1122
1123 // Helper method for getting the top window.
1124 WebView getTopWindow() {
1125 return mTabControl.getCurrentTopWebView();
1126 }
1127
1128 @Override
1129 public boolean onCreateOptionsMenu(Menu menu) {
1130 super.onCreateOptionsMenu(menu);
1131
1132 MenuInflater inflater = getMenuInflater();
1133 inflater.inflate(R.menu.browser, menu);
1134 mMenu = menu;
1135 updateInLoadMenuItems();
1136 return true;
1137 }
1138
1139 /**
1140 * As the menu can be open when loading state changes
1141 * we must manually update the state of the stop/reload menu
1142 * item
1143 */
1144 private void updateInLoadMenuItems() {
1145 if (mMenu == null) {
1146 return;
1147 }
1148 MenuItem src = mInLoad ?
1149 mMenu.findItem(R.id.stop_menu_id):
1150 mMenu.findItem(R.id.reload_menu_id);
1151 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1152 dest.setIcon(src.getIcon());
1153 dest.setTitle(src.getTitle());
1154 }
1155
1156 @Override
1157 public boolean onContextItemSelected(MenuItem item) {
1158 // chording is not an issue with context menus, but we use the same
1159 // options selector, so set mCanChord to true so we can access them.
1160 mCanChord = true;
1161 int id = item.getItemId();
1162 final WebView webView = getTopWindow();
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001163 if (null == webView) {
1164 return false;
1165 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001166 final HashMap hrefMap = new HashMap();
1167 hrefMap.put("webview", webView);
1168 final Message msg = mHandler.obtainMessage(
1169 FOCUS_NODE_HREF, id, 0, hrefMap);
1170 switch (id) {
1171 // -- Browser context menu
1172 case R.id.open_context_menu_id:
1173 case R.id.open_newtab_context_menu_id:
1174 case R.id.bookmark_context_menu_id:
1175 case R.id.save_link_context_menu_id:
1176 case R.id.share_link_context_menu_id:
1177 case R.id.copy_link_context_menu_id:
1178 webView.requestFocusNodeHref(msg);
1179 break;
1180
1181 default:
1182 // For other context menus
1183 return onOptionsItemSelected(item);
1184 }
1185 mCanChord = false;
1186 return true;
1187 }
1188
1189 private Bundle createGoogleSearchSourceBundle(String source) {
1190 Bundle bundle = new Bundle();
1191 bundle.putString(SearchManager.SOURCE, source);
1192 return bundle;
1193 }
1194
1195 /**
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001196 * Overriding this to insert a local information bundle
The Android Open Source Project0c908882009-03-03 19:32:16 -08001197 */
1198 @Override
1199 public boolean onSearchRequested() {
Leon Scroggins5bbe9802009-07-31 13:10:55 -04001200 String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
Grace Kloba83f47342009-07-20 10:44:31 -07001201 startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001202 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001203 return true;
1204 }
1205
1206 @Override
1207 public void startSearch(String initialQuery, boolean selectInitialQuery,
1208 Bundle appSearchData, boolean globalSearch) {
1209 if (appSearchData == null) {
1210 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1211 }
1212 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1213 }
1214
Leon Scroggins1f005d32009-08-10 17:36:42 -04001215 /**
1216 * Switch tabs. Called by the TitleBarSet when sliding the title bar
1217 * results in changing tabs.
Leon Scroggins160a7e72009-08-14 18:28:01 -04001218 * @param index Index of the tab to change to, as defined by
1219 * mTabControl.getTabIndex(Tab t).
1220 * @return boolean True if we successfully switched to a different tab. If
1221 * the indexth tab is null, or if that tab is the same as
1222 * the current one, return false.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001223 */
Leon Scroggins160a7e72009-08-14 18:28:01 -04001224 /* package */ boolean switchToTab(int index) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001225 TabControl.Tab tab = mTabControl.getTab(index);
1226 TabControl.Tab currentTab = mTabControl.getCurrentTab();
1227 if (tab == null || tab == currentTab) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001228 return false;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001229 }
1230 if (currentTab != null) {
1231 // currentTab may be null if it was just removed. In that case,
1232 // we do not need to remove it
1233 removeTabFromContentView(currentTab);
1234 }
1235 removeTabFromContentView(tab);
1236 mTabControl.setCurrentTab(tab);
1237 attachTabToContentView(tab);
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001238 if (CUSTOM_BROWSER_BAR) {
1239 mTitleBar.setCurrentTab(index);
Leon Scroggins416b7cb2009-08-19 16:26:12 -04001240 WebView view = tab.getWebView();
1241 view.slideIntoFocus();
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001242 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001243 return true;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001244 }
1245
1246 /* package */ void closeCurrentWindow() {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001247 final TabControl.Tab current = mTabControl.getCurrentTab();
Leon Scroggins160a7e72009-08-14 18:28:01 -04001248 if (mTabControl.getTabCount() == 1) {
1249 // This is the last tab. Open a new one, as well as the history
1250 // picker, and close the current one.
1251 TabControl.Tab newTab = openTabAndShow(
1252 BrowserActivity.EMPTY_URL_DATA, false, null);
1253 bookmarksOrHistoryPicker(false, true);
1254 closeTab(current);
1255 mTabControl.setCurrentTab(newTab);
1256 return;
1257 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001258 final TabControl.Tab parent = current.getParentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04001259 int indexToShow = -1;
1260 if (parent != null) {
1261 indexToShow = mTabControl.getTabIndex(parent);
1262 } else {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001263 final int currentIndex = mTabControl.getCurrentIndex();
1264 // Try to move to the tab to the right
1265 indexToShow = currentIndex + 1;
1266 if (indexToShow > mTabControl.getTabCount() - 1) {
1267 // Try to move to the tab to the left
1268 indexToShow = currentIndex - 1;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001269 }
1270 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001271 if (switchToTab(indexToShow)) {
1272 // Close window
1273 closeTab(current);
1274 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001275 }
1276
The Android Open Source Project0c908882009-03-03 19:32:16 -08001277 @Override
1278 public boolean onOptionsItemSelected(MenuItem item) {
1279 if (!mCanChord) {
1280 // The user has already fired a shortcut with this hold down of the
1281 // menu key.
1282 return false;
1283 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001284 if (null == getTopWindow()) {
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001285 return false;
1286 }
Grace Kloba6ee9c492009-07-13 10:04:34 -07001287 if (mMenuIsDown) {
1288 // The shortcut action consumes the MENU. Even if it is still down,
1289 // it won't trigger the next shortcut action. In the case of the
1290 // shortcut action triggering a new activity, like Bookmarks, we
1291 // won't get onKeyUp for MENU. So it is important to reset it here.
1292 mMenuIsDown = false;
1293 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001294 switch (item.getItemId()) {
1295 // -- Main menu
Leon Scroggins64b80f32009-08-07 12:03:34 -04001296 case R.id.goto_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001297 bookmarksOrHistoryPicker(false, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001298 break;
1299
Leon Scroggins1f005d32009-08-10 17:36:42 -04001300 case R.id.add_bookmark_menu_id:
1301 Intent i = new Intent(BrowserActivity.this,
1302 AddBookmarkPage.class);
1303 WebView w = getTopWindow();
1304 i.putExtra("url", w.getUrl());
1305 i.putExtra("title", w.getTitle());
1306 startActivity(i);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001307 break;
1308
1309 case R.id.stop_reload_menu_id:
1310 if (mInLoad) {
1311 stopLoading();
1312 } else {
1313 getTopWindow().reload();
1314 }
1315 break;
1316
1317 case R.id.back_menu_id:
1318 getTopWindow().goBack();
1319 break;
1320
1321 case R.id.forward_menu_id:
1322 getTopWindow().goForward();
1323 break;
1324
1325 case R.id.close_menu_id:
1326 // Close the subwindow if it exists.
1327 if (mTabControl.getCurrentSubWindow() != null) {
1328 dismissSubWindow(mTabControl.getCurrentTab());
1329 break;
1330 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001331 closeCurrentWindow();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001332 break;
1333
1334 case R.id.homepage_menu_id:
1335 TabControl.Tab current = mTabControl.getCurrentTab();
1336 if (current != null) {
1337 dismissSubWindow(current);
1338 current.getWebView().loadUrl(mSettings.getHomePage());
1339 }
1340 break;
1341
1342 case R.id.preferences_menu_id:
1343 Intent intent = new Intent(this,
1344 BrowserPreferencesPage.class);
1345 startActivityForResult(intent, PREFERENCES_PAGE);
1346 break;
1347
1348 case R.id.find_menu_id:
1349 if (null == mFindDialog) {
1350 mFindDialog = new FindDialog(this);
1351 }
1352 mFindDialog.setWebView(getTopWindow());
1353 mFindDialog.show();
1354 mMenuState = EMPTY_MENU;
1355 break;
1356
1357 case R.id.select_text_id:
1358 getTopWindow().emulateShiftHeld();
1359 break;
1360 case R.id.page_info_menu_id:
1361 showPageInfo(mTabControl.getCurrentTab(), false);
1362 break;
1363
1364 case R.id.classic_history_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001365 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001366 break;
1367
1368 case R.id.share_page_menu_id:
1369 Browser.sendString(this, getTopWindow().getUrl());
1370 break;
1371
1372 case R.id.dump_nav_menu_id:
1373 getTopWindow().debugDump();
1374 break;
1375
1376 case R.id.zoom_in_menu_id:
1377 getTopWindow().zoomIn();
1378 break;
1379
1380 case R.id.zoom_out_menu_id:
1381 getTopWindow().zoomOut();
1382 break;
1383
1384 case R.id.view_downloads_menu_id:
1385 viewDownloads(null);
1386 break;
1387
The Android Open Source Project0c908882009-03-03 19:32:16 -08001388 case R.id.window_one_menu_id:
1389 case R.id.window_two_menu_id:
1390 case R.id.window_three_menu_id:
1391 case R.id.window_four_menu_id:
1392 case R.id.window_five_menu_id:
1393 case R.id.window_six_menu_id:
1394 case R.id.window_seven_menu_id:
1395 case R.id.window_eight_menu_id:
1396 {
1397 int menuid = item.getItemId();
1398 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1399 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1400 TabControl.Tab desiredTab = mTabControl.getTab(id);
1401 if (desiredTab != null &&
1402 desiredTab != mTabControl.getCurrentTab()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001403 switchToTab(id);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001404 }
1405 break;
1406 }
1407 }
1408 }
1409 break;
1410
1411 default:
1412 if (!super.onOptionsItemSelected(item)) {
1413 return false;
1414 }
1415 // Otherwise fall through.
1416 }
1417 mCanChord = false;
1418 return true;
1419 }
1420
1421 public void closeFind() {
1422 mMenuState = R.id.MAIN_MENU;
1423 }
1424
1425 @Override public boolean onPrepareOptionsMenu(Menu menu)
1426 {
1427 // This happens when the user begins to hold down the menu key, so
1428 // allow them to chord to get a shortcut.
1429 mCanChord = true;
1430 // Note: setVisible will decide whether an item is visible; while
1431 // setEnabled() will decide whether an item is enabled, which also means
1432 // whether the matching shortcut key will function.
1433 super.onPrepareOptionsMenu(menu);
1434 switch (mMenuState) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001435 case EMPTY_MENU:
1436 if (mCurrentMenuState != mMenuState) {
1437 menu.setGroupVisible(R.id.MAIN_MENU, false);
1438 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1439 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001440 }
1441 break;
1442 default:
1443 if (mCurrentMenuState != mMenuState) {
1444 menu.setGroupVisible(R.id.MAIN_MENU, true);
1445 menu.setGroupEnabled(R.id.MAIN_MENU, true);
1446 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001447 }
1448 final WebView w = getTopWindow();
1449 boolean canGoBack = false;
1450 boolean canGoForward = false;
1451 boolean isHome = false;
1452 if (w != null) {
1453 canGoBack = w.canGoBack();
1454 canGoForward = w.canGoForward();
1455 isHome = mSettings.getHomePage().equals(w.getUrl());
1456 }
1457 final MenuItem back = menu.findItem(R.id.back_menu_id);
1458 back.setEnabled(canGoBack);
1459
1460 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1461 home.setEnabled(!isHome);
1462
1463 menu.findItem(R.id.forward_menu_id)
1464 .setEnabled(canGoForward);
1465
1466 // decide whether to show the share link option
1467 PackageManager pm = getPackageManager();
1468 Intent send = new Intent(Intent.ACTION_SEND);
1469 send.setType("text/plain");
1470 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1471 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1472
The Android Open Source Project0c908882009-03-03 19:32:16 -08001473 boolean isNavDump = mSettings.isNavDump();
1474 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1475 nav.setVisible(isNavDump);
1476 nav.setEnabled(isNavDump);
1477 break;
1478 }
1479 mCurrentMenuState = mMenuState;
1480 return true;
1481 }
1482
1483 @Override
1484 public void onCreateContextMenu(ContextMenu menu, View v,
1485 ContextMenuInfo menuInfo) {
1486 WebView webview = (WebView) v;
1487 WebView.HitTestResult result = webview.getHitTestResult();
1488 if (result == null) {
1489 return;
1490 }
1491
1492 int type = result.getType();
1493 if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1494 Log.w(LOGTAG,
1495 "We should not show context menu when nothing is touched");
1496 return;
1497 }
1498 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1499 // let TextView handles context menu
1500 return;
1501 }
1502
1503 // Note, http://b/issue?id=1106666 is requesting that
1504 // an inflated menu can be used again. This is not available
1505 // yet, so inflate each time (yuk!)
1506 MenuInflater inflater = getMenuInflater();
1507 inflater.inflate(R.menu.browsercontext, menu);
1508
1509 // Show the correct menu group
1510 String extra = result.getExtra();
1511 menu.setGroupVisible(R.id.PHONE_MENU,
1512 type == WebView.HitTestResult.PHONE_TYPE);
1513 menu.setGroupVisible(R.id.EMAIL_MENU,
1514 type == WebView.HitTestResult.EMAIL_TYPE);
1515 menu.setGroupVisible(R.id.GEO_MENU,
1516 type == WebView.HitTestResult.GEO_TYPE);
1517 menu.setGroupVisible(R.id.IMAGE_MENU,
1518 type == WebView.HitTestResult.IMAGE_TYPE
1519 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1520 menu.setGroupVisible(R.id.ANCHOR_MENU,
1521 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1522 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1523
1524 // Setup custom handling depending on the type
1525 switch (type) {
1526 case WebView.HitTestResult.PHONE_TYPE:
1527 menu.setHeaderTitle(Uri.decode(extra));
1528 menu.findItem(R.id.dial_context_menu_id).setIntent(
1529 new Intent(Intent.ACTION_VIEW, Uri
1530 .parse(WebView.SCHEME_TEL + extra)));
1531 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1532 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1533 addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1534 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1535 addIntent);
1536 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1537 new Copy(extra));
1538 break;
1539
1540 case WebView.HitTestResult.EMAIL_TYPE:
1541 menu.setHeaderTitle(extra);
1542 menu.findItem(R.id.email_context_menu_id).setIntent(
1543 new Intent(Intent.ACTION_VIEW, Uri
1544 .parse(WebView.SCHEME_MAILTO + extra)));
1545 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1546 new Copy(extra));
1547 break;
1548
1549 case WebView.HitTestResult.GEO_TYPE:
1550 menu.setHeaderTitle(extra);
1551 menu.findItem(R.id.map_context_menu_id).setIntent(
1552 new Intent(Intent.ACTION_VIEW, Uri
1553 .parse(WebView.SCHEME_GEO
1554 + URLEncoder.encode(extra))));
1555 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1556 new Copy(extra));
1557 break;
1558
1559 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1560 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1561 TextView titleView = (TextView) LayoutInflater.from(this)
1562 .inflate(android.R.layout.browser_link_context_header,
1563 null);
1564 titleView.setText(extra);
1565 menu.setHeaderView(titleView);
1566 // decide whether to show the open link in new tab option
1567 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1568 mTabControl.getTabCount() < TabControl.MAX_TABS);
1569 PackageManager pm = getPackageManager();
1570 Intent send = new Intent(Intent.ACTION_SEND);
1571 send.setType("text/plain");
1572 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1573 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1574 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1575 break;
1576 }
1577 // otherwise fall through to handle image part
1578 case WebView.HitTestResult.IMAGE_TYPE:
1579 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1580 menu.setHeaderTitle(extra);
1581 }
1582 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1583 new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1584 menu.findItem(R.id.download_context_menu_id).
1585 setOnMenuItemClickListener(new Download(extra));
1586 break;
1587
1588 default:
1589 Log.w(LOGTAG, "We should not get here.");
1590 break;
1591 }
1592 }
1593
The Android Open Source Project0c908882009-03-03 19:32:16 -08001594 // Attach the given tab to the content view.
1595 private void attachTabToContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001596 // Attach the container that contains the main WebView and any other UI
1597 // associated with the tab.
1598 mContentView.addView(t.getContainer(), COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +01001599
1600 if (mShouldShowErrorConsole) {
1601 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
1602 if (errorConsole.numberOfErrors() == 0) {
1603 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1604 } else {
1605 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1606 }
1607
1608 mErrorConsoleContainer.addView(errorConsole,
1609 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
1610 ViewGroup.LayoutParams.WRAP_CONTENT));
1611 }
1612
The Android Open Source Project0c908882009-03-03 19:32:16 -08001613 // Attach the sub window if necessary
1614 attachSubWindow(t);
1615 // Request focus on the top window.
1616 t.getTopWindow().requestFocus();
1617 }
1618
1619 // Attach a sub window to the main WebView of the given tab.
1620 private void attachSubWindow(TabControl.Tab t) {
1621 // If a sub window exists, attach it to the content view.
1622 final WebView subView = t.getSubWebView();
1623 if (subView != null) {
1624 final View container = t.getSubWebViewContainer();
1625 mContentView.addView(container, COVER_SCREEN_PARAMS);
1626 subView.requestFocus();
1627 }
1628 }
1629
1630 // Remove the given tab from the content view.
1631 private void removeTabFromContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001632 // Remove the container that contains the main WebView.
1633 mContentView.removeView(t.getContainer());
Ben Murdochbff2d602009-07-01 20:19:05 +01001634
1635 if (mTabControl.getCurrentErrorConsole(false) != null) {
1636 mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
1637 }
1638
The Android Open Source Project0c908882009-03-03 19:32:16 -08001639 // Remove the sub window if it exists.
1640 if (t.getSubWebView() != null) {
1641 mContentView.removeView(t.getSubWebViewContainer());
1642 }
1643 }
1644
1645 // Remove the sub window if it exists. Also called by TabControl when the
1646 // user clicks the 'X' to dismiss a sub window.
1647 /* package */ void dismissSubWindow(TabControl.Tab t) {
1648 final WebView mainView = t.getWebView();
1649 if (t.getSubWebView() != null) {
1650 // Remove the container view and request focus on the main WebView.
1651 mContentView.removeView(t.getSubWebViewContainer());
1652 mainView.requestFocus();
1653 // Tell the TabControl to dismiss the subwindow. This will destroy
1654 // the WebView.
1655 mTabControl.dismissSubWindow(t);
1656 }
1657 }
1658
Leon Scroggins1f005d32009-08-10 17:36:42 -04001659 // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001660 // that accepts url as string.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001661 private TabControl.Tab openTabAndShow(String url, boolean closeOnExit,
1662 String appId) {
1663 return openTabAndShow(new UrlData(url), closeOnExit, appId);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001664 }
1665
1666 // This method does a ton of stuff. It will attempt to create a new tab
1667 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
Leon Scroggins1f005d32009-08-10 17:36:42 -04001668 // url isn't null, it will load the given url.
1669 /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001670 boolean closeOnExit, String appId) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001671 final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1672 final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1673 if (newTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001674 final TabControl.Tab tab = mTabControl.createNewTab(
1675 closeOnExit, appId, urlData.mUrl);
1676 WebView webview = tab.getWebView();
1677 if (CUSTOM_BROWSER_BAR) {
1678 mTitleBar.addTab(webview, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001679 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001680 removeTabFromContentView(currentTab);
1681 attachTabToContentView(tab);
Patrick Scott8bbd69f2009-08-14 13:35:53 -04001682 // We must set the new tab as the current tab to reflect the old
1683 // animation behavior.
1684 mTabControl.setCurrentTab(tab);
Leon Scroggins160a7e72009-08-14 18:28:01 -04001685 if (!urlData.isEmpty()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001686 urlData.loadIn(webview);
1687 }
1688 return tab;
1689 } else {
1690 // Get rid of the subwindow if it exists
1691 dismissSubWindow(currentTab);
1692 if (!urlData.isEmpty()) {
1693 // Load the given url.
1694 urlData.loadIn(currentTab.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001695 }
1696 }
Grace Klobac9181842009-04-14 08:53:22 -07001697 return currentTab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001698 }
1699
Grace Klobac9181842009-04-14 08:53:22 -07001700 private TabControl.Tab openTab(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001701 if (mSettings.openInBackground()) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001702 TabControl.Tab t = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001703 if (t != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001704 WebView view = t.getWebView();
1705 if (CUSTOM_BROWSER_BAR) {
1706 mTitleBar.addTab(view, false);
1707 }
1708 view.loadUrl(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001709 }
Grace Klobac9181842009-04-14 08:53:22 -07001710 return t;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001711 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001712 return openTabAndShow(url, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001713 }
1714 }
1715
1716 private class Copy implements OnMenuItemClickListener {
1717 private CharSequence mText;
1718
1719 public boolean onMenuItemClick(MenuItem item) {
1720 copy(mText);
1721 return true;
1722 }
1723
1724 public Copy(CharSequence toCopy) {
1725 mText = toCopy;
1726 }
1727 }
1728
1729 private class Download implements OnMenuItemClickListener {
1730 private String mText;
1731
1732 public boolean onMenuItemClick(MenuItem item) {
1733 onDownloadStartNoStream(mText, null, null, null, -1);
1734 return true;
1735 }
1736
1737 public Download(String toDownload) {
1738 mText = toDownload;
1739 }
1740 }
1741
1742 private void copy(CharSequence text) {
1743 try {
1744 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
1745 if (clip != null) {
1746 clip.setClipboardText(text);
1747 }
1748 } catch (android.os.RemoteException e) {
1749 Log.e(LOGTAG, "Copy failed", e);
1750 }
1751 }
1752
1753 /**
1754 * Resets the browser title-view to whatever it must be (for example, if we
1755 * load a page from history).
1756 */
1757 private void resetTitle() {
1758 resetLockIcon();
1759 resetTitleIconAndProgress();
1760 }
1761
1762 /**
1763 * Resets the browser title-view to whatever it must be
1764 * (for example, if we had a loading error)
1765 * When we have a new page, we call resetTitle, when we
1766 * have to reset the titlebar to whatever it used to be
1767 * (for example, if the user chose to stop loading), we
1768 * call resetTitleAndRevertLockIcon.
1769 */
1770 /* package */ void resetTitleAndRevertLockIcon() {
1771 revertLockIcon();
1772 resetTitleIconAndProgress();
1773 }
1774
1775 /**
1776 * Reset the title, favicon, and progress.
1777 */
1778 private void resetTitleIconAndProgress() {
1779 WebView current = mTabControl.getCurrentWebView();
1780 if (current == null) {
1781 return;
1782 }
1783 resetTitleAndIcon(current);
1784 int progress = current.getProgress();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001785 mWebChromeClient.onProgressChanged(current, progress);
1786 }
1787
1788 // Reset the title and the icon based on the given item.
1789 private void resetTitleAndIcon(WebView view) {
1790 WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
1791 if (item != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001792 setUrlTitle(item.getUrl(), item.getTitle(), view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001793 setFavicon(item.getFavicon());
1794 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001795 setUrlTitle(null, null, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001796 setFavicon(null);
1797 }
1798 }
1799
1800 /**
1801 * Sets a title composed of the URL and the title string.
1802 * @param url The URL of the site being loaded.
1803 * @param title The title of the site being loaded.
1804 */
Leon Scroggins1f005d32009-08-10 17:36:42 -04001805 private void setUrlTitle(String url, String title, WebView view) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001806 mUrl = url;
1807 mTitle = title;
1808
Leon Scroggins1f005d32009-08-10 17:36:42 -04001809 if (CUSTOM_BROWSER_BAR) {
1810 mTitleBar.setTitleAndUrl(title, url, view);
1811 } else {
1812 setTitle(buildUrlTitle(url, title));
The Android Open Source Project0c908882009-03-03 19:32:16 -08001813 }
1814 }
1815
1816 /**
1817 * Builds and returns the page title, which is some
1818 * combination of the page URL and title.
1819 * @param url The URL of the site being loaded.
1820 * @param title The title of the site being loaded.
1821 * @return The page title.
1822 */
1823 private String buildUrlTitle(String url, String title) {
1824 String urlTitle = "";
1825
1826 if (url != null) {
1827 String titleUrl = buildTitleUrl(url);
1828
1829 if (title != null && 0 < title.length()) {
1830 if (titleUrl != null && 0 < titleUrl.length()) {
1831 urlTitle = titleUrl + ": " + title;
1832 } else {
1833 urlTitle = title;
1834 }
1835 } else {
1836 if (titleUrl != null) {
1837 urlTitle = titleUrl;
1838 }
1839 }
1840 }
1841
1842 return urlTitle;
1843 }
1844
1845 /**
1846 * @param url The URL to build a title version of the URL from.
1847 * @return The title version of the URL or null if fails.
1848 * The title version of the URL can be either the URL hostname,
1849 * or the hostname with an "https://" prefix (for secure URLs),
1850 * or an empty string if, for example, the URL in question is a
1851 * file:// URL with no hostname.
1852 */
Leon Scroggins32e14a62009-06-11 10:26:34 -04001853 /* package */ static String buildTitleUrl(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001854 String titleUrl = null;
1855
1856 if (url != null) {
1857 try {
1858 // parse the url string
1859 URL urlObj = new URL(url);
1860 if (urlObj != null) {
1861 titleUrl = "";
1862
1863 String protocol = urlObj.getProtocol();
1864 String host = urlObj.getHost();
1865
1866 if (host != null && 0 < host.length()) {
1867 titleUrl = host;
1868 if (protocol != null) {
1869 // if a secure site, add an "https://" prefix!
1870 if (protocol.equalsIgnoreCase("https")) {
1871 titleUrl = protocol + "://" + host;
1872 }
1873 }
1874 }
1875 }
1876 } catch (MalformedURLException e) {}
1877 }
1878
1879 return titleUrl;
1880 }
1881
1882 // Set the favicon in the title bar.
1883 private void setFavicon(Bitmap icon) {
Leon Scroggins81db3662009-06-04 17:45:11 -04001884 if (CUSTOM_BROWSER_BAR) {
1885 Drawable[] array = new Drawable[3];
1886 array[0] = new PaintDrawable(Color.BLACK);
1887 PaintDrawable p = new PaintDrawable(Color.WHITE);
1888 array[1] = p;
1889 if (icon == null) {
1890 array[2] = mGenericFavicon;
1891 } else {
1892 array[2] = new BitmapDrawable(icon);
1893 }
1894 LayerDrawable d = new LayerDrawable(array);
1895 d.setLayerInset(1, 1, 1, 1, 1);
1896 d.setLayerInset(2, 2, 2, 2, 2);
Leon Scroggins1f005d32009-08-10 17:36:42 -04001897 mTitleBar.setFavicon(d, getTopWindow());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001898 } else {
Leon Scroggins81db3662009-06-04 17:45:11 -04001899 Drawable[] array = new Drawable[2];
1900 PaintDrawable p = new PaintDrawable(Color.WHITE);
1901 p.setCornerRadius(3f);
1902 array[0] = p;
1903 if (icon == null) {
1904 array[1] = mGenericFavicon;
1905 } else {
1906 array[1] = new BitmapDrawable(icon);
1907 }
1908 LayerDrawable d = new LayerDrawable(array);
1909 d.setLayerInset(1, 2, 2, 2, 2);
1910 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001911 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001912 }
1913
1914 /**
1915 * Saves the current lock-icon state before resetting
1916 * the lock icon. If we have an error, we may need to
1917 * roll back to the previous state.
1918 */
1919 private void saveLockIcon() {
1920 mPrevLockType = mLockIconType;
1921 }
1922
1923 /**
1924 * Reverts the lock-icon state to the last saved state,
1925 * for example, if we had an error, and need to cancel
1926 * the load.
1927 */
1928 private void revertLockIcon() {
1929 mLockIconType = mPrevLockType;
1930
Dave Bort31a6d1c2009-04-13 15:56:49 -07001931 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001932 Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
1933 " revert lock icon to " + mLockIconType);
1934 }
1935
1936 updateLockIconImage(mLockIconType);
1937 }
1938
Leon Scroggins1f005d32009-08-10 17:36:42 -04001939 /**
1940 * Close the tab after removing its associated title bar.
1941 */
1942 private void closeTab(TabControl.Tab t) {
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001943 if (CUSTOM_BROWSER_BAR) {
1944 mTitleBar.removeTab(mTabControl.getTabIndex(t));
1945 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001946 mTabControl.removeTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001947 }
1948
1949 private void goBackOnePageOrQuit() {
1950 TabControl.Tab current = mTabControl.getCurrentTab();
1951 if (current == null) {
1952 /*
1953 * Instead of finishing the activity, simply push this to the back
1954 * of the stack and let ActivityManager to choose the foreground
1955 * activity. As BrowserActivity is singleTask, it will be always the
1956 * root of the task. So we can use either true or false for
1957 * moveTaskToBack().
1958 */
1959 moveTaskToBack(true);
1960 }
1961 WebView w = current.getWebView();
1962 if (w.canGoBack()) {
1963 w.goBack();
1964 } else {
1965 // Check to see if we are closing a window that was created by
1966 // another window. If so, we switch back to that window.
1967 TabControl.Tab parent = current.getParentTab();
1968 if (parent != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001969 switchToTab(mTabControl.getTabIndex(parent));
1970 // Now we close the other tab
1971 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001972 } else {
1973 if (current.closeOnExit()) {
1974 if (mTabControl.getTabCount() == 1) {
1975 finish();
1976 return;
1977 }
Mike Reed7bfa63b2009-05-28 11:08:32 -04001978 // call pauseWebViewTimers() now, we won't be able to call
1979 // it in onPause() as the WebView won't be valid.
Grace Klobaec1b5ad2009-08-18 08:42:32 -07001980 // Temporarily change mActivityInPause to be true as
1981 // pauseWebViewTimers() will do nothing if mActivityInPause
1982 // is false.
Grace Kloba918e1d72009-08-13 14:55:06 -07001983 boolean savedState = mActivityInPause;
1984 if (savedState) {
Grace Klobaec1b5ad2009-08-18 08:42:32 -07001985 Log.e(LOGTAG, "BrowserActivity is already paused "
1986 + "while handing goBackOnePageOrQuit.");
Grace Kloba918e1d72009-08-13 14:55:06 -07001987 }
1988 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04001989 pauseWebViewTimers();
Grace Kloba918e1d72009-08-13 14:55:06 -07001990 mActivityInPause = savedState;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001991 removeTabFromContentView(current);
1992 mTabControl.removeTab(current);
1993 }
1994 /*
1995 * Instead of finishing the activity, simply push this to the back
1996 * of the stack and let ActivityManager to choose the foreground
1997 * activity. As BrowserActivity is singleTask, it will be always the
1998 * root of the task. So we can use either true or false for
1999 * moveTaskToBack().
2000 */
2001 moveTaskToBack(true);
2002 }
2003 }
2004 }
2005
2006 public KeyTracker.State onKeyTracker(int keyCode,
2007 KeyEvent event,
2008 KeyTracker.Stage stage,
2009 int duration) {
2010 // if onKeyTracker() is called after activity onStop()
2011 // because of accumulated key events,
2012 // we should ignore it as browser is not active any more.
2013 WebView topWindow = getTopWindow();
Andrei Popescuadc008d2009-06-26 14:11:30 +01002014 if (topWindow == null && mCustomView == null)
The Android Open Source Project0c908882009-03-03 19:32:16 -08002015 return KeyTracker.State.NOT_TRACKING;
2016
2017 if (keyCode == KeyEvent.KEYCODE_BACK) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01002018 // Check if a custom view is currently showing and, if it is, hide it.
2019 if (mCustomView != null) {
2020 mWebChromeClient.onHideCustomView();
2021 return KeyTracker.State.DONE_TRACKING;
2022 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002023 if (stage == KeyTracker.Stage.LONG_REPEAT) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04002024 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002025 return KeyTracker.State.DONE_TRACKING;
2026 } else if (stage == KeyTracker.Stage.UP) {
2027 // FIXME: Currently, we do not have a notion of the
2028 // history picker for the subwindow, but maybe we
2029 // should?
2030 WebView subwindow = mTabControl.getCurrentSubWindow();
2031 if (subwindow != null) {
2032 if (subwindow.canGoBack()) {
2033 subwindow.goBack();
2034 } else {
2035 dismissSubWindow(mTabControl.getCurrentTab());
2036 }
2037 } else {
2038 goBackOnePageOrQuit();
2039 }
2040 return KeyTracker.State.DONE_TRACKING;
2041 }
2042 return KeyTracker.State.KEEP_TRACKING;
2043 }
2044 return KeyTracker.State.NOT_TRACKING;
2045 }
2046
2047 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2048 if (keyCode == KeyEvent.KEYCODE_MENU) {
2049 mMenuIsDown = true;
Grace Kloba6ee9c492009-07-13 10:04:34 -07002050 } else if (mMenuIsDown) {
2051 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2052 // still down, we don't want to trigger the search. Pretend to
2053 // consume the key and do nothing.
2054 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002055 }
2056 boolean handled = mKeyTracker.doKeyDown(keyCode, event);
2057 if (!handled) {
2058 switch (keyCode) {
2059 case KeyEvent.KEYCODE_SPACE:
2060 if (event.isShiftPressed()) {
2061 getTopWindow().pageUp(false);
2062 } else {
2063 getTopWindow().pageDown(false);
2064 }
2065 handled = true;
2066 break;
2067
2068 default:
2069 break;
2070 }
2071 }
2072 return handled || super.onKeyDown(keyCode, event);
2073 }
2074
2075 @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2076 if (keyCode == KeyEvent.KEYCODE_MENU) {
2077 mMenuIsDown = false;
2078 }
2079 return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2080 }
2081
2082 private void stopLoading() {
2083 resetTitleAndRevertLockIcon();
2084 WebView w = getTopWindow();
2085 w.stopLoading();
2086 mWebViewClient.onPageFinished(w, w.getUrl());
2087
2088 cancelStopToast();
2089 mStopToast = Toast
2090 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2091 mStopToast.show();
2092 }
2093
2094 private void cancelStopToast() {
2095 if (mStopToast != null) {
2096 mStopToast.cancel();
2097 mStopToast = null;
2098 }
2099 }
2100
2101 // called by a non-UI thread to post the message
2102 public void postMessage(int what, int arg1, int arg2, Object obj) {
2103 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2104 }
2105
2106 // public message ids
2107 public final static int LOAD_URL = 1001;
2108 public final static int STOP_LOAD = 1002;
2109
2110 // Message Ids
2111 private static final int FOCUS_NODE_HREF = 102;
2112 private static final int CANCEL_CREDS_REQUEST = 103;
Grace Kloba92c18a52009-07-31 23:48:32 -07002113 private static final int RELEASE_WAKELOCK = 107;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002114
2115 // Private handler for handling javascript and saving passwords
2116 private Handler mHandler = new Handler() {
2117
2118 public void handleMessage(Message msg) {
2119 switch (msg.what) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002120 case FOCUS_NODE_HREF:
2121 String url = (String) msg.getData().get("url");
2122 if (url == null || url.length() == 0) {
2123 break;
2124 }
2125 HashMap focusNodeMap = (HashMap) msg.obj;
2126 WebView view = (WebView) focusNodeMap.get("webview");
2127 // Only apply the action if the top window did not change.
2128 if (getTopWindow() != view) {
2129 break;
2130 }
2131 switch (msg.arg1) {
2132 case R.id.open_context_menu_id:
2133 case R.id.view_image_context_menu_id:
2134 loadURL(getTopWindow(), url);
2135 break;
2136 case R.id.open_newtab_context_menu_id:
Grace Klobac9181842009-04-14 08:53:22 -07002137 final TabControl.Tab parent = mTabControl
2138 .getCurrentTab();
2139 final TabControl.Tab newTab = openTab(url);
2140 if (newTab != parent) {
2141 parent.addChildTab(newTab);
2142 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002143 break;
2144 case R.id.bookmark_context_menu_id:
2145 Intent intent = new Intent(BrowserActivity.this,
2146 AddBookmarkPage.class);
2147 intent.putExtra("url", url);
2148 startActivity(intent);
2149 break;
2150 case R.id.share_link_context_menu_id:
2151 Browser.sendString(BrowserActivity.this, url);
2152 break;
2153 case R.id.copy_link_context_menu_id:
2154 copy(url);
2155 break;
2156 case R.id.save_link_context_menu_id:
2157 case R.id.download_context_menu_id:
2158 onDownloadStartNoStream(url, null, null, null, -1);
2159 break;
2160 }
2161 break;
2162
2163 case LOAD_URL:
2164 loadURL(getTopWindow(), (String) msg.obj);
2165 break;
2166
2167 case STOP_LOAD:
2168 stopLoading();
2169 break;
2170
2171 case CANCEL_CREDS_REQUEST:
2172 resumeAfterCredentials();
2173 break;
2174
The Android Open Source Project0c908882009-03-03 19:32:16 -08002175 case RELEASE_WAKELOCK:
2176 if (mWakeLock.isHeld()) {
2177 mWakeLock.release();
2178 }
2179 break;
2180 }
2181 }
2182 };
2183
Leon Scroggins89c6d362009-07-15 16:54:37 -04002184 private void updateScreenshot(WebView view) {
2185 // If this is a bookmarked site, add a screenshot to the database.
2186 // FIXME: When should we update? Every time?
2187 // FIXME: Would like to make sure there is actually something to
2188 // draw, but the API for that (WebViewCore.pictureReady()) is not
2189 // currently accessible here.
Patrick Scott3918d442009-08-04 13:22:29 -04002190 ContentResolver cr = getContentResolver();
2191 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04002192 cr, view.getOriginalUrl(), view.getUrl(), false);
Patrick Scott3918d442009-08-04 13:22:29 -04002193 if (c != null) {
Leon Scroggins89c6d362009-07-15 16:54:37 -04002194 boolean succeed = c.moveToFirst();
2195 ContentValues values = null;
2196 while (succeed) {
2197 if (values == null) {
2198 final ByteArrayOutputStream os
2199 = new ByteArrayOutputStream();
2200 Picture thumbnail = view.capturePicture();
2201 // Keep width and height in sync with BrowserBookmarksPage
2202 // and bookmark_thumb
2203 Bitmap bm = Bitmap.createBitmap(100, 80,
2204 Bitmap.Config.ARGB_4444);
2205 Canvas canvas = new Canvas(bm);
2206 // May need to tweak these values to determine what is the
2207 // best scale factor
2208 canvas.scale(.5f, .5f);
2209 thumbnail.draw(canvas);
2210 bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2211 values = new ContentValues();
2212 values.put(Browser.BookmarkColumns.THUMBNAIL,
2213 os.toByteArray());
2214 }
2215 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2216 c.getInt(0)), values, null, null);
2217 succeed = c.moveToNext();
2218 }
2219 c.close();
2220 }
2221 }
2222
The Android Open Source Project0c908882009-03-03 19:32:16 -08002223 // -------------------------------------------------------------------------
2224 // WebViewClient implementation.
2225 //-------------------------------------------------------------------------
2226
2227 // Use in overrideUrlLoading
2228 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2229 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2230 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2231 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2232
2233 /* package */ WebViewClient getWebViewClient() {
2234 return mWebViewClient;
2235 }
2236
Patrick Scott3918d442009-08-04 13:22:29 -04002237 private void updateIcon(WebView view, Bitmap icon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002238 if (icon != null) {
2239 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
Patrick Scott3918d442009-08-04 13:22:29 -04002240 view, icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002241 }
2242 setFavicon(icon);
2243 }
2244
2245 private final WebViewClient mWebViewClient = new WebViewClient() {
2246 @Override
2247 public void onPageStarted(WebView view, String url, Bitmap favicon) {
2248 resetLockIcon(url);
Leon Scroggins1f005d32009-08-10 17:36:42 -04002249 setUrlTitle(url, null, view);
Ben Murdochbff2d602009-07-01 20:19:05 +01002250
2251 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2252 if (errorConsole != null) {
2253 errorConsole.clearErrorMessages();
2254 if (mShouldShowErrorConsole) {
2255 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2256 }
2257 }
2258
The Android Open Source Project0c908882009-03-03 19:32:16 -08002259 // Call updateIcon instead of setFavicon so the bookmark
2260 // database can be updated.
Patrick Scott3918d442009-08-04 13:22:29 -04002261 updateIcon(view, favicon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002262
Grace Kloba4d7880f2009-08-12 09:35:42 -07002263 if (mSettings.isTracing()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002264 String host;
2265 try {
2266 WebAddress uri = new WebAddress(url);
2267 host = uri.mHost;
2268 } catch (android.net.ParseException ex) {
Grace Kloba4d7880f2009-08-12 09:35:42 -07002269 host = "browser";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002270 }
2271 host = host.replace('.', '_');
Grace Kloba4d7880f2009-08-12 09:35:42 -07002272 host += ".trace";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002273 mInTrace = true;
Grace Kloba4d7880f2009-08-12 09:35:42 -07002274 Debug.startMethodTracing(host, 20 * 1024 * 1024);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002275 }
2276
2277 // Performance probe
2278 if (false) {
2279 mStart = SystemClock.uptimeMillis();
2280 mProcessStart = Process.getElapsedCpuTime();
2281 long[] sysCpu = new long[7];
2282 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2283 sysCpu, null)) {
2284 mUserStart = sysCpu[0] + sysCpu[1];
2285 mSystemStart = sysCpu[2];
2286 mIdleStart = sysCpu[3];
2287 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2288 }
2289 mUiStart = SystemClock.currentThreadTimeMillis();
2290 }
2291
2292 if (!mPageStarted) {
2293 mPageStarted = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002294 // if onResume() has been called, resumeWebViewTimers() does
2295 // nothing.
2296 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002297 }
2298
2299 // reset sync timer to avoid sync starts during loading a page
2300 CookieSyncManager.getInstance().resetSync();
2301
2302 mInLoad = true;
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002303 if (CUSTOM_BROWSER_BAR) {
2304 mTitleBar.setVisibility(View.VISIBLE);
2305 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002306 updateInLoadMenuItems();
2307 if (!mIsNetworkUp) {
2308 if ( mAlertDialog == null) {
2309 mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
2310 .setTitle(R.string.loadSuspendedTitle)
2311 .setMessage(R.string.loadSuspended)
2312 .setPositiveButton(R.string.ok, null)
2313 .show();
2314 }
2315 if (view != null) {
2316 view.setNetworkAvailable(false);
2317 }
2318 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002319 }
2320
2321 @Override
2322 public void onPageFinished(WebView view, String url) {
2323 // Reset the title and icon in case we stopped a provisional
2324 // load.
2325 resetTitleAndIcon(view);
2326
2327 // Update the lock icon image only once we are done loading
2328 updateLockIconImage(mLockIconType);
Leon Scroggins89c6d362009-07-15 16:54:37 -04002329 updateScreenshot(view);
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -04002330
The Android Open Source Project0c908882009-03-03 19:32:16 -08002331 // Performance probe
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002332 if (false) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002333 long[] sysCpu = new long[7];
2334 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2335 sysCpu, null)) {
2336 String uiInfo = "UI thread used "
2337 + (SystemClock.currentThreadTimeMillis() - mUiStart)
2338 + " ms";
Dave Bort31a6d1c2009-04-13 15:56:49 -07002339 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002340 Log.d(LOGTAG, uiInfo);
2341 }
2342 //The string that gets written to the log
2343 String performanceString = "It took total "
2344 + (SystemClock.uptimeMillis() - mStart)
2345 + " ms clock time to load the page."
2346 + "\nbrowser process used "
2347 + (Process.getElapsedCpuTime() - mProcessStart)
2348 + " ms, user processes used "
2349 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2350 + " ms, kernel used "
2351 + (sysCpu[2] - mSystemStart) * 10
2352 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2353 + " ms and irq took "
2354 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2355 * 10 + " ms, " + uiInfo;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002356 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002357 Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2358 }
2359 if (url != null) {
2360 // strip the url to maintain consistency
2361 String newUrl = new String(url);
2362 if (newUrl.startsWith("http://www.")) {
2363 newUrl = newUrl.substring(11);
2364 } else if (newUrl.startsWith("http://")) {
2365 newUrl = newUrl.substring(7);
2366 } else if (newUrl.startsWith("https://www.")) {
2367 newUrl = newUrl.substring(12);
2368 } else if (newUrl.startsWith("https://")) {
2369 newUrl = newUrl.substring(8);
2370 }
Dave Bort31a6d1c2009-04-13 15:56:49 -07002371 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002372 Log.d(LOGTAG, newUrl + " loaded");
2373 }
2374 /*
2375 if (sWhiteList.contains(newUrl)) {
2376 // The string that gets pushed to the statistcs
2377 // service
2378 performanceString = performanceString
2379 + "\nWebpage: "
2380 + newUrl
2381 + "\nCarrier: "
2382 + android.os.SystemProperties
2383 .get("gsm.sim.operator.alpha");
2384 if (mWebView != null
2385 && mWebView.getContext() != null
2386 && mWebView.getContext().getSystemService(
2387 Context.CONNECTIVITY_SERVICE) != null) {
2388 ConnectivityManager cManager =
2389 (ConnectivityManager) mWebView
2390 .getContext().getSystemService(
2391 Context.CONNECTIVITY_SERVICE);
2392 NetworkInfo nInfo = cManager
2393 .getActiveNetworkInfo();
2394 if (nInfo != null) {
2395 performanceString = performanceString
2396 + "\nNetwork Type: "
2397 + nInfo.getType().toString();
2398 }
2399 }
2400 Checkin.logEvent(mResolver,
2401 Checkin.Events.Tag.WEBPAGE_LOAD,
2402 performanceString);
2403 Log.w(LOGTAG, "pushed to the statistics service");
2404 }
2405 */
2406 }
2407 }
2408 }
2409
2410 if (mInTrace) {
2411 mInTrace = false;
2412 Debug.stopMethodTracing();
2413 }
2414
2415 if (mPageStarted) {
2416 mPageStarted = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002417 // pauseWebViewTimers() will do nothing and return false if
2418 // onPause() is not called yet.
2419 if (pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002420 if (mWakeLock.isHeld()) {
2421 mHandler.removeMessages(RELEASE_WAKELOCK);
2422 mWakeLock.release();
2423 }
2424 }
2425 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002426 }
2427
2428 // return true if want to hijack the url to let another app to handle it
2429 @Override
2430 public boolean shouldOverrideUrlLoading(WebView view, String url) {
2431 if (url.startsWith(SCHEME_WTAI)) {
2432 // wtai://wp/mc;number
2433 // number=string(phone-number)
2434 if (url.startsWith(SCHEME_WTAI_MC)) {
2435 Intent intent = new Intent(Intent.ACTION_VIEW,
2436 Uri.parse(WebView.SCHEME_TEL +
2437 url.substring(SCHEME_WTAI_MC.length())));
2438 startActivity(intent);
2439 return true;
2440 }
2441 // wtai://wp/sd;dtmf
2442 // dtmf=string(dialstring)
2443 if (url.startsWith(SCHEME_WTAI_SD)) {
2444 // TODO
2445 // only send when there is active voice connection
2446 return false;
2447 }
2448 // wtai://wp/ap;number;name
2449 // number=string(phone-number)
2450 // name=string
2451 if (url.startsWith(SCHEME_WTAI_AP)) {
2452 // TODO
2453 return false;
2454 }
2455 }
2456
Dianne Hackborn99189432009-06-17 18:06:18 -07002457 // The "about:" schemes are internal to the browser; don't
2458 // want these to be dispatched to other apps.
2459 if (url.startsWith("about:")) {
2460 return false;
2461 }
Ben Murdochbff2d602009-07-01 20:19:05 +01002462
Dianne Hackborn99189432009-06-17 18:06:18 -07002463 Intent intent;
Ben Murdochbff2d602009-07-01 20:19:05 +01002464
Dianne Hackborn99189432009-06-17 18:06:18 -07002465 // perform generic parsing of the URI to turn it into an Intent.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002466 try {
Dianne Hackborn99189432009-06-17 18:06:18 -07002467 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2468 } catch (URISyntaxException ex) {
2469 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
The Android Open Source Project0c908882009-03-03 19:32:16 -08002470 return false;
2471 }
2472
Grace Kloba5b078b52009-06-24 20:23:41 -07002473 // check whether the intent can be resolved. If not, we will see
2474 // whether we can download it from the Market.
2475 if (getPackageManager().resolveActivity(intent, 0) == null) {
2476 String packagename = intent.getPackage();
2477 if (packagename != null) {
2478 intent = new Intent(Intent.ACTION_VIEW, Uri
2479 .parse("market://search?q=pname:" + packagename));
2480 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2481 startActivity(intent);
2482 return true;
2483 } else {
2484 return false;
2485 }
2486 }
2487
Dianne Hackborn99189432009-06-17 18:06:18 -07002488 // sanitize the Intent, ensuring web pages can not bypass browser
2489 // security (only access to BROWSABLE activities).
The Android Open Source Project0c908882009-03-03 19:32:16 -08002490 intent.addCategory(Intent.CATEGORY_BROWSABLE);
Dianne Hackborn99189432009-06-17 18:06:18 -07002491 intent.setComponent(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002492 try {
2493 if (startActivityIfNeeded(intent, -1)) {
2494 return true;
2495 }
2496 } catch (ActivityNotFoundException ex) {
2497 // ignore the error. If no application can handle the URL,
2498 // eg about:blank, assume the browser can handle it.
2499 }
2500
2501 if (mMenuIsDown) {
2502 openTab(url);
2503 closeOptionsMenu();
2504 return true;
2505 }
2506
2507 return false;
2508 }
2509
2510 /**
2511 * Updates the lock icon. This method is called when we discover another
2512 * resource to be loaded for this page (for example, javascript). While
2513 * we update the icon type, we do not update the lock icon itself until
2514 * we are done loading, it is slightly more secure this way.
2515 */
2516 @Override
2517 public void onLoadResource(WebView view, String url) {
2518 if (url != null && url.length() > 0) {
2519 // It is only if the page claims to be secure
2520 // that we may have to update the lock:
2521 if (mLockIconType == LOCK_ICON_SECURE) {
2522 // If NOT a 'safe' url, change the lock to mixed content!
2523 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2524 mLockIconType = LOCK_ICON_MIXED;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002525 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002526 Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2527 " updated lock icon to " + mLockIconType + " due to " + url);
2528 }
2529 }
2530 }
2531 }
2532 }
2533
2534 /**
2535 * Show the dialog, asking the user if they would like to continue after
2536 * an excessive number of HTTP redirects.
2537 */
2538 @Override
2539 public void onTooManyRedirects(WebView view, final Message cancelMsg,
2540 final Message continueMsg) {
2541 new AlertDialog.Builder(BrowserActivity.this)
2542 .setTitle(R.string.browserFrameRedirect)
2543 .setMessage(R.string.browserFrame307Post)
2544 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2545 public void onClick(DialogInterface dialog, int which) {
2546 continueMsg.sendToTarget();
2547 }})
2548 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2549 public void onClick(DialogInterface dialog, int which) {
2550 cancelMsg.sendToTarget();
2551 }})
2552 .setOnCancelListener(new OnCancelListener() {
2553 public void onCancel(DialogInterface dialog) {
2554 cancelMsg.sendToTarget();
2555 }})
2556 .show();
2557 }
2558
Patrick Scott37911c72009-03-24 18:02:58 -07002559 // Container class for the next error dialog that needs to be
2560 // displayed.
2561 class ErrorDialog {
2562 public final int mTitle;
2563 public final String mDescription;
2564 public final int mError;
2565 ErrorDialog(int title, String desc, int error) {
2566 mTitle = title;
2567 mDescription = desc;
2568 mError = error;
2569 }
2570 };
2571
2572 private void processNextError() {
2573 if (mQueuedErrors == null) {
2574 return;
2575 }
2576 // The first one is currently displayed so just remove it.
2577 mQueuedErrors.removeFirst();
2578 if (mQueuedErrors.size() == 0) {
2579 mQueuedErrors = null;
2580 return;
2581 }
2582 showError(mQueuedErrors.getFirst());
2583 }
2584
2585 private DialogInterface.OnDismissListener mDialogListener =
2586 new DialogInterface.OnDismissListener() {
2587 public void onDismiss(DialogInterface d) {
2588 processNextError();
2589 }
2590 };
2591 private LinkedList<ErrorDialog> mQueuedErrors;
2592
2593 private void queueError(int err, String desc) {
2594 if (mQueuedErrors == null) {
2595 mQueuedErrors = new LinkedList<ErrorDialog>();
2596 }
2597 for (ErrorDialog d : mQueuedErrors) {
2598 if (d.mError == err) {
2599 // Already saw a similar error, ignore the new one.
2600 return;
2601 }
2602 }
2603 ErrorDialog errDialog = new ErrorDialog(
2604 err == EventHandler.FILE_NOT_FOUND_ERROR ?
2605 R.string.browserFrameFileErrorLabel :
2606 R.string.browserFrameNetworkErrorLabel,
2607 desc, err);
2608 mQueuedErrors.addLast(errDialog);
2609
2610 // Show the dialog now if the queue was empty.
2611 if (mQueuedErrors.size() == 1) {
2612 showError(errDialog);
2613 }
2614 }
2615
2616 private void showError(ErrorDialog errDialog) {
2617 AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
2618 .setTitle(errDialog.mTitle)
2619 .setMessage(errDialog.mDescription)
2620 .setPositiveButton(R.string.ok, null)
2621 .create();
2622 d.setOnDismissListener(mDialogListener);
2623 d.show();
2624 }
2625
The Android Open Source Project0c908882009-03-03 19:32:16 -08002626 /**
2627 * Show a dialog informing the user of the network error reported by
2628 * WebCore.
2629 */
2630 @Override
2631 public void onReceivedError(WebView view, int errorCode,
2632 String description, String failingUrl) {
2633 if (errorCode != EventHandler.ERROR_LOOKUP &&
2634 errorCode != EventHandler.ERROR_CONNECT &&
2635 errorCode != EventHandler.ERROR_BAD_URL &&
2636 errorCode != EventHandler.ERROR_UNSUPPORTED_SCHEME &&
2637 errorCode != EventHandler.FILE_ERROR) {
Patrick Scott37911c72009-03-24 18:02:58 -07002638 queueError(errorCode, description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002639 }
Patrick Scott37911c72009-03-24 18:02:58 -07002640 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
2641 + " " + description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002642
2643 // We need to reset the title after an error.
2644 resetTitleAndRevertLockIcon();
2645 }
2646
2647 /**
2648 * Check with the user if it is ok to resend POST data as the page they
2649 * are trying to navigate to is the result of a POST.
2650 */
2651 @Override
2652 public void onFormResubmission(WebView view, final Message dontResend,
2653 final Message resend) {
2654 new AlertDialog.Builder(BrowserActivity.this)
2655 .setTitle(R.string.browserFrameFormResubmitLabel)
2656 .setMessage(R.string.browserFrameFormResubmitMessage)
2657 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2658 public void onClick(DialogInterface dialog, int which) {
2659 resend.sendToTarget();
2660 }})
2661 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2662 public void onClick(DialogInterface dialog, int which) {
2663 dontResend.sendToTarget();
2664 }})
2665 .setOnCancelListener(new OnCancelListener() {
2666 public void onCancel(DialogInterface dialog) {
2667 dontResend.sendToTarget();
2668 }})
2669 .show();
2670 }
2671
2672 /**
2673 * Insert the url into the visited history database.
2674 * @param url The url to be inserted.
2675 * @param isReload True if this url is being reloaded.
2676 * FIXME: Not sure what to do when reloading the page.
2677 */
2678 @Override
2679 public void doUpdateVisitedHistory(WebView view, String url,
2680 boolean isReload) {
2681 if (url.regionMatches(true, 0, "about:", 0, 6)) {
2682 return;
2683 }
2684 Browser.updateVisitedHistory(mResolver, url, true);
2685 WebIconDatabase.getInstance().retainIconForPageUrl(url);
2686 }
2687
2688 /**
2689 * Displays SSL error(s) dialog to the user.
2690 */
2691 @Override
2692 public void onReceivedSslError(
2693 final WebView view, final SslErrorHandler handler, final SslError error) {
2694
2695 if (mSettings.showSecurityWarnings()) {
2696 final LayoutInflater factory =
2697 LayoutInflater.from(BrowserActivity.this);
2698 final View warningsView =
2699 factory.inflate(R.layout.ssl_warnings, null);
2700 final LinearLayout placeholder =
2701 (LinearLayout)warningsView.findViewById(R.id.placeholder);
2702
2703 if (error.hasError(SslError.SSL_UNTRUSTED)) {
2704 LinearLayout ll = (LinearLayout)factory
2705 .inflate(R.layout.ssl_warning, null);
2706 ((TextView)ll.findViewById(R.id.warning))
2707 .setText(R.string.ssl_untrusted);
2708 placeholder.addView(ll);
2709 }
2710
2711 if (error.hasError(SslError.SSL_IDMISMATCH)) {
2712 LinearLayout ll = (LinearLayout)factory
2713 .inflate(R.layout.ssl_warning, null);
2714 ((TextView)ll.findViewById(R.id.warning))
2715 .setText(R.string.ssl_mismatch);
2716 placeholder.addView(ll);
2717 }
2718
2719 if (error.hasError(SslError.SSL_EXPIRED)) {
2720 LinearLayout ll = (LinearLayout)factory
2721 .inflate(R.layout.ssl_warning, null);
2722 ((TextView)ll.findViewById(R.id.warning))
2723 .setText(R.string.ssl_expired);
2724 placeholder.addView(ll);
2725 }
2726
2727 if (error.hasError(SslError.SSL_NOTYETVALID)) {
2728 LinearLayout ll = (LinearLayout)factory
2729 .inflate(R.layout.ssl_warning, null);
2730 ((TextView)ll.findViewById(R.id.warning))
2731 .setText(R.string.ssl_not_yet_valid);
2732 placeholder.addView(ll);
2733 }
2734
2735 new AlertDialog.Builder(BrowserActivity.this)
2736 .setTitle(R.string.security_warning)
2737 .setIcon(android.R.drawable.ic_dialog_alert)
2738 .setView(warningsView)
2739 .setPositiveButton(R.string.ssl_continue,
2740 new DialogInterface.OnClickListener() {
2741 public void onClick(DialogInterface dialog, int whichButton) {
2742 handler.proceed();
2743 }
2744 })
2745 .setNeutralButton(R.string.view_certificate,
2746 new DialogInterface.OnClickListener() {
2747 public void onClick(DialogInterface dialog, int whichButton) {
2748 showSSLCertificateOnError(view, handler, error);
2749 }
2750 })
2751 .setNegativeButton(R.string.cancel,
2752 new DialogInterface.OnClickListener() {
2753 public void onClick(DialogInterface dialog, int whichButton) {
2754 handler.cancel();
2755 BrowserActivity.this.resetTitleAndRevertLockIcon();
2756 }
2757 })
2758 .setOnCancelListener(
2759 new DialogInterface.OnCancelListener() {
2760 public void onCancel(DialogInterface dialog) {
2761 handler.cancel();
2762 BrowserActivity.this.resetTitleAndRevertLockIcon();
2763 }
2764 })
2765 .show();
2766 } else {
2767 handler.proceed();
2768 }
2769 }
2770
2771 /**
2772 * Handles an HTTP authentication request.
2773 *
2774 * @param handler The authentication handler
2775 * @param host The host
2776 * @param realm The realm
2777 */
2778 @Override
2779 public void onReceivedHttpAuthRequest(WebView view,
2780 final HttpAuthHandler handler, final String host, final String realm) {
2781 String username = null;
2782 String password = null;
2783
2784 boolean reuseHttpAuthUsernamePassword =
2785 handler.useHttpAuthUsernamePassword();
2786
2787 if (reuseHttpAuthUsernamePassword &&
2788 (mTabControl.getCurrentWebView() != null)) {
2789 String[] credentials =
2790 mTabControl.getCurrentWebView()
2791 .getHttpAuthUsernamePassword(host, realm);
2792 if (credentials != null && credentials.length == 2) {
2793 username = credentials[0];
2794 password = credentials[1];
2795 }
2796 }
2797
2798 if (username != null && password != null) {
2799 handler.proceed(username, password);
2800 } else {
2801 showHttpAuthentication(handler, host, realm, null, null, null, 0);
2802 }
2803 }
2804
2805 @Override
2806 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
2807 if (mMenuIsDown) {
2808 // only check shortcut key when MENU is held
2809 return getWindow().isShortcutKey(event.getKeyCode(), event);
2810 } else {
2811 return false;
2812 }
2813 }
2814
2815 @Override
2816 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
2817 if (view != mTabControl.getCurrentTopWebView()) {
2818 return;
2819 }
2820 if (event.isDown()) {
2821 BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
2822 } else {
2823 BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
2824 }
2825 }
2826 };
2827
2828 //--------------------------------------------------------------------------
2829 // WebChromeClient implementation
2830 //--------------------------------------------------------------------------
2831
2832 /* package */ WebChromeClient getWebChromeClient() {
2833 return mWebChromeClient;
2834 }
2835
2836 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
2837 // Helper method to create a new tab or sub window.
2838 private void createWindow(final boolean dialog, final Message msg) {
2839 if (dialog) {
2840 mTabControl.createSubWindow();
2841 final TabControl.Tab t = mTabControl.getCurrentTab();
2842 attachSubWindow(t);
2843 WebView.WebViewTransport transport =
2844 (WebView.WebViewTransport) msg.obj;
2845 transport.setWebView(t.getSubWebView());
2846 msg.sendToTarget();
2847 } else {
2848 final TabControl.Tab parent = mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04002849 final TabControl.Tab newTab
2850 = openTabAndShow(EMPTY_URL_DATA, false, null);
Grace Klobac9181842009-04-14 08:53:22 -07002851 if (newTab != parent) {
2852 parent.addChildTab(newTab);
2853 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002854 WebView.WebViewTransport transport =
2855 (WebView.WebViewTransport) msg.obj;
2856 transport.setWebView(mTabControl.getCurrentWebView());
Leon Scroggins1f005d32009-08-10 17:36:42 -04002857 msg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002858 }
2859 }
2860
2861 @Override
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002862 public void onChangeViewingMode(WebView view, int newViewingMode) {
2863 if (!CUSTOM_BROWSER_BAR || view != getTopWindow()) {
Leon Scroggins4943a312009-08-07 16:12:57 -04002864 return;
2865 }
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002866 switch (newViewingMode) {
2867 case WebView.NO_VIEWING_MODE:
2868 break;
2869 case WebView.OVERVIEW_MODE:
2870 case WebView.READING_MODE_WITH_TITLE_BAR:
2871 case WebView.TITLE_BAR_DISMISS_MODE:
Leon Scroggins4943a312009-08-07 16:12:57 -04002872 mTitleBar.setVisibility(View.VISIBLE);
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002873 break;
2874 case WebView.READING_MODE:
Leon Scroggins4943a312009-08-07 16:12:57 -04002875 mTitleBar.setVisibility(View.GONE);
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002876 break;
2877 default:
2878 break;
Leon Scroggins4943a312009-08-07 16:12:57 -04002879 }
2880 }
2881
2882 @Override
The Android Open Source Project0c908882009-03-03 19:32:16 -08002883 public boolean onCreateWindow(WebView view, final boolean dialog,
2884 final boolean userGesture, final Message resultMsg) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002885 // Short-circuit if we can't create any more tabs or sub windows.
2886 if (dialog && mTabControl.getCurrentSubWindow() != null) {
2887 new AlertDialog.Builder(BrowserActivity.this)
2888 .setTitle(R.string.too_many_subwindows_dialog_title)
2889 .setIcon(android.R.drawable.ic_dialog_alert)
2890 .setMessage(R.string.too_many_subwindows_dialog_message)
2891 .setPositiveButton(R.string.ok, null)
2892 .show();
2893 return false;
2894 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
2895 new AlertDialog.Builder(BrowserActivity.this)
2896 .setTitle(R.string.too_many_windows_dialog_title)
2897 .setIcon(android.R.drawable.ic_dialog_alert)
2898 .setMessage(R.string.too_many_windows_dialog_message)
2899 .setPositiveButton(R.string.ok, null)
2900 .show();
2901 return false;
2902 }
2903
2904 // Short-circuit if this was a user gesture.
2905 if (userGesture) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002906 createWindow(dialog, resultMsg);
2907 return true;
2908 }
2909
2910 // Allow the popup and create the appropriate window.
2911 final AlertDialog.OnClickListener allowListener =
2912 new AlertDialog.OnClickListener() {
2913 public void onClick(DialogInterface d,
2914 int which) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002915 createWindow(dialog, resultMsg);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002916 }
2917 };
2918
2919 // Block the popup by returning a null WebView.
2920 final AlertDialog.OnClickListener blockListener =
2921 new AlertDialog.OnClickListener() {
2922 public void onClick(DialogInterface d, int which) {
2923 resultMsg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002924 }
2925 };
2926
2927 // Build a confirmation dialog to display to the user.
2928 final AlertDialog d =
2929 new AlertDialog.Builder(BrowserActivity.this)
2930 .setTitle(R.string.attention)
2931 .setIcon(android.R.drawable.ic_dialog_alert)
2932 .setMessage(R.string.popup_window_attempt)
2933 .setPositiveButton(R.string.allow, allowListener)
2934 .setNegativeButton(R.string.block, blockListener)
2935 .setCancelable(false)
2936 .create();
2937
2938 // Show the confirmation dialog.
2939 d.show();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002940 return true;
2941 }
2942
2943 @Override
2944 public void onCloseWindow(WebView window) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002945 final TabControl.Tab current = mTabControl.getCurrentTab();
2946 final TabControl.Tab parent = current.getParentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002947 if (parent != null) {
2948 // JavaScript can only close popup window.
Leon Scroggins1f005d32009-08-10 17:36:42 -04002949 switchToTab(mTabControl.getTabIndex(parent));
2950 // Now we need to close the window
2951 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002952 }
2953 }
2954
2955 @Override
2956 public void onProgressChanged(WebView view, int newProgress) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002957 if (CUSTOM_BROWSER_BAR) {
2958 mTitleBar.setProgress(newProgress, view);
2959 } else {
2960 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
2961 newProgress * 100);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002962 }
2963
2964 if (newProgress == 100) {
2965 // onProgressChanged() is called for sub-frame too while
2966 // onPageFinished() is only called for the main frame. sync
2967 // cookie and cache promptly here.
2968 CookieSyncManager.getInstance().sync();
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002969 if (mInLoad) {
2970 mInLoad = false;
2971 updateInLoadMenuItems();
2972 }
2973 } else {
2974 // onPageFinished may have already been called but a subframe
2975 // is still loading and updating the progress. Reset mInLoad
2976 // and update the menu items.
2977 if (!mInLoad) {
2978 mInLoad = true;
2979 updateInLoadMenuItems();
2980 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002981 }
2982 }
2983
2984 @Override
2985 public void onReceivedTitle(WebView view, String title) {
Patrick Scott598c9cc2009-06-04 11:10:38 -04002986 String url = view.getUrl();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002987
2988 // here, if url is null, we want to reset the title
Leon Scroggins1f005d32009-08-10 17:36:42 -04002989 setUrlTitle(url, title, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002990
2991 if (url == null ||
2992 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
2993 return;
2994 }
Leon Scrogginsfce182b2009-05-08 13:54:52 -04002995 // See if we can find the current url in our history database and
2996 // add the new title to it.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002997 if (url.startsWith("http://www.")) {
2998 url = url.substring(11);
2999 } else if (url.startsWith("http://")) {
3000 url = url.substring(4);
3001 }
3002 try {
3003 url = "%" + url;
3004 String [] selArgs = new String[] { url };
3005
3006 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
3007 + Browser.BookmarkColumns.BOOKMARK + " = 0";
3008 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
3009 Browser.HISTORY_PROJECTION, where, selArgs, null);
3010 if (c.moveToFirst()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003011 // Current implementation of database only has one entry per
3012 // url.
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003013 ContentValues map = new ContentValues();
3014 map.put(Browser.BookmarkColumns.TITLE, title);
3015 mResolver.update(Browser.BOOKMARKS_URI, map,
3016 "_id = " + c.getInt(0), null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003017 }
3018 c.close();
3019 } catch (IllegalStateException e) {
3020 Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3021 } catch (SQLiteException ex) {
3022 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3023 }
3024 }
3025
3026 @Override
3027 public void onReceivedIcon(WebView view, Bitmap icon) {
Patrick Scott3918d442009-08-04 13:22:29 -04003028 updateIcon(view, icon);
3029 }
3030
3031 @Override
3032 public void onReceivedTouchIconUrl(WebView view, String url) {
3033 final ContentResolver cr = getContentResolver();
3034 final Cursor c =
3035 BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04003036 view.getOriginalUrl(), view.getUrl(), true);
Patrick Scott3918d442009-08-04 13:22:29 -04003037 if (c != null) {
3038 if (c.getCount() > 0) {
3039 new DownloadTouchIcon(cr, c, view).execute(url);
3040 } else {
3041 c.close();
3042 }
3043 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003044 }
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003045
Andrei Popescuadc008d2009-06-26 14:11:30 +01003046 @Override
Andrei Popescuc9b55562009-07-07 10:51:15 +01003047 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01003048 if (mCustomView != null)
3049 return;
3050
3051 // Add the custom view to its container.
3052 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
3053 mCustomView = view;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003054 mCustomViewCallback = callback;
Andrei Popescuadc008d2009-06-26 14:11:30 +01003055 // Save the menu state and set it to empty while the custom
3056 // view is showing.
3057 mOldMenuState = mMenuState;
3058 mMenuState = EMPTY_MENU;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003059 // Hide the content view.
3060 mContentView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003061 // Finally show the custom view container.
Andrei Popescuc9b55562009-07-07 10:51:15 +01003062 mCustomViewContainer.setVisibility(View.VISIBLE);
3063 mCustomViewContainer.bringToFront();
Andrei Popescuadc008d2009-06-26 14:11:30 +01003064 }
3065
3066 @Override
3067 public void onHideCustomView() {
3068 if (mCustomView == null)
3069 return;
3070
Andrei Popescuc9b55562009-07-07 10:51:15 +01003071 // Hide the custom view.
3072 mCustomView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003073 // Remove the custom view from its container.
3074 mCustomViewContainer.removeView(mCustomView);
3075 mCustomView = null;
3076 // Reset the old menu state.
3077 mMenuState = mOldMenuState;
3078 mOldMenuState = EMPTY_MENU;
3079 mCustomViewContainer.setVisibility(View.GONE);
Andrei Popescuc9b55562009-07-07 10:51:15 +01003080 mCustomViewCallback.onCustomViewHidden();
3081 // Show the content view.
3082 mContentView.setVisibility(View.VISIBLE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003083 }
3084
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003085 /**
Andrei Popescu79e82b72009-07-27 12:01:59 +01003086 * The origin has exceeded its database quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003087 * @param url the URL that exceeded the quota
3088 * @param databaseIdentifier the identifier of the database on
3089 * which the transaction that caused the quota overflow was run
3090 * @param currentQuota the current quota for the origin.
Ben Murdoch25a15232009-08-25 19:38:07 +01003091 * @param estimatedSize the estimated size of the database.
Andrei Popescu79e82b72009-07-27 12:01:59 +01003092 * @param totalUsedQuota is the sum of all origins' quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003093 * @param quotaUpdater The callback to run when a decision to allow or
3094 * deny quota has been made. Don't forget to call this!
3095 */
3096 @Override
3097 public void onExceededDatabaseQuota(String url,
Ben Murdoch25a15232009-08-25 19:38:07 +01003098 String databaseIdentifier, long currentQuota, long estimatedSize,
3099 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
Andrei Popescu79e82b72009-07-27 12:01:59 +01003100 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
Ben Murdoch25a15232009-08-25 19:38:07 +01003101 url, databaseIdentifier, currentQuota, estimatedSize,
3102 totalUsedQuota, quotaUpdater);
Andrei Popescu79e82b72009-07-27 12:01:59 +01003103 }
3104
3105 /**
3106 * The Application Cache has exceeded its max size.
3107 * @param spaceNeeded is the amount of disk space that would be needed
3108 * in order for the last appcache operation to succeed.
3109 * @param totalUsedQuota is the sum of all origins' quota.
3110 * @param quotaUpdater A callback to inform the WebCore thread that a new
3111 * app cache size is available. This callback must always be executed at
3112 * some point to ensure that the sleeping WebCore thread is woken up.
3113 */
3114 @Override
3115 public void onReachedMaxAppCacheSize(long spaceNeeded,
3116 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3117 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3118 spaceNeeded, totalUsedQuota, quotaUpdater);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003119 }
Ben Murdoch7db26342009-06-03 18:21:19 +01003120
Steve Block2bc69912009-07-30 14:45:13 +01003121 /**
3122 * Instructs the browser to show a prompt to ask the user to set the
3123 * Geolocation permission state for the specified origin.
3124 * @param origin The origin for which Geolocation permissions are
3125 * requested.
3126 * @param callback The callback to call once the user has set the
3127 * Geolocation permission state.
3128 */
3129 @Override
3130 public void onGeolocationPermissionsShowPrompt(String origin,
3131 GeolocationPermissions.Callback callback) {
3132 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3133 origin, callback);
3134 }
3135
3136 /**
3137 * Instructs the browser to hide the Geolocation permissions prompt.
3138 */
3139 @Override
3140 public void onGeolocationPermissionsHidePrompt() {
3141 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3142 }
3143
Ben Murdoch7db26342009-06-03 18:21:19 +01003144 /* Adds a JavaScript error message to the system log.
3145 * @param message The error message to report.
3146 * @param lineNumber The line number of the error.
3147 * @param sourceID The name of the source file that caused the error.
3148 */
3149 @Override
3150 public void addMessageToConsole(String message, int lineNumber, String sourceID) {
Ben Murdochbff2d602009-07-01 20:19:05 +01003151 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3152 errorConsole.addErrorMessage(message, sourceID, lineNumber);
3153 if (mShouldShowErrorConsole &&
3154 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3155 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3156 }
3157 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
Ben Murdoch7db26342009-06-03 18:21:19 +01003158 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003159 };
3160
3161 /**
3162 * Notify the host application a download should be done, or that
3163 * the data should be streamed if a streaming viewer is available.
3164 * @param url The full url to the content that should be downloaded
3165 * @param contentDisposition Content-disposition http header, if
3166 * present.
3167 * @param mimetype The mimetype of the content reported by the server
3168 * @param contentLength The file size reported by the server
3169 */
3170 public void onDownloadStart(String url, String userAgent,
3171 String contentDisposition, String mimetype, long contentLength) {
3172 // if we're dealing wih A/V content that's not explicitly marked
3173 // for download, check if it's streamable.
3174 if (contentDisposition == null
3175 || !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) {
3176 // query the package manager to see if there's a registered handler
3177 // that matches.
3178 Intent intent = new Intent(Intent.ACTION_VIEW);
3179 intent.setDataAndType(Uri.parse(url), mimetype);
3180 if (getPackageManager().resolveActivity(intent,
3181 PackageManager.MATCH_DEFAULT_ONLY) != null) {
3182 // someone knows how to handle this mime type with this scheme, don't download.
3183 try {
3184 startActivity(intent);
3185 return;
3186 } catch (ActivityNotFoundException ex) {
Dave Bort31a6d1c2009-04-13 15:56:49 -07003187 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003188 Log.d(LOGTAG, "activity not found for " + mimetype
3189 + " over " + Uri.parse(url).getScheme(), ex);
3190 }
3191 // Best behavior is to fall back to a download in this case
3192 }
3193 }
3194 }
3195 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3196 }
3197
3198 /**
3199 * Notify the host application a download should be done, even if there
3200 * is a streaming viewer available for thise type.
3201 * @param url The full url to the content that should be downloaded
3202 * @param contentDisposition Content-disposition http header, if
3203 * present.
3204 * @param mimetype The mimetype of the content reported by the server
3205 * @param contentLength The file size reported by the server
3206 */
3207 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3208 String contentDisposition, String mimetype, long contentLength) {
3209
3210 String filename = URLUtil.guessFileName(url,
3211 contentDisposition, mimetype);
3212
3213 // Check to see if we have an SDCard
3214 String status = Environment.getExternalStorageState();
3215 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3216 int title;
3217 String msg;
3218
3219 // Check to see if the SDCard is busy, same as the music app
3220 if (status.equals(Environment.MEDIA_SHARED)) {
3221 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3222 title = R.string.download_sdcard_busy_dlg_title;
3223 } else {
3224 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3225 title = R.string.download_no_sdcard_dlg_title;
3226 }
3227
3228 new AlertDialog.Builder(this)
3229 .setTitle(title)
3230 .setIcon(android.R.drawable.ic_dialog_alert)
3231 .setMessage(msg)
3232 .setPositiveButton(R.string.ok, null)
3233 .show();
3234 return;
3235 }
3236
3237 // java.net.URI is a lot stricter than KURL so we have to undo
3238 // KURL's percent-encoding and redo the encoding using java.net.URI.
3239 URI uri = null;
3240 try {
3241 // Undo the percent-encoding that KURL may have done.
3242 String newUrl = new String(URLUtil.decode(url.getBytes()));
3243 // Parse the url into pieces
3244 WebAddress w = new WebAddress(newUrl);
3245 String frag = null;
3246 String query = null;
3247 String path = w.mPath;
3248 // Break the path into path, query, and fragment
3249 if (path.length() > 0) {
3250 // Strip the fragment
3251 int idx = path.lastIndexOf('#');
3252 if (idx != -1) {
3253 frag = path.substring(idx + 1);
3254 path = path.substring(0, idx);
3255 }
3256 idx = path.lastIndexOf('?');
3257 if (idx != -1) {
3258 query = path.substring(idx + 1);
3259 path = path.substring(0, idx);
3260 }
3261 }
3262 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3263 query, frag);
3264 } catch (Exception e) {
3265 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3266 return;
3267 }
3268
3269 // XXX: Have to use the old url since the cookies were stored using the
3270 // old percent-encoded url.
3271 String cookies = CookieManager.getInstance().getCookie(url);
3272
3273 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003274 values.put(Downloads.COLUMN_URI, uri.toString());
3275 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3276 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3277 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003278 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003279 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003280 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003281 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3282 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3283 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3284 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003285 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003286 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003287 }
3288 if (mimetype == null) {
3289 // We must have long pressed on a link or image to download it. We
3290 // are not sure of the mimetype in this case, so do a head request
3291 new FetchUrlMimeType(this).execute(values);
3292 } else {
3293 final Uri contentUri =
3294 getContentResolver().insert(Downloads.CONTENT_URI, values);
3295 viewDownloads(contentUri);
3296 }
3297
3298 }
3299
3300 /**
3301 * Resets the lock icon. This method is called when we start a new load and
3302 * know the url to be loaded.
3303 */
3304 private void resetLockIcon(String url) {
3305 // Save the lock-icon state (we revert to it if the load gets cancelled)
3306 saveLockIcon();
3307
3308 mLockIconType = LOCK_ICON_UNSECURE;
3309 if (URLUtil.isHttpsUrl(url)) {
3310 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003311 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003312 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3313 " reset lock icon to " + mLockIconType);
3314 }
3315 }
3316
3317 updateLockIconImage(LOCK_ICON_UNSECURE);
3318 }
3319
3320 /**
3321 * Resets the lock icon. This method is called when the icon needs to be
3322 * reset but we do not know whether we are loading a secure or not secure
3323 * page.
3324 */
3325 private void resetLockIcon() {
3326 // Save the lock-icon state (we revert to it if the load gets cancelled)
3327 saveLockIcon();
3328
3329 mLockIconType = LOCK_ICON_UNSECURE;
3330
Dave Bort31a6d1c2009-04-13 15:56:49 -07003331 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003332 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3333 " reset lock icon to " + mLockIconType);
3334 }
3335
3336 updateLockIconImage(LOCK_ICON_UNSECURE);
3337 }
3338
3339 /**
3340 * Updates the lock-icon image in the title-bar.
3341 */
3342 private void updateLockIconImage(int lockIconType) {
3343 Drawable d = null;
3344 if (lockIconType == LOCK_ICON_SECURE) {
3345 d = mSecLockIcon;
3346 } else if (lockIconType == LOCK_ICON_MIXED) {
3347 d = mMixLockIcon;
3348 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04003349 if (CUSTOM_BROWSER_BAR) {
3350 mTitleBar.setLock(d, getTopWindow());
3351 } else {
3352 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003353 }
3354 }
3355
3356 /**
3357 * Displays a page-info dialog.
3358 * @param tab The tab to show info about
3359 * @param fromShowSSLCertificateOnError The flag that indicates whether
3360 * this dialog was opened from the SSL-certificate-on-error dialog or
3361 * not. This is important, since we need to know whether to return to
3362 * the parent dialog or simply dismiss.
3363 */
3364 private void showPageInfo(final TabControl.Tab tab,
3365 final boolean fromShowSSLCertificateOnError) {
3366 final LayoutInflater factory = LayoutInflater
3367 .from(this);
3368
3369 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3370
3371 final WebView view = tab.getWebView();
3372
3373 String url = null;
3374 String title = null;
3375
3376 if (view == null) {
3377 url = tab.getUrl();
3378 title = tab.getTitle();
3379 } else if (view == mTabControl.getCurrentWebView()) {
3380 // Use the cached title and url if this is the current WebView
3381 url = mUrl;
3382 title = mTitle;
3383 } else {
3384 url = view.getUrl();
3385 title = view.getTitle();
3386 }
3387
3388 if (url == null) {
3389 url = "";
3390 }
3391 if (title == null) {
3392 title = "";
3393 }
3394
3395 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3396 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3397
3398 mPageInfoView = tab;
3399 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3400
3401 AlertDialog.Builder alertDialogBuilder =
3402 new AlertDialog.Builder(this)
3403 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3404 .setView(pageInfoView)
3405 .setPositiveButton(
3406 R.string.ok,
3407 new DialogInterface.OnClickListener() {
3408 public void onClick(DialogInterface dialog,
3409 int whichButton) {
3410 mPageInfoDialog = null;
3411 mPageInfoView = null;
3412 mPageInfoFromShowSSLCertificateOnError = null;
3413
3414 // if we came here from the SSL error dialog
3415 if (fromShowSSLCertificateOnError) {
3416 // go back to the SSL error dialog
3417 showSSLCertificateOnError(
3418 mSSLCertificateOnErrorView,
3419 mSSLCertificateOnErrorHandler,
3420 mSSLCertificateOnErrorError);
3421 }
3422 }
3423 })
3424 .setOnCancelListener(
3425 new DialogInterface.OnCancelListener() {
3426 public void onCancel(DialogInterface dialog) {
3427 mPageInfoDialog = null;
3428 mPageInfoView = null;
3429 mPageInfoFromShowSSLCertificateOnError = null;
3430
3431 // if we came here from the SSL error dialog
3432 if (fromShowSSLCertificateOnError) {
3433 // go back to the SSL error dialog
3434 showSSLCertificateOnError(
3435 mSSLCertificateOnErrorView,
3436 mSSLCertificateOnErrorHandler,
3437 mSSLCertificateOnErrorError);
3438 }
3439 }
3440 });
3441
3442 // if we have a main top-level page SSL certificate set or a certificate
3443 // error
3444 if (fromShowSSLCertificateOnError ||
3445 (view != null && view.getCertificate() != null)) {
3446 // add a 'View Certificate' button
3447 alertDialogBuilder.setNeutralButton(
3448 R.string.view_certificate,
3449 new DialogInterface.OnClickListener() {
3450 public void onClick(DialogInterface dialog,
3451 int whichButton) {
3452 mPageInfoDialog = null;
3453 mPageInfoView = null;
3454 mPageInfoFromShowSSLCertificateOnError = null;
3455
3456 // if we came here from the SSL error dialog
3457 if (fromShowSSLCertificateOnError) {
3458 // go back to the SSL error dialog
3459 showSSLCertificateOnError(
3460 mSSLCertificateOnErrorView,
3461 mSSLCertificateOnErrorHandler,
3462 mSSLCertificateOnErrorError);
3463 } else {
3464 // otherwise, display the top-most certificate from
3465 // the chain
3466 if (view.getCertificate() != null) {
3467 showSSLCertificate(tab);
3468 }
3469 }
3470 }
3471 });
3472 }
3473
3474 mPageInfoDialog = alertDialogBuilder.show();
3475 }
3476
3477 /**
3478 * Displays the main top-level page SSL certificate dialog
3479 * (accessible from the Page-Info dialog).
3480 * @param tab The tab to show certificate for.
3481 */
3482 private void showSSLCertificate(final TabControl.Tab tab) {
3483 final View certificateView =
3484 inflateCertificateView(tab.getWebView().getCertificate());
3485 if (certificateView == null) {
3486 return;
3487 }
3488
3489 LayoutInflater factory = LayoutInflater.from(this);
3490
3491 final LinearLayout placeholder =
3492 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3493
3494 LinearLayout ll = (LinearLayout) factory.inflate(
3495 R.layout.ssl_success, placeholder);
3496 ((TextView)ll.findViewById(R.id.success))
3497 .setText(R.string.ssl_certificate_is_valid);
3498
3499 mSSLCertificateView = tab;
3500 mSSLCertificateDialog =
3501 new AlertDialog.Builder(this)
3502 .setTitle(R.string.ssl_certificate).setIcon(
3503 R.drawable.ic_dialog_browser_certificate_secure)
3504 .setView(certificateView)
3505 .setPositiveButton(R.string.ok,
3506 new DialogInterface.OnClickListener() {
3507 public void onClick(DialogInterface dialog,
3508 int whichButton) {
3509 mSSLCertificateDialog = null;
3510 mSSLCertificateView = null;
3511
3512 showPageInfo(tab, false);
3513 }
3514 })
3515 .setOnCancelListener(
3516 new DialogInterface.OnCancelListener() {
3517 public void onCancel(DialogInterface dialog) {
3518 mSSLCertificateDialog = null;
3519 mSSLCertificateView = null;
3520
3521 showPageInfo(tab, false);
3522 }
3523 })
3524 .show();
3525 }
3526
3527 /**
3528 * Displays the SSL error certificate dialog.
3529 * @param view The target web-view.
3530 * @param handler The SSL error handler responsible for cancelling the
3531 * connection that resulted in an SSL error or proceeding per user request.
3532 * @param error The SSL error object.
3533 */
3534 private void showSSLCertificateOnError(
3535 final WebView view, final SslErrorHandler handler, final SslError error) {
3536
3537 final View certificateView =
3538 inflateCertificateView(error.getCertificate());
3539 if (certificateView == null) {
3540 return;
3541 }
3542
3543 LayoutInflater factory = LayoutInflater.from(this);
3544
3545 final LinearLayout placeholder =
3546 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3547
3548 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3549 LinearLayout ll = (LinearLayout)factory
3550 .inflate(R.layout.ssl_warning, placeholder);
3551 ((TextView)ll.findViewById(R.id.warning))
3552 .setText(R.string.ssl_untrusted);
3553 }
3554
3555 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3556 LinearLayout ll = (LinearLayout)factory
3557 .inflate(R.layout.ssl_warning, placeholder);
3558 ((TextView)ll.findViewById(R.id.warning))
3559 .setText(R.string.ssl_mismatch);
3560 }
3561
3562 if (error.hasError(SslError.SSL_EXPIRED)) {
3563 LinearLayout ll = (LinearLayout)factory
3564 .inflate(R.layout.ssl_warning, placeholder);
3565 ((TextView)ll.findViewById(R.id.warning))
3566 .setText(R.string.ssl_expired);
3567 }
3568
3569 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3570 LinearLayout ll = (LinearLayout)factory
3571 .inflate(R.layout.ssl_warning, placeholder);
3572 ((TextView)ll.findViewById(R.id.warning))
3573 .setText(R.string.ssl_not_yet_valid);
3574 }
3575
3576 mSSLCertificateOnErrorHandler = handler;
3577 mSSLCertificateOnErrorView = view;
3578 mSSLCertificateOnErrorError = error;
3579 mSSLCertificateOnErrorDialog =
3580 new AlertDialog.Builder(this)
3581 .setTitle(R.string.ssl_certificate).setIcon(
3582 R.drawable.ic_dialog_browser_certificate_partially_secure)
3583 .setView(certificateView)
3584 .setPositiveButton(R.string.ok,
3585 new DialogInterface.OnClickListener() {
3586 public void onClick(DialogInterface dialog,
3587 int whichButton) {
3588 mSSLCertificateOnErrorDialog = null;
3589 mSSLCertificateOnErrorView = null;
3590 mSSLCertificateOnErrorHandler = null;
3591 mSSLCertificateOnErrorError = null;
3592
3593 mWebViewClient.onReceivedSslError(
3594 view, handler, error);
3595 }
3596 })
3597 .setNeutralButton(R.string.page_info_view,
3598 new DialogInterface.OnClickListener() {
3599 public void onClick(DialogInterface dialog,
3600 int whichButton) {
3601 mSSLCertificateOnErrorDialog = null;
3602
3603 // do not clear the dialog state: we will
3604 // need to show the dialog again once the
3605 // user is done exploring the page-info details
3606
3607 showPageInfo(mTabControl.getTabFromView(view),
3608 true);
3609 }
3610 })
3611 .setOnCancelListener(
3612 new DialogInterface.OnCancelListener() {
3613 public void onCancel(DialogInterface dialog) {
3614 mSSLCertificateOnErrorDialog = null;
3615 mSSLCertificateOnErrorView = null;
3616 mSSLCertificateOnErrorHandler = null;
3617 mSSLCertificateOnErrorError = null;
3618
3619 mWebViewClient.onReceivedSslError(
3620 view, handler, error);
3621 }
3622 })
3623 .show();
3624 }
3625
3626 /**
3627 * Inflates the SSL certificate view (helper method).
3628 * @param certificate The SSL certificate.
3629 * @return The resultant certificate view with issued-to, issued-by,
3630 * issued-on, expires-on, and possibly other fields set.
3631 * If the input certificate is null, returns null.
3632 */
3633 private View inflateCertificateView(SslCertificate certificate) {
3634 if (certificate == null) {
3635 return null;
3636 }
3637
3638 LayoutInflater factory = LayoutInflater.from(this);
3639
3640 View certificateView = factory.inflate(
3641 R.layout.ssl_certificate, null);
3642
3643 // issued to:
3644 SslCertificate.DName issuedTo = certificate.getIssuedTo();
3645 if (issuedTo != null) {
3646 ((TextView) certificateView.findViewById(R.id.to_common))
3647 .setText(issuedTo.getCName());
3648 ((TextView) certificateView.findViewById(R.id.to_org))
3649 .setText(issuedTo.getOName());
3650 ((TextView) certificateView.findViewById(R.id.to_org_unit))
3651 .setText(issuedTo.getUName());
3652 }
3653
3654 // issued by:
3655 SslCertificate.DName issuedBy = certificate.getIssuedBy();
3656 if (issuedBy != null) {
3657 ((TextView) certificateView.findViewById(R.id.by_common))
3658 .setText(issuedBy.getCName());
3659 ((TextView) certificateView.findViewById(R.id.by_org))
3660 .setText(issuedBy.getOName());
3661 ((TextView) certificateView.findViewById(R.id.by_org_unit))
3662 .setText(issuedBy.getUName());
3663 }
3664
3665 // issued on:
3666 String issuedOn = reformatCertificateDate(
3667 certificate.getValidNotBefore());
3668 ((TextView) certificateView.findViewById(R.id.issued_on))
3669 .setText(issuedOn);
3670
3671 // expires on:
3672 String expiresOn = reformatCertificateDate(
3673 certificate.getValidNotAfter());
3674 ((TextView) certificateView.findViewById(R.id.expires_on))
3675 .setText(expiresOn);
3676
3677 return certificateView;
3678 }
3679
3680 /**
3681 * Re-formats the certificate date (Date.toString()) string to
3682 * a properly localized date string.
3683 * @return Properly localized version of the certificate date string and
3684 * the original certificate date string if fails to localize.
3685 * If the original string is null, returns an empty string "".
3686 */
3687 private String reformatCertificateDate(String certificateDate) {
3688 String reformattedDate = null;
3689
3690 if (certificateDate != null) {
3691 Date date = null;
3692 try {
3693 date = java.text.DateFormat.getInstance().parse(certificateDate);
3694 } catch (ParseException e) {
3695 date = null;
3696 }
3697
3698 if (date != null) {
3699 reformattedDate =
3700 DateFormat.getDateFormat(this).format(date);
3701 }
3702 }
3703
3704 return reformattedDate != null ? reformattedDate :
3705 (certificateDate != null ? certificateDate : "");
3706 }
3707
3708 /**
3709 * Displays an http-authentication dialog.
3710 */
3711 private void showHttpAuthentication(final HttpAuthHandler handler,
3712 final String host, final String realm, final String title,
3713 final String name, final String password, int focusId) {
3714 LayoutInflater factory = LayoutInflater.from(this);
3715 final View v = factory
3716 .inflate(R.layout.http_authentication, null);
3717 if (name != null) {
3718 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3719 }
3720 if (password != null) {
3721 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3722 }
3723
3724 String titleText = title;
3725 if (titleText == null) {
3726 titleText = getText(R.string.sign_in_to).toString().replace(
3727 "%s1", host).replace("%s2", realm);
3728 }
3729
3730 mHttpAuthHandler = handler;
3731 AlertDialog dialog = new AlertDialog.Builder(this)
3732 .setTitle(titleText)
3733 .setIcon(android.R.drawable.ic_dialog_alert)
3734 .setView(v)
3735 .setPositiveButton(R.string.action,
3736 new DialogInterface.OnClickListener() {
3737 public void onClick(DialogInterface dialog,
3738 int whichButton) {
3739 String nm = ((EditText) v
3740 .findViewById(R.id.username_edit))
3741 .getText().toString();
3742 String pw = ((EditText) v
3743 .findViewById(R.id.password_edit))
3744 .getText().toString();
3745 BrowserActivity.this.setHttpAuthUsernamePassword
3746 (host, realm, nm, pw);
3747 handler.proceed(nm, pw);
3748 mHttpAuthenticationDialog = null;
3749 mHttpAuthHandler = null;
3750 }})
3751 .setNegativeButton(R.string.cancel,
3752 new DialogInterface.OnClickListener() {
3753 public void onClick(DialogInterface dialog,
3754 int whichButton) {
3755 handler.cancel();
3756 BrowserActivity.this.resetTitleAndRevertLockIcon();
3757 mHttpAuthenticationDialog = null;
3758 mHttpAuthHandler = null;
3759 }})
3760 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3761 public void onCancel(DialogInterface dialog) {
3762 handler.cancel();
3763 BrowserActivity.this.resetTitleAndRevertLockIcon();
3764 mHttpAuthenticationDialog = null;
3765 mHttpAuthHandler = null;
3766 }})
3767 .create();
3768 // Make the IME appear when the dialog is displayed if applicable.
3769 dialog.getWindow().setSoftInputMode(
3770 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3771 dialog.show();
3772 if (focusId != 0) {
3773 dialog.findViewById(focusId).requestFocus();
3774 } else {
3775 v.findViewById(R.id.username_edit).requestFocus();
3776 }
3777 mHttpAuthenticationDialog = dialog;
3778 }
3779
3780 public int getProgress() {
3781 WebView w = mTabControl.getCurrentWebView();
3782 if (w != null) {
3783 return w.getProgress();
3784 } else {
3785 return 100;
3786 }
3787 }
3788
3789 /**
3790 * Set HTTP authentication password.
3791 *
3792 * @param host The host for the password
3793 * @param realm The realm for the password
3794 * @param username The username for the password. If it is null, it means
3795 * password can't be saved.
3796 * @param password The password
3797 */
3798 public void setHttpAuthUsernamePassword(String host, String realm,
3799 String username,
3800 String password) {
3801 WebView w = mTabControl.getCurrentWebView();
3802 if (w != null) {
3803 w.setHttpAuthUsernamePassword(host, realm, username, password);
3804 }
3805 }
3806
3807 /**
3808 * connectivity manager says net has come or gone... inform the user
3809 * @param up true if net has come up, false if net has gone down
3810 */
3811 public void onNetworkToggle(boolean up) {
3812 if (up == mIsNetworkUp) {
3813 return;
3814 } else if (up) {
3815 mIsNetworkUp = true;
3816 if (mAlertDialog != null) {
3817 mAlertDialog.cancel();
3818 mAlertDialog = null;
3819 }
3820 } else {
3821 mIsNetworkUp = false;
3822 if (mInLoad && mAlertDialog == null) {
3823 mAlertDialog = new AlertDialog.Builder(this)
3824 .setTitle(R.string.loadSuspendedTitle)
3825 .setMessage(R.string.loadSuspended)
3826 .setPositiveButton(R.string.ok, null)
3827 .show();
3828 }
3829 }
3830 WebView w = mTabControl.getCurrentWebView();
3831 if (w != null) {
3832 w.setNetworkAvailable(up);
3833 }
3834 }
3835
3836 @Override
3837 protected void onActivityResult(int requestCode, int resultCode,
3838 Intent intent) {
3839 switch (requestCode) {
3840 case COMBO_PAGE:
3841 if (resultCode == RESULT_OK && intent != null) {
3842 String data = intent.getAction();
3843 Bundle extras = intent.getExtras();
3844 if (extras != null && extras.getBoolean("new_window", false)) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003845 final TabControl.Tab newTab = openTab(data);
3846 if (mSettings.openInBackground() &&
Leon Scroggins1f005d32009-08-10 17:36:42 -04003847 newTab != null) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003848 mTabControl.populatePickerData(newTab);
3849 mTabControl.setCurrentTab(newTab);
Leon Scroggins1f005d32009-08-10 17:36:42 -04003850 int newIndex = mTabControl.getCurrentIndex();
3851 if (CUSTOM_BROWSER_BAR) {
3852 mTitleBar.setCurrentTab(newIndex);
3853 }
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003854 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003855 } else {
3856 final TabControl.Tab currentTab =
3857 mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04003858 dismissSubWindow(currentTab);
3859 if (data != null && data.length() != 0) {
3860 getTopWindow().loadUrl(data);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003861 }
3862 }
Leon Scroggins39362092009-08-18 19:47:09 -04003863/*
3864 FIXME: Removing this breaks the behavior of pressing BACK from
3865 the Go page resulting in the window being closed. However, it
3866 needs to be removed so that the user can use the Search bar to
3867 enter a URL. Further, the Go behavior is going to change
3868 drastically, so this behavior may not last anyway.
Leon Scroggins160a7e72009-08-14 18:28:01 -04003869 } else if (resultCode == RESULT_CANCELED
3870 && mCancelGoPageMeansClose) {
3871 if (mTabControl.getTabCount() == 1) {
3872 // finish the Browser. When the Browser opens up again,
3873 // we will go through onCreate and once again open up
3874 // the Go page.
3875 finish();
3876 return;
3877 }
3878 closeCurrentWindow();
Leon Scroggins39362092009-08-18 19:47:09 -04003879*/
The Android Open Source Project0c908882009-03-03 19:32:16 -08003880 }
3881 break;
3882 default:
3883 break;
3884 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003885 mCancelGoPageMeansClose = false;
3886 if (getTopWindow() != null) {
3887 getTopWindow().requestFocus();
3888 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003889 }
3890
3891 /*
3892 * This method is called as a result of the user selecting the options
3893 * menu to see the download window, or when a download changes state. It
3894 * shows the download window ontop of the current window.
3895 */
3896 /* package */ void viewDownloads(Uri downloadRecord) {
3897 Intent intent = new Intent(this,
3898 BrowserDownloadPage.class);
3899 intent.setData(downloadRecord);
3900 startActivityForResult(intent, this.DOWNLOAD_PAGE);
3901
3902 }
3903
Leon Scroggins160a7e72009-08-14 18:28:01 -04003904 // True if canceling the "Go" screen should result in closing the current
3905 // window/browser.
3906 private boolean mCancelGoPageMeansClose;
3907
3908 /**
3909 * Open the Go page.
3910 * @param startWithHistory If true, open starting on the history tab.
3911 * Otherwise, start with the bookmarks tab.
3912 * @param cancelGoPageMeansClose Set to true if this came from a new tab, or
3913 * from the only tab, and canceling means to
3914 * close the tab (and possibly the browser)
3915 */
3916 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory,
3917 boolean cancelGoPageMeansClose) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003918 WebView current = mTabControl.getCurrentWebView();
3919 if (current == null) {
3920 return;
3921 }
3922 Intent intent = new Intent(this,
3923 CombinedBookmarkHistoryActivity.class);
3924 String title = current.getTitle();
3925 String url = current.getUrl();
3926 // Just in case the user opens bookmarks before a page finishes loading
3927 // so the current history item, and therefore the page, is null.
3928 if (null == url) {
3929 url = mLastEnteredUrl;
3930 // This can happen.
3931 if (null == url) {
3932 url = mSettings.getHomePage();
3933 }
3934 }
3935 // In case the web page has not yet received its associated title.
3936 if (title == null) {
3937 title = url;
3938 }
3939 intent.putExtra("title", title);
3940 intent.putExtra("url", url);
Leon Scroggins190095d2009-08-17 17:01:38 -04003941 // If this is opening in a new window, then disable opening in a
3942 // (different) new window. Also disable it if we have maxed out the
3943 // windows.
3944 intent.putExtra("disable_new_window", cancelGoPageMeansClose
3945 || mTabControl.getTabCount() >= TabControl.MAX_TABS);
Patrick Scott3918d442009-08-04 13:22:29 -04003946 intent.putExtra("touch_icon_url", current.getTouchIconUrl());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003947 if (startWithHistory) {
3948 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
3949 CombinedBookmarkHistoryActivity.HISTORY_TAB);
3950 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003951 mCancelGoPageMeansClose = cancelGoPageMeansClose;
The Android Open Source Project0c908882009-03-03 19:32:16 -08003952 startActivityForResult(intent, COMBO_PAGE);
3953 }
3954
3955 // Called when loading from context menu or LOAD_URL message
3956 private void loadURL(WebView view, String url) {
3957 // In case the user enters nothing.
3958 if (url != null && url.length() != 0 && view != null) {
3959 url = smartUrlFilter(url);
3960 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
3961 view.loadUrl(url);
3962 }
3963 }
3964 }
3965
The Android Open Source Project0c908882009-03-03 19:32:16 -08003966 private String smartUrlFilter(Uri inUri) {
3967 if (inUri != null) {
3968 return smartUrlFilter(inUri.toString());
3969 }
3970 return null;
3971 }
3972
3973
3974 // get window count
3975
3976 int getWindowCount(){
3977 if(mTabControl != null){
3978 return mTabControl.getTabCount();
3979 }
3980 return 0;
3981 }
3982
Feng Qianb34f87a2009-03-24 21:27:26 -07003983 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08003984 "(?i)" + // switch on case insensitive matching
3985 "(" + // begin group for schema
3986 "(?:http|https|file):\\/\\/" +
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07003987 "|(?:inline|data|about|content|javascript):" +
The Android Open Source Project0c908882009-03-03 19:32:16 -08003988 ")" +
3989 "(.*)" );
3990
3991 /**
3992 * Attempts to determine whether user input is a URL or search
3993 * terms. Anything with a space is passed to search.
3994 *
3995 * Converts to lowercase any mistakenly uppercased schema (i.e.,
3996 * "Http://" converts to "http://"
3997 *
3998 * @return Original or modified URL
3999 *
4000 */
4001 String smartUrlFilter(String url) {
4002
4003 String inUrl = url.trim();
4004 boolean hasSpace = inUrl.indexOf(' ') != -1;
4005
4006 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4007 if (matcher.matches()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004008 // force scheme to lowercase
4009 String scheme = matcher.group(1);
4010 String lcScheme = scheme.toLowerCase();
4011 if (!lcScheme.equals(scheme)) {
Mitsuru Oshima123ecfb2009-05-18 19:11:14 -07004012 inUrl = lcScheme + matcher.group(2);
4013 }
4014 if (hasSpace) {
4015 inUrl = inUrl.replace(" ", "%20");
The Android Open Source Project0c908882009-03-03 19:32:16 -08004016 }
4017 return inUrl;
4018 }
4019 if (hasSpace) {
Satish Sampath565505b2009-05-29 15:37:27 +01004020 // FIXME: Is this the correct place to add to searches?
4021 // what if someone else calls this function?
4022 int shortcut = parseUrlShortcut(inUrl);
4023 if (shortcut != SHORTCUT_INVALID) {
4024 Browser.addSearchUrl(mResolver, inUrl);
4025 String query = inUrl.substring(2);
4026 switch (shortcut) {
4027 case SHORTCUT_GOOGLE_SEARCH:
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004028 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
Satish Sampath565505b2009-05-29 15:37:27 +01004029 case SHORTCUT_WIKIPEDIA_SEARCH:
4030 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4031 case SHORTCUT_DICTIONARY_SEARCH:
4032 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4033 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
The Android Open Source Project0c908882009-03-03 19:32:16 -08004034 // FIXME: we need location in this case
Satish Sampath565505b2009-05-29 15:37:27 +01004035 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004036 }
4037 }
4038 } else {
4039 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4040 return URLUtil.guessUrl(inUrl);
4041 }
4042 }
4043
4044 Browser.addSearchUrl(mResolver, inUrl);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004045 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004046 }
4047
Ben Murdochbff2d602009-07-01 20:19:05 +01004048 /* package */ void setShouldShowErrorConsole(boolean flag) {
4049 if (flag == mShouldShowErrorConsole) {
4050 // Nothing to do.
4051 return;
4052 }
4053
4054 mShouldShowErrorConsole = flag;
4055
4056 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4057
4058 if (flag) {
4059 // Setting the show state of the console will cause it's the layout to be inflated.
4060 if (errorConsole.numberOfErrors() > 0) {
4061 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4062 } else {
4063 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4064 }
4065
4066 // Now we can add it to the main view.
4067 mErrorConsoleContainer.addView(errorConsole,
4068 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4069 ViewGroup.LayoutParams.WRAP_CONTENT));
4070 } else {
4071 mErrorConsoleContainer.removeView(errorConsole);
4072 }
4073
4074 }
4075
The Android Open Source Project0c908882009-03-03 19:32:16 -08004076 private final static int LOCK_ICON_UNSECURE = 0;
4077 private final static int LOCK_ICON_SECURE = 1;
4078 private final static int LOCK_ICON_MIXED = 2;
4079
4080 private int mLockIconType = LOCK_ICON_UNSECURE;
4081 private int mPrevLockType = LOCK_ICON_UNSECURE;
4082
4083 private BrowserSettings mSettings;
4084 private TabControl mTabControl;
4085 private ContentResolver mResolver;
4086 private FrameLayout mContentView;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004087 private View mCustomView;
4088 private FrameLayout mCustomViewContainer;
Andrei Popescuc9b55562009-07-07 10:51:15 +01004089 private WebChromeClient.CustomViewCallback mCustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004090
4091 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4092 // view, we should rewrite this.
4093 private int mCurrentMenuState = 0;
4094 private int mMenuState = R.id.MAIN_MENU;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004095 private int mOldMenuState = EMPTY_MENU;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004096 private static final int EMPTY_MENU = -1;
4097 private Menu mMenu;
4098
4099 private FindDialog mFindDialog;
4100 // Used to prevent chording to result in firing two shortcuts immediately
4101 // one after another. Fixes bug 1211714.
4102 boolean mCanChord;
4103
4104 private boolean mInLoad;
4105 private boolean mIsNetworkUp;
4106
4107 private boolean mPageStarted;
4108 private boolean mActivityInPause = true;
4109
4110 private boolean mMenuIsDown;
4111
4112 private final KeyTracker mKeyTracker = new KeyTracker(this);
4113
4114 // As trackball doesn't send repeat down, we have to track it ourselves
4115 private boolean mTrackTrackball;
4116
4117 private static boolean mInTrace;
4118
4119 // Performance probe
4120 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4121 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4122 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4123 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4124 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4125 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4126 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4127 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4128 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4129 };
4130
4131 private long mStart;
4132 private long mProcessStart;
4133 private long mUserStart;
4134 private long mSystemStart;
4135 private long mIdleStart;
4136 private long mIrqStart;
4137
4138 private long mUiStart;
4139
4140 private Drawable mMixLockIcon;
4141 private Drawable mSecLockIcon;
4142 private Drawable mGenericFavicon;
4143
4144 /* hold a ref so we can auto-cancel if necessary */
4145 private AlertDialog mAlertDialog;
4146
4147 // Wait for credentials before loading google.com
4148 private ProgressDialog mCredsDlg;
4149
4150 // The up-to-date URL and title (these can be different from those stored
4151 // in WebView, since it takes some time for the information in WebView to
4152 // get updated)
4153 private String mUrl;
4154 private String mTitle;
4155
4156 // As PageInfo has different style for landscape / portrait, we have
4157 // to re-open it when configuration changed
4158 private AlertDialog mPageInfoDialog;
4159 private TabControl.Tab mPageInfoView;
4160 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4161 // dialog, we should not just dismiss it, but should get back to the
4162 // SSL-certificate-on-error dialog. This flag is used to store this state
4163 private Boolean mPageInfoFromShowSSLCertificateOnError;
4164
4165 // as SSLCertificateOnError has different style for landscape / portrait,
4166 // we have to re-open it when configuration changed
4167 private AlertDialog mSSLCertificateOnErrorDialog;
4168 private WebView mSSLCertificateOnErrorView;
4169 private SslErrorHandler mSSLCertificateOnErrorHandler;
4170 private SslError mSSLCertificateOnErrorError;
4171
4172 // as SSLCertificate has different style for landscape / portrait, we
4173 // have to re-open it when configuration changed
4174 private AlertDialog mSSLCertificateDialog;
4175 private TabControl.Tab mSSLCertificateView;
4176
4177 // as HttpAuthentication has different style for landscape / portrait, we
4178 // have to re-open it when configuration changed
4179 private AlertDialog mHttpAuthenticationDialog;
4180 private HttpAuthHandler mHttpAuthHandler;
4181
4182 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4183 new FrameLayout.LayoutParams(
4184 ViewGroup.LayoutParams.FILL_PARENT,
4185 ViewGroup.LayoutParams.FILL_PARENT);
Andrei Popescuadc008d2009-06-26 14:11:30 +01004186 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4187 new FrameLayout.LayoutParams(
4188 ViewGroup.LayoutParams.FILL_PARENT,
4189 ViewGroup.LayoutParams.FILL_PARENT,
4190 Gravity.CENTER);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004191 // Google search
4192 final static String QuickSearch_G = "http://www.google.com/m?q=%s";
The Android Open Source Project0c908882009-03-03 19:32:16 -08004193 // Wikipedia search
4194 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4195 // Dictionary search
4196 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4197 // Google Mobile Local search
4198 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4199
4200 final static String QUERY_PLACE_HOLDER = "%s";
4201
4202 // "source" parameter for Google search through search key
4203 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4204 // "source" parameter for Google search through goto menu
4205 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4206 // "source" parameter for Google search through simplily type
4207 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4208 // "source" parameter for Google search suggested by the browser
4209 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4210 // "source" parameter for Google search from unknown source
4211 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4212
4213 private final static String LOGTAG = "browser";
4214
The Android Open Source Project0c908882009-03-03 19:32:16 -08004215 private String mLastEnteredUrl;
4216
4217 private PowerManager.WakeLock mWakeLock;
4218 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4219
4220 private Toast mStopToast;
4221
Leon Scroggins1f005d32009-08-10 17:36:42 -04004222 private TitleBarSet mTitleBar;
Leon Scroggins81db3662009-06-04 17:45:11 -04004223
Ben Murdochbff2d602009-07-01 20:19:05 +01004224 private LinearLayout mErrorConsoleContainer = null;
4225 private boolean mShouldShowErrorConsole = false;
4226
The Android Open Source Project0c908882009-03-03 19:32:16 -08004227 // As the ids are dynamically created, we can't guarantee that they will
4228 // be in sequence, so this static array maps ids to a window number.
4229 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4230 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4231 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4232 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4233
4234 // monitor platform changes
4235 private IntentFilter mNetworkStateChangedFilter;
4236 private BroadcastReceiver mNetworkStateIntentReceiver;
4237
Grace Klobab4da0ad2009-05-14 14:45:40 -07004238 private BroadcastReceiver mPackageInstallationReceiver;
4239
The Android Open Source Project0c908882009-03-03 19:32:16 -08004240 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004241 final static int COMBO_PAGE = 1;
4242 final static int DOWNLOAD_PAGE = 2;
4243 final static int PREFERENCES_PAGE = 3;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004244
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004245 /**
4246 * A UrlData class to abstract how the content will be set to WebView.
4247 * This base class uses loadUrl to show the content.
4248 */
4249 private static class UrlData {
4250 String mUrl;
Grace Kloba60e095c2009-06-16 11:50:55 -07004251 byte[] mPostData;
4252
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004253 UrlData(String url) {
4254 this.mUrl = url;
4255 }
Grace Kloba60e095c2009-06-16 11:50:55 -07004256
4257 void setPostData(byte[] postData) {
4258 mPostData = postData;
4259 }
4260
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004261 boolean isEmpty() {
4262 return mUrl == null || mUrl.length() == 0;
4263 }
4264
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004265 public void loadIn(WebView webView) {
Grace Kloba60e095c2009-06-16 11:50:55 -07004266 if (mPostData != null) {
4267 webView.postUrl(mUrl, mPostData);
4268 } else {
4269 webView.loadUrl(mUrl);
4270 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004271 }
4272 };
4273
4274 /**
4275 * A subclass of UrlData class that can display inlined content using
4276 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4277 */
4278 private static class InlinedUrlData extends UrlData {
4279 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4280 super(failUrl);
4281 mInlined = inlined;
4282 mMimeType = mimeType;
4283 mEncoding = encoding;
4284 }
4285 String mMimeType;
4286 String mInlined;
4287 String mEncoding;
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004288 @Override
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004289 boolean isEmpty() {
Ben Murdochbff2d602009-07-01 20:19:05 +01004290 return mInlined == null || mInlined.length() == 0 || super.isEmpty();
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004291 }
4292
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004293 @Override
4294 public void loadIn(WebView webView) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004295 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4296 }
4297 }
4298
Leon Scroggins1f005d32009-08-10 17:36:42 -04004299 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004300}