blob: 818056ce8724c1b4adba976cb594a5659ca20209 [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.
Andrei Popescu79e82b72009-07-27 12:01:59 +01003091 * @param totalUsedQuota is the sum of all origins' quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003092 * @param quotaUpdater The callback to run when a decision to allow or
3093 * deny quota has been made. Don't forget to call this!
3094 */
3095 @Override
3096 public void onExceededDatabaseQuota(String url,
Andrei Popescu79e82b72009-07-27 12:01:59 +01003097 String databaseIdentifier, long currentQuota, long totalUsedQuota,
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003098 WebStorage.QuotaUpdater quotaUpdater) {
Andrei Popescu79e82b72009-07-27 12:01:59 +01003099 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
3100 url, databaseIdentifier, currentQuota, totalUsedQuota,
3101 quotaUpdater);
3102 }
3103
3104 /**
3105 * The Application Cache has exceeded its max size.
3106 * @param spaceNeeded is the amount of disk space that would be needed
3107 * in order for the last appcache operation to succeed.
3108 * @param totalUsedQuota is the sum of all origins' quota.
3109 * @param quotaUpdater A callback to inform the WebCore thread that a new
3110 * app cache size is available. This callback must always be executed at
3111 * some point to ensure that the sleeping WebCore thread is woken up.
3112 */
3113 @Override
3114 public void onReachedMaxAppCacheSize(long spaceNeeded,
3115 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3116 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3117 spaceNeeded, totalUsedQuota, quotaUpdater);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003118 }
Ben Murdoch7db26342009-06-03 18:21:19 +01003119
Steve Block2bc69912009-07-30 14:45:13 +01003120 /**
3121 * Instructs the browser to show a prompt to ask the user to set the
3122 * Geolocation permission state for the specified origin.
3123 * @param origin The origin for which Geolocation permissions are
3124 * requested.
3125 * @param callback The callback to call once the user has set the
3126 * Geolocation permission state.
3127 */
3128 @Override
3129 public void onGeolocationPermissionsShowPrompt(String origin,
3130 GeolocationPermissions.Callback callback) {
3131 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3132 origin, callback);
3133 }
3134
3135 /**
3136 * Instructs the browser to hide the Geolocation permissions prompt.
3137 */
3138 @Override
3139 public void onGeolocationPermissionsHidePrompt() {
3140 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3141 }
3142
Ben Murdoch7db26342009-06-03 18:21:19 +01003143 /* Adds a JavaScript error message to the system log.
3144 * @param message The error message to report.
3145 * @param lineNumber The line number of the error.
3146 * @param sourceID The name of the source file that caused the error.
3147 */
3148 @Override
3149 public void addMessageToConsole(String message, int lineNumber, String sourceID) {
Ben Murdochbff2d602009-07-01 20:19:05 +01003150 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3151 errorConsole.addErrorMessage(message, sourceID, lineNumber);
3152 if (mShouldShowErrorConsole &&
3153 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3154 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3155 }
3156 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
Ben Murdoch7db26342009-06-03 18:21:19 +01003157 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003158 };
3159
3160 /**
3161 * Notify the host application a download should be done, or that
3162 * the data should be streamed if a streaming viewer is available.
3163 * @param url The full url to the content that should be downloaded
3164 * @param contentDisposition Content-disposition http header, if
3165 * present.
3166 * @param mimetype The mimetype of the content reported by the server
3167 * @param contentLength The file size reported by the server
3168 */
3169 public void onDownloadStart(String url, String userAgent,
3170 String contentDisposition, String mimetype, long contentLength) {
3171 // if we're dealing wih A/V content that's not explicitly marked
3172 // for download, check if it's streamable.
3173 if (contentDisposition == null
3174 || !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) {
3175 // query the package manager to see if there's a registered handler
3176 // that matches.
3177 Intent intent = new Intent(Intent.ACTION_VIEW);
3178 intent.setDataAndType(Uri.parse(url), mimetype);
3179 if (getPackageManager().resolveActivity(intent,
3180 PackageManager.MATCH_DEFAULT_ONLY) != null) {
3181 // someone knows how to handle this mime type with this scheme, don't download.
3182 try {
3183 startActivity(intent);
3184 return;
3185 } catch (ActivityNotFoundException ex) {
Dave Bort31a6d1c2009-04-13 15:56:49 -07003186 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003187 Log.d(LOGTAG, "activity not found for " + mimetype
3188 + " over " + Uri.parse(url).getScheme(), ex);
3189 }
3190 // Best behavior is to fall back to a download in this case
3191 }
3192 }
3193 }
3194 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3195 }
3196
3197 /**
3198 * Notify the host application a download should be done, even if there
3199 * is a streaming viewer available for thise type.
3200 * @param url The full url to the content that should be downloaded
3201 * @param contentDisposition Content-disposition http header, if
3202 * present.
3203 * @param mimetype The mimetype of the content reported by the server
3204 * @param contentLength The file size reported by the server
3205 */
3206 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3207 String contentDisposition, String mimetype, long contentLength) {
3208
3209 String filename = URLUtil.guessFileName(url,
3210 contentDisposition, mimetype);
3211
3212 // Check to see if we have an SDCard
3213 String status = Environment.getExternalStorageState();
3214 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3215 int title;
3216 String msg;
3217
3218 // Check to see if the SDCard is busy, same as the music app
3219 if (status.equals(Environment.MEDIA_SHARED)) {
3220 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3221 title = R.string.download_sdcard_busy_dlg_title;
3222 } else {
3223 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3224 title = R.string.download_no_sdcard_dlg_title;
3225 }
3226
3227 new AlertDialog.Builder(this)
3228 .setTitle(title)
3229 .setIcon(android.R.drawable.ic_dialog_alert)
3230 .setMessage(msg)
3231 .setPositiveButton(R.string.ok, null)
3232 .show();
3233 return;
3234 }
3235
3236 // java.net.URI is a lot stricter than KURL so we have to undo
3237 // KURL's percent-encoding and redo the encoding using java.net.URI.
3238 URI uri = null;
3239 try {
3240 // Undo the percent-encoding that KURL may have done.
3241 String newUrl = new String(URLUtil.decode(url.getBytes()));
3242 // Parse the url into pieces
3243 WebAddress w = new WebAddress(newUrl);
3244 String frag = null;
3245 String query = null;
3246 String path = w.mPath;
3247 // Break the path into path, query, and fragment
3248 if (path.length() > 0) {
3249 // Strip the fragment
3250 int idx = path.lastIndexOf('#');
3251 if (idx != -1) {
3252 frag = path.substring(idx + 1);
3253 path = path.substring(0, idx);
3254 }
3255 idx = path.lastIndexOf('?');
3256 if (idx != -1) {
3257 query = path.substring(idx + 1);
3258 path = path.substring(0, idx);
3259 }
3260 }
3261 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3262 query, frag);
3263 } catch (Exception e) {
3264 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3265 return;
3266 }
3267
3268 // XXX: Have to use the old url since the cookies were stored using the
3269 // old percent-encoded url.
3270 String cookies = CookieManager.getInstance().getCookie(url);
3271
3272 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003273 values.put(Downloads.COLUMN_URI, uri.toString());
3274 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3275 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3276 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003277 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003278 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003279 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003280 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3281 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3282 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3283 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003284 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003285 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003286 }
3287 if (mimetype == null) {
3288 // We must have long pressed on a link or image to download it. We
3289 // are not sure of the mimetype in this case, so do a head request
3290 new FetchUrlMimeType(this).execute(values);
3291 } else {
3292 final Uri contentUri =
3293 getContentResolver().insert(Downloads.CONTENT_URI, values);
3294 viewDownloads(contentUri);
3295 }
3296
3297 }
3298
3299 /**
3300 * Resets the lock icon. This method is called when we start a new load and
3301 * know the url to be loaded.
3302 */
3303 private void resetLockIcon(String url) {
3304 // Save the lock-icon state (we revert to it if the load gets cancelled)
3305 saveLockIcon();
3306
3307 mLockIconType = LOCK_ICON_UNSECURE;
3308 if (URLUtil.isHttpsUrl(url)) {
3309 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003310 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003311 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3312 " reset lock icon to " + mLockIconType);
3313 }
3314 }
3315
3316 updateLockIconImage(LOCK_ICON_UNSECURE);
3317 }
3318
3319 /**
3320 * Resets the lock icon. This method is called when the icon needs to be
3321 * reset but we do not know whether we are loading a secure or not secure
3322 * page.
3323 */
3324 private void resetLockIcon() {
3325 // Save the lock-icon state (we revert to it if the load gets cancelled)
3326 saveLockIcon();
3327
3328 mLockIconType = LOCK_ICON_UNSECURE;
3329
Dave Bort31a6d1c2009-04-13 15:56:49 -07003330 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003331 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3332 " reset lock icon to " + mLockIconType);
3333 }
3334
3335 updateLockIconImage(LOCK_ICON_UNSECURE);
3336 }
3337
3338 /**
3339 * Updates the lock-icon image in the title-bar.
3340 */
3341 private void updateLockIconImage(int lockIconType) {
3342 Drawable d = null;
3343 if (lockIconType == LOCK_ICON_SECURE) {
3344 d = mSecLockIcon;
3345 } else if (lockIconType == LOCK_ICON_MIXED) {
3346 d = mMixLockIcon;
3347 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04003348 if (CUSTOM_BROWSER_BAR) {
3349 mTitleBar.setLock(d, getTopWindow());
3350 } else {
3351 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003352 }
3353 }
3354
3355 /**
3356 * Displays a page-info dialog.
3357 * @param tab The tab to show info about
3358 * @param fromShowSSLCertificateOnError The flag that indicates whether
3359 * this dialog was opened from the SSL-certificate-on-error dialog or
3360 * not. This is important, since we need to know whether to return to
3361 * the parent dialog or simply dismiss.
3362 */
3363 private void showPageInfo(final TabControl.Tab tab,
3364 final boolean fromShowSSLCertificateOnError) {
3365 final LayoutInflater factory = LayoutInflater
3366 .from(this);
3367
3368 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3369
3370 final WebView view = tab.getWebView();
3371
3372 String url = null;
3373 String title = null;
3374
3375 if (view == null) {
3376 url = tab.getUrl();
3377 title = tab.getTitle();
3378 } else if (view == mTabControl.getCurrentWebView()) {
3379 // Use the cached title and url if this is the current WebView
3380 url = mUrl;
3381 title = mTitle;
3382 } else {
3383 url = view.getUrl();
3384 title = view.getTitle();
3385 }
3386
3387 if (url == null) {
3388 url = "";
3389 }
3390 if (title == null) {
3391 title = "";
3392 }
3393
3394 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3395 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3396
3397 mPageInfoView = tab;
3398 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3399
3400 AlertDialog.Builder alertDialogBuilder =
3401 new AlertDialog.Builder(this)
3402 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3403 .setView(pageInfoView)
3404 .setPositiveButton(
3405 R.string.ok,
3406 new DialogInterface.OnClickListener() {
3407 public void onClick(DialogInterface dialog,
3408 int whichButton) {
3409 mPageInfoDialog = null;
3410 mPageInfoView = null;
3411 mPageInfoFromShowSSLCertificateOnError = null;
3412
3413 // if we came here from the SSL error dialog
3414 if (fromShowSSLCertificateOnError) {
3415 // go back to the SSL error dialog
3416 showSSLCertificateOnError(
3417 mSSLCertificateOnErrorView,
3418 mSSLCertificateOnErrorHandler,
3419 mSSLCertificateOnErrorError);
3420 }
3421 }
3422 })
3423 .setOnCancelListener(
3424 new DialogInterface.OnCancelListener() {
3425 public void onCancel(DialogInterface dialog) {
3426 mPageInfoDialog = null;
3427 mPageInfoView = null;
3428 mPageInfoFromShowSSLCertificateOnError = null;
3429
3430 // if we came here from the SSL error dialog
3431 if (fromShowSSLCertificateOnError) {
3432 // go back to the SSL error dialog
3433 showSSLCertificateOnError(
3434 mSSLCertificateOnErrorView,
3435 mSSLCertificateOnErrorHandler,
3436 mSSLCertificateOnErrorError);
3437 }
3438 }
3439 });
3440
3441 // if we have a main top-level page SSL certificate set or a certificate
3442 // error
3443 if (fromShowSSLCertificateOnError ||
3444 (view != null && view.getCertificate() != null)) {
3445 // add a 'View Certificate' button
3446 alertDialogBuilder.setNeutralButton(
3447 R.string.view_certificate,
3448 new DialogInterface.OnClickListener() {
3449 public void onClick(DialogInterface dialog,
3450 int whichButton) {
3451 mPageInfoDialog = null;
3452 mPageInfoView = null;
3453 mPageInfoFromShowSSLCertificateOnError = null;
3454
3455 // if we came here from the SSL error dialog
3456 if (fromShowSSLCertificateOnError) {
3457 // go back to the SSL error dialog
3458 showSSLCertificateOnError(
3459 mSSLCertificateOnErrorView,
3460 mSSLCertificateOnErrorHandler,
3461 mSSLCertificateOnErrorError);
3462 } else {
3463 // otherwise, display the top-most certificate from
3464 // the chain
3465 if (view.getCertificate() != null) {
3466 showSSLCertificate(tab);
3467 }
3468 }
3469 }
3470 });
3471 }
3472
3473 mPageInfoDialog = alertDialogBuilder.show();
3474 }
3475
3476 /**
3477 * Displays the main top-level page SSL certificate dialog
3478 * (accessible from the Page-Info dialog).
3479 * @param tab The tab to show certificate for.
3480 */
3481 private void showSSLCertificate(final TabControl.Tab tab) {
3482 final View certificateView =
3483 inflateCertificateView(tab.getWebView().getCertificate());
3484 if (certificateView == null) {
3485 return;
3486 }
3487
3488 LayoutInflater factory = LayoutInflater.from(this);
3489
3490 final LinearLayout placeholder =
3491 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3492
3493 LinearLayout ll = (LinearLayout) factory.inflate(
3494 R.layout.ssl_success, placeholder);
3495 ((TextView)ll.findViewById(R.id.success))
3496 .setText(R.string.ssl_certificate_is_valid);
3497
3498 mSSLCertificateView = tab;
3499 mSSLCertificateDialog =
3500 new AlertDialog.Builder(this)
3501 .setTitle(R.string.ssl_certificate).setIcon(
3502 R.drawable.ic_dialog_browser_certificate_secure)
3503 .setView(certificateView)
3504 .setPositiveButton(R.string.ok,
3505 new DialogInterface.OnClickListener() {
3506 public void onClick(DialogInterface dialog,
3507 int whichButton) {
3508 mSSLCertificateDialog = null;
3509 mSSLCertificateView = null;
3510
3511 showPageInfo(tab, false);
3512 }
3513 })
3514 .setOnCancelListener(
3515 new DialogInterface.OnCancelListener() {
3516 public void onCancel(DialogInterface dialog) {
3517 mSSLCertificateDialog = null;
3518 mSSLCertificateView = null;
3519
3520 showPageInfo(tab, false);
3521 }
3522 })
3523 .show();
3524 }
3525
3526 /**
3527 * Displays the SSL error certificate dialog.
3528 * @param view The target web-view.
3529 * @param handler The SSL error handler responsible for cancelling the
3530 * connection that resulted in an SSL error or proceeding per user request.
3531 * @param error The SSL error object.
3532 */
3533 private void showSSLCertificateOnError(
3534 final WebView view, final SslErrorHandler handler, final SslError error) {
3535
3536 final View certificateView =
3537 inflateCertificateView(error.getCertificate());
3538 if (certificateView == null) {
3539 return;
3540 }
3541
3542 LayoutInflater factory = LayoutInflater.from(this);
3543
3544 final LinearLayout placeholder =
3545 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3546
3547 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3548 LinearLayout ll = (LinearLayout)factory
3549 .inflate(R.layout.ssl_warning, placeholder);
3550 ((TextView)ll.findViewById(R.id.warning))
3551 .setText(R.string.ssl_untrusted);
3552 }
3553
3554 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3555 LinearLayout ll = (LinearLayout)factory
3556 .inflate(R.layout.ssl_warning, placeholder);
3557 ((TextView)ll.findViewById(R.id.warning))
3558 .setText(R.string.ssl_mismatch);
3559 }
3560
3561 if (error.hasError(SslError.SSL_EXPIRED)) {
3562 LinearLayout ll = (LinearLayout)factory
3563 .inflate(R.layout.ssl_warning, placeholder);
3564 ((TextView)ll.findViewById(R.id.warning))
3565 .setText(R.string.ssl_expired);
3566 }
3567
3568 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3569 LinearLayout ll = (LinearLayout)factory
3570 .inflate(R.layout.ssl_warning, placeholder);
3571 ((TextView)ll.findViewById(R.id.warning))
3572 .setText(R.string.ssl_not_yet_valid);
3573 }
3574
3575 mSSLCertificateOnErrorHandler = handler;
3576 mSSLCertificateOnErrorView = view;
3577 mSSLCertificateOnErrorError = error;
3578 mSSLCertificateOnErrorDialog =
3579 new AlertDialog.Builder(this)
3580 .setTitle(R.string.ssl_certificate).setIcon(
3581 R.drawable.ic_dialog_browser_certificate_partially_secure)
3582 .setView(certificateView)
3583 .setPositiveButton(R.string.ok,
3584 new DialogInterface.OnClickListener() {
3585 public void onClick(DialogInterface dialog,
3586 int whichButton) {
3587 mSSLCertificateOnErrorDialog = null;
3588 mSSLCertificateOnErrorView = null;
3589 mSSLCertificateOnErrorHandler = null;
3590 mSSLCertificateOnErrorError = null;
3591
3592 mWebViewClient.onReceivedSslError(
3593 view, handler, error);
3594 }
3595 })
3596 .setNeutralButton(R.string.page_info_view,
3597 new DialogInterface.OnClickListener() {
3598 public void onClick(DialogInterface dialog,
3599 int whichButton) {
3600 mSSLCertificateOnErrorDialog = null;
3601
3602 // do not clear the dialog state: we will
3603 // need to show the dialog again once the
3604 // user is done exploring the page-info details
3605
3606 showPageInfo(mTabControl.getTabFromView(view),
3607 true);
3608 }
3609 })
3610 .setOnCancelListener(
3611 new DialogInterface.OnCancelListener() {
3612 public void onCancel(DialogInterface dialog) {
3613 mSSLCertificateOnErrorDialog = null;
3614 mSSLCertificateOnErrorView = null;
3615 mSSLCertificateOnErrorHandler = null;
3616 mSSLCertificateOnErrorError = null;
3617
3618 mWebViewClient.onReceivedSslError(
3619 view, handler, error);
3620 }
3621 })
3622 .show();
3623 }
3624
3625 /**
3626 * Inflates the SSL certificate view (helper method).
3627 * @param certificate The SSL certificate.
3628 * @return The resultant certificate view with issued-to, issued-by,
3629 * issued-on, expires-on, and possibly other fields set.
3630 * If the input certificate is null, returns null.
3631 */
3632 private View inflateCertificateView(SslCertificate certificate) {
3633 if (certificate == null) {
3634 return null;
3635 }
3636
3637 LayoutInflater factory = LayoutInflater.from(this);
3638
3639 View certificateView = factory.inflate(
3640 R.layout.ssl_certificate, null);
3641
3642 // issued to:
3643 SslCertificate.DName issuedTo = certificate.getIssuedTo();
3644 if (issuedTo != null) {
3645 ((TextView) certificateView.findViewById(R.id.to_common))
3646 .setText(issuedTo.getCName());
3647 ((TextView) certificateView.findViewById(R.id.to_org))
3648 .setText(issuedTo.getOName());
3649 ((TextView) certificateView.findViewById(R.id.to_org_unit))
3650 .setText(issuedTo.getUName());
3651 }
3652
3653 // issued by:
3654 SslCertificate.DName issuedBy = certificate.getIssuedBy();
3655 if (issuedBy != null) {
3656 ((TextView) certificateView.findViewById(R.id.by_common))
3657 .setText(issuedBy.getCName());
3658 ((TextView) certificateView.findViewById(R.id.by_org))
3659 .setText(issuedBy.getOName());
3660 ((TextView) certificateView.findViewById(R.id.by_org_unit))
3661 .setText(issuedBy.getUName());
3662 }
3663
3664 // issued on:
3665 String issuedOn = reformatCertificateDate(
3666 certificate.getValidNotBefore());
3667 ((TextView) certificateView.findViewById(R.id.issued_on))
3668 .setText(issuedOn);
3669
3670 // expires on:
3671 String expiresOn = reformatCertificateDate(
3672 certificate.getValidNotAfter());
3673 ((TextView) certificateView.findViewById(R.id.expires_on))
3674 .setText(expiresOn);
3675
3676 return certificateView;
3677 }
3678
3679 /**
3680 * Re-formats the certificate date (Date.toString()) string to
3681 * a properly localized date string.
3682 * @return Properly localized version of the certificate date string and
3683 * the original certificate date string if fails to localize.
3684 * If the original string is null, returns an empty string "".
3685 */
3686 private String reformatCertificateDate(String certificateDate) {
3687 String reformattedDate = null;
3688
3689 if (certificateDate != null) {
3690 Date date = null;
3691 try {
3692 date = java.text.DateFormat.getInstance().parse(certificateDate);
3693 } catch (ParseException e) {
3694 date = null;
3695 }
3696
3697 if (date != null) {
3698 reformattedDate =
3699 DateFormat.getDateFormat(this).format(date);
3700 }
3701 }
3702
3703 return reformattedDate != null ? reformattedDate :
3704 (certificateDate != null ? certificateDate : "");
3705 }
3706
3707 /**
3708 * Displays an http-authentication dialog.
3709 */
3710 private void showHttpAuthentication(final HttpAuthHandler handler,
3711 final String host, final String realm, final String title,
3712 final String name, final String password, int focusId) {
3713 LayoutInflater factory = LayoutInflater.from(this);
3714 final View v = factory
3715 .inflate(R.layout.http_authentication, null);
3716 if (name != null) {
3717 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3718 }
3719 if (password != null) {
3720 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3721 }
3722
3723 String titleText = title;
3724 if (titleText == null) {
3725 titleText = getText(R.string.sign_in_to).toString().replace(
3726 "%s1", host).replace("%s2", realm);
3727 }
3728
3729 mHttpAuthHandler = handler;
3730 AlertDialog dialog = new AlertDialog.Builder(this)
3731 .setTitle(titleText)
3732 .setIcon(android.R.drawable.ic_dialog_alert)
3733 .setView(v)
3734 .setPositiveButton(R.string.action,
3735 new DialogInterface.OnClickListener() {
3736 public void onClick(DialogInterface dialog,
3737 int whichButton) {
3738 String nm = ((EditText) v
3739 .findViewById(R.id.username_edit))
3740 .getText().toString();
3741 String pw = ((EditText) v
3742 .findViewById(R.id.password_edit))
3743 .getText().toString();
3744 BrowserActivity.this.setHttpAuthUsernamePassword
3745 (host, realm, nm, pw);
3746 handler.proceed(nm, pw);
3747 mHttpAuthenticationDialog = null;
3748 mHttpAuthHandler = null;
3749 }})
3750 .setNegativeButton(R.string.cancel,
3751 new DialogInterface.OnClickListener() {
3752 public void onClick(DialogInterface dialog,
3753 int whichButton) {
3754 handler.cancel();
3755 BrowserActivity.this.resetTitleAndRevertLockIcon();
3756 mHttpAuthenticationDialog = null;
3757 mHttpAuthHandler = null;
3758 }})
3759 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3760 public void onCancel(DialogInterface dialog) {
3761 handler.cancel();
3762 BrowserActivity.this.resetTitleAndRevertLockIcon();
3763 mHttpAuthenticationDialog = null;
3764 mHttpAuthHandler = null;
3765 }})
3766 .create();
3767 // Make the IME appear when the dialog is displayed if applicable.
3768 dialog.getWindow().setSoftInputMode(
3769 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3770 dialog.show();
3771 if (focusId != 0) {
3772 dialog.findViewById(focusId).requestFocus();
3773 } else {
3774 v.findViewById(R.id.username_edit).requestFocus();
3775 }
3776 mHttpAuthenticationDialog = dialog;
3777 }
3778
3779 public int getProgress() {
3780 WebView w = mTabControl.getCurrentWebView();
3781 if (w != null) {
3782 return w.getProgress();
3783 } else {
3784 return 100;
3785 }
3786 }
3787
3788 /**
3789 * Set HTTP authentication password.
3790 *
3791 * @param host The host for the password
3792 * @param realm The realm for the password
3793 * @param username The username for the password. If it is null, it means
3794 * password can't be saved.
3795 * @param password The password
3796 */
3797 public void setHttpAuthUsernamePassword(String host, String realm,
3798 String username,
3799 String password) {
3800 WebView w = mTabControl.getCurrentWebView();
3801 if (w != null) {
3802 w.setHttpAuthUsernamePassword(host, realm, username, password);
3803 }
3804 }
3805
3806 /**
3807 * connectivity manager says net has come or gone... inform the user
3808 * @param up true if net has come up, false if net has gone down
3809 */
3810 public void onNetworkToggle(boolean up) {
3811 if (up == mIsNetworkUp) {
3812 return;
3813 } else if (up) {
3814 mIsNetworkUp = true;
3815 if (mAlertDialog != null) {
3816 mAlertDialog.cancel();
3817 mAlertDialog = null;
3818 }
3819 } else {
3820 mIsNetworkUp = false;
3821 if (mInLoad && mAlertDialog == null) {
3822 mAlertDialog = new AlertDialog.Builder(this)
3823 .setTitle(R.string.loadSuspendedTitle)
3824 .setMessage(R.string.loadSuspended)
3825 .setPositiveButton(R.string.ok, null)
3826 .show();
3827 }
3828 }
3829 WebView w = mTabControl.getCurrentWebView();
3830 if (w != null) {
3831 w.setNetworkAvailable(up);
3832 }
3833 }
3834
3835 @Override
3836 protected void onActivityResult(int requestCode, int resultCode,
3837 Intent intent) {
3838 switch (requestCode) {
3839 case COMBO_PAGE:
3840 if (resultCode == RESULT_OK && intent != null) {
3841 String data = intent.getAction();
3842 Bundle extras = intent.getExtras();
3843 if (extras != null && extras.getBoolean("new_window", false)) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003844 final TabControl.Tab newTab = openTab(data);
3845 if (mSettings.openInBackground() &&
Leon Scroggins1f005d32009-08-10 17:36:42 -04003846 newTab != null) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003847 mTabControl.populatePickerData(newTab);
3848 mTabControl.setCurrentTab(newTab);
Leon Scroggins1f005d32009-08-10 17:36:42 -04003849 int newIndex = mTabControl.getCurrentIndex();
3850 if (CUSTOM_BROWSER_BAR) {
3851 mTitleBar.setCurrentTab(newIndex);
3852 }
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003853 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003854 } else {
3855 final TabControl.Tab currentTab =
3856 mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04003857 dismissSubWindow(currentTab);
3858 if (data != null && data.length() != 0) {
3859 getTopWindow().loadUrl(data);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003860 }
3861 }
Leon Scroggins39362092009-08-18 19:47:09 -04003862/*
3863 FIXME: Removing this breaks the behavior of pressing BACK from
3864 the Go page resulting in the window being closed. However, it
3865 needs to be removed so that the user can use the Search bar to
3866 enter a URL. Further, the Go behavior is going to change
3867 drastically, so this behavior may not last anyway.
Leon Scroggins160a7e72009-08-14 18:28:01 -04003868 } else if (resultCode == RESULT_CANCELED
3869 && mCancelGoPageMeansClose) {
3870 if (mTabControl.getTabCount() == 1) {
3871 // finish the Browser. When the Browser opens up again,
3872 // we will go through onCreate and once again open up
3873 // the Go page.
3874 finish();
3875 return;
3876 }
3877 closeCurrentWindow();
Leon Scroggins39362092009-08-18 19:47:09 -04003878*/
The Android Open Source Project0c908882009-03-03 19:32:16 -08003879 }
3880 break;
3881 default:
3882 break;
3883 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003884 mCancelGoPageMeansClose = false;
3885 if (getTopWindow() != null) {
3886 getTopWindow().requestFocus();
3887 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003888 }
3889
3890 /*
3891 * This method is called as a result of the user selecting the options
3892 * menu to see the download window, or when a download changes state. It
3893 * shows the download window ontop of the current window.
3894 */
3895 /* package */ void viewDownloads(Uri downloadRecord) {
3896 Intent intent = new Intent(this,
3897 BrowserDownloadPage.class);
3898 intent.setData(downloadRecord);
3899 startActivityForResult(intent, this.DOWNLOAD_PAGE);
3900
3901 }
3902
Leon Scroggins160a7e72009-08-14 18:28:01 -04003903 // True if canceling the "Go" screen should result in closing the current
3904 // window/browser.
3905 private boolean mCancelGoPageMeansClose;
3906
3907 /**
3908 * Open the Go page.
3909 * @param startWithHistory If true, open starting on the history tab.
3910 * Otherwise, start with the bookmarks tab.
3911 * @param cancelGoPageMeansClose Set to true if this came from a new tab, or
3912 * from the only tab, and canceling means to
3913 * close the tab (and possibly the browser)
3914 */
3915 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory,
3916 boolean cancelGoPageMeansClose) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003917 WebView current = mTabControl.getCurrentWebView();
3918 if (current == null) {
3919 return;
3920 }
3921 Intent intent = new Intent(this,
3922 CombinedBookmarkHistoryActivity.class);
3923 String title = current.getTitle();
3924 String url = current.getUrl();
3925 // Just in case the user opens bookmarks before a page finishes loading
3926 // so the current history item, and therefore the page, is null.
3927 if (null == url) {
3928 url = mLastEnteredUrl;
3929 // This can happen.
3930 if (null == url) {
3931 url = mSettings.getHomePage();
3932 }
3933 }
3934 // In case the web page has not yet received its associated title.
3935 if (title == null) {
3936 title = url;
3937 }
3938 intent.putExtra("title", title);
3939 intent.putExtra("url", url);
Leon Scroggins190095d2009-08-17 17:01:38 -04003940 // If this is opening in a new window, then disable opening in a
3941 // (different) new window. Also disable it if we have maxed out the
3942 // windows.
3943 intent.putExtra("disable_new_window", cancelGoPageMeansClose
3944 || mTabControl.getTabCount() >= TabControl.MAX_TABS);
Patrick Scott3918d442009-08-04 13:22:29 -04003945 intent.putExtra("touch_icon_url", current.getTouchIconUrl());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003946 if (startWithHistory) {
3947 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
3948 CombinedBookmarkHistoryActivity.HISTORY_TAB);
3949 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003950 mCancelGoPageMeansClose = cancelGoPageMeansClose;
The Android Open Source Project0c908882009-03-03 19:32:16 -08003951 startActivityForResult(intent, COMBO_PAGE);
3952 }
3953
3954 // Called when loading from context menu or LOAD_URL message
3955 private void loadURL(WebView view, String url) {
3956 // In case the user enters nothing.
3957 if (url != null && url.length() != 0 && view != null) {
3958 url = smartUrlFilter(url);
3959 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
3960 view.loadUrl(url);
3961 }
3962 }
3963 }
3964
The Android Open Source Project0c908882009-03-03 19:32:16 -08003965 private String smartUrlFilter(Uri inUri) {
3966 if (inUri != null) {
3967 return smartUrlFilter(inUri.toString());
3968 }
3969 return null;
3970 }
3971
3972
3973 // get window count
3974
3975 int getWindowCount(){
3976 if(mTabControl != null){
3977 return mTabControl.getTabCount();
3978 }
3979 return 0;
3980 }
3981
Feng Qianb34f87a2009-03-24 21:27:26 -07003982 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08003983 "(?i)" + // switch on case insensitive matching
3984 "(" + // begin group for schema
3985 "(?:http|https|file):\\/\\/" +
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07003986 "|(?:inline|data|about|content|javascript):" +
The Android Open Source Project0c908882009-03-03 19:32:16 -08003987 ")" +
3988 "(.*)" );
3989
3990 /**
3991 * Attempts to determine whether user input is a URL or search
3992 * terms. Anything with a space is passed to search.
3993 *
3994 * Converts to lowercase any mistakenly uppercased schema (i.e.,
3995 * "Http://" converts to "http://"
3996 *
3997 * @return Original or modified URL
3998 *
3999 */
4000 String smartUrlFilter(String url) {
4001
4002 String inUrl = url.trim();
4003 boolean hasSpace = inUrl.indexOf(' ') != -1;
4004
4005 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4006 if (matcher.matches()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004007 // force scheme to lowercase
4008 String scheme = matcher.group(1);
4009 String lcScheme = scheme.toLowerCase();
4010 if (!lcScheme.equals(scheme)) {
Mitsuru Oshima123ecfb2009-05-18 19:11:14 -07004011 inUrl = lcScheme + matcher.group(2);
4012 }
4013 if (hasSpace) {
4014 inUrl = inUrl.replace(" ", "%20");
The Android Open Source Project0c908882009-03-03 19:32:16 -08004015 }
4016 return inUrl;
4017 }
4018 if (hasSpace) {
Satish Sampath565505b2009-05-29 15:37:27 +01004019 // FIXME: Is this the correct place to add to searches?
4020 // what if someone else calls this function?
4021 int shortcut = parseUrlShortcut(inUrl);
4022 if (shortcut != SHORTCUT_INVALID) {
4023 Browser.addSearchUrl(mResolver, inUrl);
4024 String query = inUrl.substring(2);
4025 switch (shortcut) {
4026 case SHORTCUT_GOOGLE_SEARCH:
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004027 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
Satish Sampath565505b2009-05-29 15:37:27 +01004028 case SHORTCUT_WIKIPEDIA_SEARCH:
4029 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4030 case SHORTCUT_DICTIONARY_SEARCH:
4031 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4032 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
The Android Open Source Project0c908882009-03-03 19:32:16 -08004033 // FIXME: we need location in this case
Satish Sampath565505b2009-05-29 15:37:27 +01004034 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004035 }
4036 }
4037 } else {
4038 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4039 return URLUtil.guessUrl(inUrl);
4040 }
4041 }
4042
4043 Browser.addSearchUrl(mResolver, inUrl);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004044 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004045 }
4046
Ben Murdochbff2d602009-07-01 20:19:05 +01004047 /* package */ void setShouldShowErrorConsole(boolean flag) {
4048 if (flag == mShouldShowErrorConsole) {
4049 // Nothing to do.
4050 return;
4051 }
4052
4053 mShouldShowErrorConsole = flag;
4054
4055 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4056
4057 if (flag) {
4058 // Setting the show state of the console will cause it's the layout to be inflated.
4059 if (errorConsole.numberOfErrors() > 0) {
4060 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4061 } else {
4062 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4063 }
4064
4065 // Now we can add it to the main view.
4066 mErrorConsoleContainer.addView(errorConsole,
4067 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4068 ViewGroup.LayoutParams.WRAP_CONTENT));
4069 } else {
4070 mErrorConsoleContainer.removeView(errorConsole);
4071 }
4072
4073 }
4074
The Android Open Source Project0c908882009-03-03 19:32:16 -08004075 private final static int LOCK_ICON_UNSECURE = 0;
4076 private final static int LOCK_ICON_SECURE = 1;
4077 private final static int LOCK_ICON_MIXED = 2;
4078
4079 private int mLockIconType = LOCK_ICON_UNSECURE;
4080 private int mPrevLockType = LOCK_ICON_UNSECURE;
4081
4082 private BrowserSettings mSettings;
4083 private TabControl mTabControl;
4084 private ContentResolver mResolver;
4085 private FrameLayout mContentView;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004086 private View mCustomView;
4087 private FrameLayout mCustomViewContainer;
Andrei Popescuc9b55562009-07-07 10:51:15 +01004088 private WebChromeClient.CustomViewCallback mCustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004089
4090 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4091 // view, we should rewrite this.
4092 private int mCurrentMenuState = 0;
4093 private int mMenuState = R.id.MAIN_MENU;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004094 private int mOldMenuState = EMPTY_MENU;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004095 private static final int EMPTY_MENU = -1;
4096 private Menu mMenu;
4097
4098 private FindDialog mFindDialog;
4099 // Used to prevent chording to result in firing two shortcuts immediately
4100 // one after another. Fixes bug 1211714.
4101 boolean mCanChord;
4102
4103 private boolean mInLoad;
4104 private boolean mIsNetworkUp;
4105
4106 private boolean mPageStarted;
4107 private boolean mActivityInPause = true;
4108
4109 private boolean mMenuIsDown;
4110
4111 private final KeyTracker mKeyTracker = new KeyTracker(this);
4112
4113 // As trackball doesn't send repeat down, we have to track it ourselves
4114 private boolean mTrackTrackball;
4115
4116 private static boolean mInTrace;
4117
4118 // Performance probe
4119 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4120 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4121 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4122 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4123 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4124 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4125 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4126 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4127 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4128 };
4129
4130 private long mStart;
4131 private long mProcessStart;
4132 private long mUserStart;
4133 private long mSystemStart;
4134 private long mIdleStart;
4135 private long mIrqStart;
4136
4137 private long mUiStart;
4138
4139 private Drawable mMixLockIcon;
4140 private Drawable mSecLockIcon;
4141 private Drawable mGenericFavicon;
4142
4143 /* hold a ref so we can auto-cancel if necessary */
4144 private AlertDialog mAlertDialog;
4145
4146 // Wait for credentials before loading google.com
4147 private ProgressDialog mCredsDlg;
4148
4149 // The up-to-date URL and title (these can be different from those stored
4150 // in WebView, since it takes some time for the information in WebView to
4151 // get updated)
4152 private String mUrl;
4153 private String mTitle;
4154
4155 // As PageInfo has different style for landscape / portrait, we have
4156 // to re-open it when configuration changed
4157 private AlertDialog mPageInfoDialog;
4158 private TabControl.Tab mPageInfoView;
4159 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4160 // dialog, we should not just dismiss it, but should get back to the
4161 // SSL-certificate-on-error dialog. This flag is used to store this state
4162 private Boolean mPageInfoFromShowSSLCertificateOnError;
4163
4164 // as SSLCertificateOnError has different style for landscape / portrait,
4165 // we have to re-open it when configuration changed
4166 private AlertDialog mSSLCertificateOnErrorDialog;
4167 private WebView mSSLCertificateOnErrorView;
4168 private SslErrorHandler mSSLCertificateOnErrorHandler;
4169 private SslError mSSLCertificateOnErrorError;
4170
4171 // as SSLCertificate has different style for landscape / portrait, we
4172 // have to re-open it when configuration changed
4173 private AlertDialog mSSLCertificateDialog;
4174 private TabControl.Tab mSSLCertificateView;
4175
4176 // as HttpAuthentication has different style for landscape / portrait, we
4177 // have to re-open it when configuration changed
4178 private AlertDialog mHttpAuthenticationDialog;
4179 private HttpAuthHandler mHttpAuthHandler;
4180
4181 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4182 new FrameLayout.LayoutParams(
4183 ViewGroup.LayoutParams.FILL_PARENT,
4184 ViewGroup.LayoutParams.FILL_PARENT);
Andrei Popescuadc008d2009-06-26 14:11:30 +01004185 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4186 new FrameLayout.LayoutParams(
4187 ViewGroup.LayoutParams.FILL_PARENT,
4188 ViewGroup.LayoutParams.FILL_PARENT,
4189 Gravity.CENTER);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004190 // Google search
4191 final static String QuickSearch_G = "http://www.google.com/m?q=%s";
The Android Open Source Project0c908882009-03-03 19:32:16 -08004192 // Wikipedia search
4193 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4194 // Dictionary search
4195 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4196 // Google Mobile Local search
4197 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4198
4199 final static String QUERY_PLACE_HOLDER = "%s";
4200
4201 // "source" parameter for Google search through search key
4202 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4203 // "source" parameter for Google search through goto menu
4204 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4205 // "source" parameter for Google search through simplily type
4206 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4207 // "source" parameter for Google search suggested by the browser
4208 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4209 // "source" parameter for Google search from unknown source
4210 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4211
4212 private final static String LOGTAG = "browser";
4213
The Android Open Source Project0c908882009-03-03 19:32:16 -08004214 private String mLastEnteredUrl;
4215
4216 private PowerManager.WakeLock mWakeLock;
4217 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4218
4219 private Toast mStopToast;
4220
Leon Scroggins1f005d32009-08-10 17:36:42 -04004221 private TitleBarSet mTitleBar;
Leon Scroggins81db3662009-06-04 17:45:11 -04004222
Ben Murdochbff2d602009-07-01 20:19:05 +01004223 private LinearLayout mErrorConsoleContainer = null;
4224 private boolean mShouldShowErrorConsole = false;
4225
The Android Open Source Project0c908882009-03-03 19:32:16 -08004226 // As the ids are dynamically created, we can't guarantee that they will
4227 // be in sequence, so this static array maps ids to a window number.
4228 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4229 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4230 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4231 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4232
4233 // monitor platform changes
4234 private IntentFilter mNetworkStateChangedFilter;
4235 private BroadcastReceiver mNetworkStateIntentReceiver;
4236
Grace Klobab4da0ad2009-05-14 14:45:40 -07004237 private BroadcastReceiver mPackageInstallationReceiver;
4238
The Android Open Source Project0c908882009-03-03 19:32:16 -08004239 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004240 final static int COMBO_PAGE = 1;
4241 final static int DOWNLOAD_PAGE = 2;
4242 final static int PREFERENCES_PAGE = 3;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004243
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004244 /**
4245 * A UrlData class to abstract how the content will be set to WebView.
4246 * This base class uses loadUrl to show the content.
4247 */
4248 private static class UrlData {
4249 String mUrl;
Grace Kloba60e095c2009-06-16 11:50:55 -07004250 byte[] mPostData;
4251
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004252 UrlData(String url) {
4253 this.mUrl = url;
4254 }
Grace Kloba60e095c2009-06-16 11:50:55 -07004255
4256 void setPostData(byte[] postData) {
4257 mPostData = postData;
4258 }
4259
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004260 boolean isEmpty() {
4261 return mUrl == null || mUrl.length() == 0;
4262 }
4263
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004264 public void loadIn(WebView webView) {
Grace Kloba60e095c2009-06-16 11:50:55 -07004265 if (mPostData != null) {
4266 webView.postUrl(mUrl, mPostData);
4267 } else {
4268 webView.loadUrl(mUrl);
4269 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004270 }
4271 };
4272
4273 /**
4274 * A subclass of UrlData class that can display inlined content using
4275 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4276 */
4277 private static class InlinedUrlData extends UrlData {
4278 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4279 super(failUrl);
4280 mInlined = inlined;
4281 mMimeType = mimeType;
4282 mEncoding = encoding;
4283 }
4284 String mMimeType;
4285 String mInlined;
4286 String mEncoding;
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004287 @Override
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004288 boolean isEmpty() {
Ben Murdochbff2d602009-07-01 20:19:05 +01004289 return mInlined == null || mInlined.length() == 0 || super.isEmpty();
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004290 }
4291
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004292 @Override
4293 public void loadIn(WebView webView) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004294 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4295 }
4296 }
4297
Leon Scroggins1f005d32009-08-10 17:36:42 -04004298 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004299}