blob: 7b76cf82b6a49b07d8b36a277f166081132d0d18 [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) {
282 f.delete();
283 }
284 }
285 return null;
286 }
287 }
288
Leon Scroggins81db3662009-06-04 17:45:11 -0400289 // Flag to enable the touchable browser bar with buttons
290 private final boolean CUSTOM_BROWSER_BAR = true;
291
The Android Open Source Project0c908882009-03-03 19:32:16 -0800292 @Override public void onCreate(Bundle icicle) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700293 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800294 Log.v(LOGTAG, this + " onStart");
295 }
296 super.onCreate(icicle);
Leon Scroggins81db3662009-06-04 17:45:11 -0400297 if (CUSTOM_BROWSER_BAR) {
298 this.requestWindowFeature(Window.FEATURE_NO_TITLE);
Leon Scroggins81db3662009-06-04 17:45:11 -0400299 } else {
300 this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
301 this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
302 this.requestWindowFeature(Window.FEATURE_PROGRESS);
303 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
304 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800305 // test the browser in OpenGL
306 // requestWindowFeature(Window.FEATURE_OPENGL);
307
308 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
309
310 mResolver = getContentResolver();
311
The Android Open Source Project0c908882009-03-03 19:32:16 -0800312 //
313 // start MASF proxy service
314 //
315 //Intent proxyServiceIntent = new Intent();
316 //proxyServiceIntent.setComponent
317 // (new ComponentName(
318 // "com.android.masfproxyservice",
319 // "com.android.masfproxyservice.MasfProxyService"));
320 //startService(proxyServiceIntent, null);
321
322 mSecLockIcon = Resources.getSystem().getDrawable(
323 android.R.drawable.ic_secure);
324 mMixLockIcon = Resources.getSystem().getDrawable(
325 android.R.drawable.ic_partial_secure);
326 mGenericFavicon = getResources().getDrawable(
327 R.drawable.app_web_browser_sm);
328
Leon Scroggins81db3662009-06-04 17:45:11 -0400329 FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
330 .findViewById(com.android.internal.R.id.content);
331 if (CUSTOM_BROWSER_BAR) {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100332 // This FrameLayout will hold the custom FrameLayout and a LinearLayout
333 // that contains the title bar and a FrameLayout, which
Leon Scroggins81db3662009-06-04 17:45:11 -0400334 // holds everything else.
Andrei Popescuadc008d2009-06-26 14:11:30 +0100335 FrameLayout browserFrameLayout = (FrameLayout) LayoutInflater.from(this)
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400336 .inflate(R.layout.custom_screen, null);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400337 mTitleBar = (TitleBarSet) browserFrameLayout.findViewById(R.id.title_bar);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100338 mContentView = (FrameLayout) browserFrameLayout.findViewById(
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400339 R.id.main_content);
Ben Murdochbff2d602009-07-01 20:19:05 +0100340 mErrorConsoleContainer = (LinearLayout) browserFrameLayout.findViewById(
341 R.id.error_console);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100342 mCustomViewContainer = (FrameLayout) browserFrameLayout
343 .findViewById(R.id.fullscreen_custom_content);
344 frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400345 } else {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100346 mCustomViewContainer = new FrameLayout(this);
Andrei Popescu78f75702009-06-26 16:50:04 +0100347 mCustomViewContainer.setBackgroundColor(Color.BLACK);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100348 mContentView = new FrameLayout(this);
Ben Murdochbff2d602009-07-01 20:19:05 +0100349
350 LinearLayout linearLayout = new LinearLayout(this);
351 linearLayout.setOrientation(LinearLayout.VERTICAL);
352 mErrorConsoleContainer = new LinearLayout(this);
353 linearLayout.addView(mErrorConsoleContainer, new LinearLayout.LayoutParams(
354 ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
355 linearLayout.addView(mContentView, COVER_SCREEN_PARAMS);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100356 frameLayout.addView(mCustomViewContainer, COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +0100357 frameLayout.addView(linearLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400358 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800359
360 // Create the tab control and our initial tab
361 mTabControl = new TabControl(this);
362
363 // Open the icon database and retain all the bookmark urls for favicons
364 retainIconsOnStartup();
365
366 // Keep a settings instance handy.
367 mSettings = BrowserSettings.getInstance();
368 mSettings.setTabControl(mTabControl);
369 mSettings.loadFromDb(this);
370
371 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
372 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
373
Grace Klobaa34f6862009-07-31 16:28:17 -0700374 /* enables registration for changes in network status from
375 http stack */
376 mNetworkStateChangedFilter = new IntentFilter();
377 mNetworkStateChangedFilter.addAction(
378 ConnectivityManager.CONNECTIVITY_ACTION);
379 mNetworkStateIntentReceiver = new BroadcastReceiver() {
380 @Override
381 public void onReceive(Context context, Intent intent) {
382 if (intent.getAction().equals(
383 ConnectivityManager.CONNECTIVITY_ACTION)) {
384 boolean down = intent.getBooleanExtra(
385 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
386 onNetworkToggle(!down);
387 }
388 }
389 };
390
Grace Kloba615c6c92009-08-03 10:22:44 -0700391 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
392 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
393 filter.addDataScheme("package");
394 mPackageInstallationReceiver = new BroadcastReceiver() {
395 @Override
396 public void onReceive(Context context, Intent intent) {
397 final String action = intent.getAction();
398 final String packageName = intent.getData()
399 .getSchemeSpecificPart();
400 final boolean replacing = intent.getBooleanExtra(
401 Intent.EXTRA_REPLACING, false);
402 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
403 // if it is replacing, refreshPlugins() when adding
404 return;
405 }
406 PackageManager pm = BrowserActivity.this.getPackageManager();
407 PackageInfo pkgInfo = null;
408 try {
409 pkgInfo = pm.getPackageInfo(packageName,
410 PackageManager.GET_PERMISSIONS);
411 } catch (PackageManager.NameNotFoundException e) {
412 return;
413 }
414 if (pkgInfo != null) {
415 String permissions[] = pkgInfo.requestedPermissions;
416 if (permissions == null) {
417 return;
418 }
419 boolean permissionOk = false;
420 for (String permit : permissions) {
421 if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
422 permissionOk = true;
423 break;
424 }
425 }
426 if (permissionOk) {
427 PluginManager.getInstance(BrowserActivity.this)
428 .refreshPlugins(
429 Intent.ACTION_PACKAGE_ADDED
430 .equals(action));
431 }
432 }
433 }
434 };
435 registerReceiver(mPackageInstallationReceiver, filter);
436
Satish Sampath565505b2009-05-29 15:37:27 +0100437 // If this was a web search request, pass it on to the default web search provider.
438 if (handleWebSearchIntent(getIntent())) {
439 moveTaskToBack(true);
440 return;
441 }
442
The Android Open Source Project0c908882009-03-03 19:32:16 -0800443 if (!mTabControl.restoreState(icicle)) {
444 // clear up the thumbnail directory if we can't restore the state as
445 // none of the files in the directory are referenced any more.
446 new ClearThumbnails().execute(
447 mTabControl.getThumbnailDir().listFiles());
Grace Klobaaab3f092009-07-30 12:29:51 -0700448 // there is no quit on Android. But if we can't restore the state,
449 // we can treat it as a new Browser, remove the old session cookies.
450 CookieManager.getInstance().removeSessionCookie();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800451 final Intent intent = getIntent();
452 final Bundle extra = intent.getExtras();
453 // Create an initial tab.
454 // If the intent is ACTION_VIEW and data is not null, the Browser is
455 // invoked to view the content by another application. In this case,
456 // the tab will be close when exit.
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700457 UrlData urlData = getUrlDataFromIntent(intent);
458
The Android Open Source Project0c908882009-03-03 19:32:16 -0800459 final TabControl.Tab t = mTabControl.createNewTab(
460 Intent.ACTION_VIEW.equals(intent.getAction()) &&
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700461 intent.getData() != null,
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700462 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800463 mTabControl.setCurrentTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800464 attachTabToContentView(t);
465 WebView webView = t.getWebView();
466 if (extra != null) {
467 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
468 if (scale > 0 && scale <= 1000) {
469 webView.setInitialScale(scale);
470 }
471 }
472 // If we are not restoring from an icicle, then there is a high
473 // likely hood this is the first run. So, check to see if the
474 // homepage needs to be configured and copy any plugins from our
475 // asset directory to the data partition.
476 if ((extra == null || !extra.getBoolean("testing"))
477 && !mSettings.isLoginInitialized()) {
478 setupHomePage();
479 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800480
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700481 if (urlData.isEmpty()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -0400482 bookmarksOrHistoryPicker(false);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800483 } else {
Grace Kloba81678d92009-06-30 07:09:56 -0700484 if (extra != null) {
485 urlData.setPostData(extra
486 .getByteArray(Browser.EXTRA_POST_DATA));
487 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700488 urlData.loadIn(webView);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800489 }
490 } else {
491 // TabControl.restoreState() will create a new tab even if
Leon Scroggins1f005d32009-08-10 17:36:42 -0400492 // restoring the state fails.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800493 attachTabToContentView(mTabControl.getCurrentTab());
494 }
Grace Kloba615c6c92009-08-03 10:22:44 -0700495
Leon Scroggins1f005d32009-08-10 17:36:42 -0400496 if (CUSTOM_BROWSER_BAR) {
497 // Create title bars for all of the tabs that have been created
498 for (int i = 0; i < mTabControl.getTabCount(); i ++) {
499 WebView view = mTabControl.getTab(i).getWebView();
500 mTitleBar.addTab(view, false);
501 }
502
503 mTitleBar.setBrowserActivity(this);
504 mTitleBar.setCurrentTab(mTabControl.getCurrentIndex());
505 }
506
Feng Qianb3c02da2009-06-29 15:58:08 -0700507 // Read JavaScript flags if it exists.
508 String jsFlags = mSettings.getJsFlags();
509 if (jsFlags.trim().length() != 0) {
510 mTabControl.getCurrentWebView().setJsFlags(jsFlags);
511 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800512 }
513
514 @Override
515 protected void onNewIntent(Intent intent) {
516 TabControl.Tab current = mTabControl.getCurrentTab();
517 // When a tab is closed on exit, the current tab index is set to -1.
518 // Reset before proceed as Browser requires the current tab to be set.
519 if (current == null) {
520 // Try to reset the tab in case the index was incorrect.
521 current = mTabControl.getTab(0);
522 if (current == null) {
523 // No tabs at all so just ignore this intent.
524 return;
525 }
526 mTabControl.setCurrentTab(current);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400527 if (CUSTOM_BROWSER_BAR) {
528 mTitleBar.setCurrentTab(mTabControl.getTabIndex(current));
529 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800530 attachTabToContentView(current);
531 resetTitleAndIcon(current.getWebView());
532 }
533 final String action = intent.getAction();
534 final int flags = intent.getFlags();
535 if (Intent.ACTION_MAIN.equals(action) ||
536 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
537 // just resume the browser
538 return;
539 }
540 if (Intent.ACTION_VIEW.equals(action)
541 || Intent.ACTION_SEARCH.equals(action)
542 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
543 || Intent.ACTION_WEB_SEARCH.equals(action)) {
Satish Sampath565505b2009-05-29 15:37:27 +0100544 // If this was a search request (e.g. search query directly typed into the address bar),
545 // pass it on to the default web search provider.
546 if (handleWebSearchIntent(intent)) {
547 return;
548 }
549
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700550 UrlData urlData = getUrlDataFromIntent(intent);
551 if (urlData.isEmpty()) {
552 urlData = new UrlData(mSettings.getHomePage());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800553 }
Grace Kloba81678d92009-06-30 07:09:56 -0700554 urlData.setPostData(intent
555 .getByteArrayExtra(Browser.EXTRA_POST_DATA));
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700556
Grace Klobacc634032009-07-28 15:58:19 -0700557 final String appId = intent
558 .getStringExtra(Browser.EXTRA_APPLICATION_ID);
559 if (Intent.ACTION_VIEW.equals(action)
560 && !getPackageName().equals(appId)
561 && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
Patrick Scottcd115892009-07-16 09:42:58 -0400562 TabControl.Tab appTab = mTabControl.getTabFromId(appId);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700563 if (appTab != null) {
564 Log.i(LOGTAG, "Reusing tab for " + appId);
565 // Dismiss the subwindow if applicable.
566 dismissSubWindow(appTab);
567 // Since we might kill the WebView, remove it from the
568 // content view first.
569 removeTabFromContentView(appTab);
570 // Recreate the main WebView after destroying the old one.
571 // If the WebView has the same original url and is on that
572 // page, it can be reused.
573 boolean needsLoad =
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700574 mTabControl.recreateWebView(appTab, urlData.mUrl);
Ben Murdochbff2d602009-07-01 20:19:05 +0100575
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700576 if (current != appTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -0400577 switchToTab(mTabControl.getTabIndex(appTab));
578 if (needsLoad) {
579 urlData.loadIn(appTab.getWebView());
580 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700581 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -0400582 // If the tab was the current tab, we have to attach
583 // it to the view system again.
584 attachTabToContentView(appTab);
585 if (needsLoad) {
586 urlData.loadIn(appTab.getWebView());
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700587 }
588 }
589 return;
Patrick Scottcd115892009-07-16 09:42:58 -0400590 } else {
591 // No matching application tab, try to find a regular tab
592 // with a matching url.
593 appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400594 if (appTab != null && current != appTab) {
595 switchToTab(mTabControl.getTabIndex(appTab));
Patrick Scottcd115892009-07-16 09:42:58 -0400596 } else {
597 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
598 // will be opened in a new tab unless we have reached
599 // MAX_TABS. Then the url will be opened in the current
600 // tab. If a new tab is created, it will have "true" for
601 // exit on close.
Leon Scroggins1f005d32009-08-10 17:36:42 -0400602 openTabAndShow(urlData, true, appId);
Patrick Scottcd115892009-07-16 09:42:58 -0400603 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700604 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800605 } else {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700606 if ("about:debug".equals(urlData.mUrl)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800607 mSettings.toggleDebugSettings();
608 return;
609 }
Leon Scroggins1f005d32009-08-10 17:36:42 -0400610 // Get rid of the subwindow if it exists
611 dismissSubWindow(current);
612 urlData.loadIn(current.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800613 }
614 }
615 }
616
Satish Sampath565505b2009-05-29 15:37:27 +0100617 private int parseUrlShortcut(String url) {
618 if (url == null) return SHORTCUT_INVALID;
619
620 // FIXME: quick search, need to be customized by setting
621 if (url.length() > 2 && url.charAt(1) == ' ') {
622 switch (url.charAt(0)) {
623 case 'g': return SHORTCUT_GOOGLE_SEARCH;
624 case 'w': return SHORTCUT_WIKIPEDIA_SEARCH;
625 case 'd': return SHORTCUT_DICTIONARY_SEARCH;
626 case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH;
627 }
628 }
629 return SHORTCUT_INVALID;
630 }
631
632 /**
633 * Launches the default web search activity with the query parameters if the given intent's data
634 * are identified as plain search terms and not URLs/shortcuts.
635 * @return true if the intent was handled and web search activity was launched, false if not.
636 */
637 private boolean handleWebSearchIntent(Intent intent) {
638 if (intent == null) return false;
639
640 String url = null;
641 final String action = intent.getAction();
642 if (Intent.ACTION_VIEW.equals(action)) {
643 url = intent.getData().toString();
644 } else if (Intent.ACTION_SEARCH.equals(action)
645 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
646 || Intent.ACTION_WEB_SEARCH.equals(action)) {
647 url = intent.getStringExtra(SearchManager.QUERY);
648 }
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100649 return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA));
Satish Sampath565505b2009-05-29 15:37:27 +0100650 }
651
652 /**
653 * Launches the default web search activity with the query parameters if the given url string
654 * was identified as plain search terms and not URL/shortcut.
655 * @return true if the request was handled and web search activity was launched, false if not.
656 */
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100657 private boolean handleWebSearchRequest(String inUrl, Bundle appData) {
Satish Sampath565505b2009-05-29 15:37:27 +0100658 if (inUrl == null) return false;
659
660 // In general, we shouldn't modify URL from Intent.
661 // But currently, we get the user-typed URL from search box as well.
662 String url = fixUrl(inUrl).trim();
663
664 // URLs and site specific search shortcuts are handled by the regular flow of control, so
665 // return early.
666 if (Regex.WEB_URL_PATTERN.matcher(url).matches()
Satish Sampathbc5b9f32009-06-04 18:21:40 +0100667 || ACCEPTED_URI_SCHEMA.matcher(url).matches()
Satish Sampath565505b2009-05-29 15:37:27 +0100668 || parseUrlShortcut(url) != SHORTCUT_INVALID) {
669 return false;
670 }
671
672 Browser.updateVisitedHistory(mResolver, url, false);
673 Browser.addSearchUrl(mResolver, url);
674
675 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
676 intent.addCategory(Intent.CATEGORY_DEFAULT);
677 intent.putExtra(SearchManager.QUERY, url);
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100678 if (appData != null) {
679 intent.putExtra(SearchManager.APP_DATA, appData);
680 }
Grace Klobacc634032009-07-28 15:58:19 -0700681 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
Satish Sampath565505b2009-05-29 15:37:27 +0100682 startActivity(intent);
683
684 return true;
685 }
686
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700687 private UrlData getUrlDataFromIntent(Intent intent) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800688 String url = null;
689 if (intent != null) {
690 final String action = intent.getAction();
691 if (Intent.ACTION_VIEW.equals(action)) {
692 url = smartUrlFilter(intent.getData());
693 if (url != null && url.startsWith("content:")) {
694 /* Append mimetype so webview knows how to display */
695 String mimeType = intent.resolveType(getContentResolver());
696 if (mimeType != null) {
697 url += "?" + mimeType;
698 }
699 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700700 if ("inline:".equals(url)) {
701 return new InlinedUrlData(
702 intent.getStringExtra(Browser.EXTRA_INLINE_CONTENT),
703 intent.getType(),
704 intent.getStringExtra(Browser.EXTRA_INLINE_ENCODING),
705 intent.getStringExtra(Browser.EXTRA_INLINE_FAILURL));
706 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800707 } else if (Intent.ACTION_SEARCH.equals(action)
708 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
709 || Intent.ACTION_WEB_SEARCH.equals(action)) {
710 url = intent.getStringExtra(SearchManager.QUERY);
711 if (url != null) {
712 mLastEnteredUrl = url;
713 // Don't add Urls, just search terms.
714 // Urls will get added when the page is loaded.
715 if (!Regex.WEB_URL_PATTERN.matcher(url).matches()) {
716 Browser.updateVisitedHistory(mResolver, url, false);
717 }
718 // In general, we shouldn't modify URL from Intent.
719 // But currently, we get the user-typed URL from search box as well.
720 url = fixUrl(url);
721 url = smartUrlFilter(url);
722 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
723 if (url.contains(searchSource)) {
724 String source = null;
725 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
726 if (appData != null) {
727 source = appData.getString(SearchManager.SOURCE);
728 }
729 if (TextUtils.isEmpty(source)) {
730 source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
731 }
732 url = url.replace(searchSource, "&source=android-"+source+"&");
733 }
734 }
735 }
736 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700737 return new UrlData(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800738 }
739
740 /* package */ static String fixUrl(String inUrl) {
741 if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
742 return inUrl;
743 if (inUrl.startsWith("http:") ||
744 inUrl.startsWith("https:")) {
745 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
746 inUrl = inUrl.replaceFirst("/", "//");
747 } else inUrl = inUrl.replaceFirst(":", "://");
748 }
749 return inUrl;
750 }
751
752 /**
753 * Looking for the pattern like this
754 *
755 * *
756 * * *
757 * *** * *******
758 * * *
759 * * *
760 * *
761 */
762 private final SensorListener mSensorListener = new SensorListener() {
763 private long mLastGestureTime;
764 private float[] mPrev = new float[3];
765 private float[] mPrevDiff = new float[3];
766 private float[] mDiff = new float[3];
767 private float[] mRevertDiff = new float[3];
768
769 public void onSensorChanged(int sensor, float[] values) {
770 boolean show = false;
771 float[] diff = new float[3];
772
773 for (int i = 0; i < 3; i++) {
774 diff[i] = values[i] - mPrev[i];
775 if (Math.abs(diff[i]) > 1) {
776 show = true;
777 }
778 if ((diff[i] > 1.0 && mDiff[i] < 0.2)
779 || (diff[i] < -1.0 && mDiff[i] > -0.2)) {
780 // start track when there is a big move, or revert
781 mRevertDiff[i] = mDiff[i];
782 mDiff[i] = 0;
783 } else if (diff[i] > -0.2 && diff[i] < 0.2) {
784 // reset when it is flat
785 mDiff[i] = mRevertDiff[i] = 0;
786 }
787 mDiff[i] += diff[i];
788 mPrevDiff[i] = diff[i];
789 mPrev[i] = values[i];
790 }
791
792 if (false) {
793 // only shows if we think the delta is big enough, in an attempt
794 // to detect "serious" moves left/right or up/down
795 Log.d("BrowserSensorHack", "sensorChanged " + sensor + " ("
796 + values[0] + ", " + values[1] + ", " + values[2] + ")"
797 + " diff(" + diff[0] + " " + diff[1] + " " + diff[2]
798 + ")");
799 Log.d("BrowserSensorHack", " mDiff(" + mDiff[0] + " "
800 + mDiff[1] + " " + mDiff[2] + ")" + " mRevertDiff("
801 + mRevertDiff[0] + " " + mRevertDiff[1] + " "
802 + mRevertDiff[2] + ")");
803 }
804
805 long now = android.os.SystemClock.uptimeMillis();
806 if (now - mLastGestureTime > 1000) {
807 mLastGestureTime = 0;
808
809 float y = mDiff[1];
810 float z = mDiff[2];
811 float ay = Math.abs(y);
812 float az = Math.abs(z);
813 float ry = mRevertDiff[1];
814 float rz = mRevertDiff[2];
815 float ary = Math.abs(ry);
816 float arz = Math.abs(rz);
817 boolean gestY = ay > 2.5f && ary > 1.0f && ay > ary;
818 boolean gestZ = az > 3.5f && arz > 1.0f && az > arz;
819
820 if ((gestY || gestZ) && !(gestY && gestZ)) {
821 WebView view = mTabControl.getCurrentWebView();
822
823 if (view != null) {
824 if (gestZ) {
825 if (z < 0) {
826 view.zoomOut();
827 } else {
828 view.zoomIn();
829 }
830 } else {
831 view.flingScroll(0, Math.round(y * 100));
832 }
833 }
834 mLastGestureTime = now;
835 }
836 }
837 }
838
839 public void onAccuracyChanged(int sensor, int accuracy) {
840 // TODO Auto-generated method stub
841
842 }
843 };
844
845 @Override protected void onResume() {
846 super.onResume();
Dave Bort31a6d1c2009-04-13 15:56:49 -0700847 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800848 Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
849 }
850
851 if (!mActivityInPause) {
852 Log.e(LOGTAG, "BrowserActivity is already resumed.");
853 return;
854 }
855
Mike Reed7bfa63b2009-05-28 11:08:32 -0400856 mTabControl.resumeCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800857 mActivityInPause = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400858 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800859
860 if (mWakeLock.isHeld()) {
861 mHandler.removeMessages(RELEASE_WAKELOCK);
862 mWakeLock.release();
863 }
864
865 if (mCredsDlg != null) {
866 if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
867 // In case credential request never comes back
868 mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
869 }
870 }
871
872 registerReceiver(mNetworkStateIntentReceiver,
873 mNetworkStateChangedFilter);
874 WebView.enablePlatformNotifications();
875
876 if (mSettings.doFlick()) {
877 if (mSensorManager == null) {
878 mSensorManager = (SensorManager) getSystemService(
879 Context.SENSOR_SERVICE);
880 }
881 mSensorManager.registerListener(mSensorListener,
882 SensorManager.SENSOR_ACCELEROMETER,
883 SensorManager.SENSOR_DELAY_FASTEST);
884 } else {
885 mSensorManager = null;
886 }
887 }
888
889 /**
890 * onSaveInstanceState(Bundle map)
891 * onSaveInstanceState is called right before onStop(). The map contains
892 * the saved state.
893 */
894 @Override protected void onSaveInstanceState(Bundle outState) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700895 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800896 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
897 }
898 // the default implementation requires each view to have an id. As the
899 // browser handles the state itself and it doesn't use id for the views,
900 // don't call the default implementation. Otherwise it will trigger the
901 // warning like this, "couldn't save which view has focus because the
902 // focused view XXX has no id".
903
904 // Save all the tabs
905 mTabControl.saveState(outState);
906 }
907
908 @Override protected void onPause() {
909 super.onPause();
910
911 if (mActivityInPause) {
912 Log.e(LOGTAG, "BrowserActivity is already paused.");
913 return;
914 }
915
Mike Reed7bfa63b2009-05-28 11:08:32 -0400916 mTabControl.pauseCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800917 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400918 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800919 mWakeLock.acquire();
920 mHandler.sendMessageDelayed(mHandler
921 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
922 }
923
924 // Clear the credentials toast if it is up
925 if (mCredsDlg != null && mCredsDlg.isShowing()) {
926 mCredsDlg.dismiss();
927 }
928 mCredsDlg = null;
929
930 cancelStopToast();
931
932 // unregister network state listener
933 unregisterReceiver(mNetworkStateIntentReceiver);
934 WebView.disablePlatformNotifications();
935
936 if (mSensorManager != null) {
937 mSensorManager.unregisterListener(mSensorListener);
938 }
939 }
940
941 @Override protected void onDestroy() {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700942 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800943 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
944 }
945 super.onDestroy();
946 // Remove the current tab and sub window
947 TabControl.Tab t = mTabControl.getCurrentTab();
Patrick Scottfb5e77f2009-04-08 19:17:37 -0700948 if (t != null) {
949 dismissSubWindow(t);
950 removeTabFromContentView(t);
951 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800952 // Destroy all the tabs
953 mTabControl.destroy();
954 WebIconDatabase.getInstance().close();
955 if (mGlsConnection != null) {
956 unbindService(mGlsConnection);
957 mGlsConnection = null;
958 }
959
960 //
961 // stop MASF proxy service
962 //
963 //Intent proxyServiceIntent = new Intent();
964 //proxyServiceIntent.setComponent
965 // (new ComponentName(
966 // "com.android.masfproxyservice",
967 // "com.android.masfproxyservice.MasfProxyService"));
968 //stopService(proxyServiceIntent);
Grace Klobab4da0ad2009-05-14 14:45:40 -0700969
970 unregisterReceiver(mPackageInstallationReceiver);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800971 }
972
973 @Override
974 public void onConfigurationChanged(Configuration newConfig) {
975 super.onConfigurationChanged(newConfig);
976
977 if (mPageInfoDialog != null) {
978 mPageInfoDialog.dismiss();
979 showPageInfo(
980 mPageInfoView,
981 mPageInfoFromShowSSLCertificateOnError.booleanValue());
982 }
983 if (mSSLCertificateDialog != null) {
984 mSSLCertificateDialog.dismiss();
985 showSSLCertificate(
986 mSSLCertificateView);
987 }
988 if (mSSLCertificateOnErrorDialog != null) {
989 mSSLCertificateOnErrorDialog.dismiss();
990 showSSLCertificateOnError(
991 mSSLCertificateOnErrorView,
992 mSSLCertificateOnErrorHandler,
993 mSSLCertificateOnErrorError);
994 }
995 if (mHttpAuthenticationDialog != null) {
996 String title = ((TextView) mHttpAuthenticationDialog
997 .findViewById(com.android.internal.R.id.alertTitle)).getText()
998 .toString();
999 String name = ((TextView) mHttpAuthenticationDialog
1000 .findViewById(R.id.username_edit)).getText().toString();
1001 String password = ((TextView) mHttpAuthenticationDialog
1002 .findViewById(R.id.password_edit)).getText().toString();
1003 int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1004 .getId();
1005 mHttpAuthenticationDialog.dismiss();
1006 showHttpAuthentication(mHttpAuthHandler, null, null, title,
1007 name, password, focusId);
1008 }
1009 if (mFindDialog != null && mFindDialog.isShowing()) {
1010 mFindDialog.onConfigurationChanged(newConfig);
1011 }
1012 }
1013
1014 @Override public void onLowMemory() {
1015 super.onLowMemory();
1016 mTabControl.freeMemory();
1017 }
1018
Mike Reed7bfa63b2009-05-28 11:08:32 -04001019 private boolean resumeWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001020 if ((!mActivityInPause && !mPageStarted) ||
1021 (mActivityInPause && mPageStarted)) {
1022 CookieSyncManager.getInstance().startSync();
1023 WebView w = mTabControl.getCurrentWebView();
1024 if (w != null) {
1025 w.resumeTimers();
1026 }
1027 return true;
1028 } else {
1029 return false;
1030 }
1031 }
1032
Mike Reed7bfa63b2009-05-28 11:08:32 -04001033 private boolean pauseWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001034 if (mActivityInPause && !mPageStarted) {
1035 CookieSyncManager.getInstance().stopSync();
1036 WebView w = mTabControl.getCurrentWebView();
1037 if (w != null) {
1038 w.pauseTimers();
1039 }
1040 return true;
1041 } else {
1042 return false;
1043 }
1044 }
1045
Leon Scroggins1f005d32009-08-10 17:36:42 -04001046 // FIXME: Do we want to call this when loading google for the first time?
The Android Open Source Project0c908882009-03-03 19:32:16 -08001047 /*
1048 * This function is called when we are launching for the first time. We
1049 * are waiting for the login credentials before loading Google home
1050 * pages. This way the user will be logged in straight away.
1051 */
1052 private void waitForCredentials() {
1053 // Show a toast
1054 mCredsDlg = new ProgressDialog(this);
1055 mCredsDlg.setIndeterminate(true);
1056 mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1057 // If the user cancels the operation, then cancel the Google
1058 // Credentials request.
1059 mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1060 mCredsDlg.show();
1061
1062 // We set a timeout for the retrieval of credentials in onResume()
1063 // as that is when we have freed up some CPU time to get
1064 // the login credentials.
1065 }
1066
1067 /*
1068 * If we have received the credentials or we have timed out and we are
1069 * showing the credentials dialog, then it is time to move on.
1070 */
1071 private void resumeAfterCredentials() {
1072 if (mCredsDlg == null) {
1073 return;
1074 }
1075
1076 // Clear the toast
1077 if (mCredsDlg.isShowing()) {
1078 mCredsDlg.dismiss();
1079 }
1080 mCredsDlg = null;
1081
1082 // Clear any pending timeout
1083 mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1084
1085 // Load the page
1086 WebView w = mTabControl.getCurrentWebView();
1087 if (w != null) {
1088 w.loadUrl(mSettings.getHomePage());
1089 }
1090
1091 // Update the settings, need to do this last as it can take a moment
1092 // to persist the settings. In the mean time we could be loading
1093 // content.
1094 mSettings.setLoginInitialized(this);
1095 }
1096
1097 // Open the icon database and retain all the icons for visited sites.
1098 private void retainIconsOnStartup() {
1099 final WebIconDatabase db = WebIconDatabase.getInstance();
1100 db.open(getDir("icons", 0).getPath());
1101 try {
1102 Cursor c = Browser.getAllBookmarks(mResolver);
1103 if (!c.moveToFirst()) {
1104 c.deactivate();
1105 return;
1106 }
1107 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1108 do {
1109 String url = c.getString(urlIndex);
1110 db.retainIconForPageUrl(url);
1111 } while (c.moveToNext());
1112 c.deactivate();
1113 } catch (IllegalStateException e) {
1114 Log.e(LOGTAG, "retainIconsOnStartup", e);
1115 }
1116 }
1117
1118 // Helper method for getting the top window.
1119 WebView getTopWindow() {
1120 return mTabControl.getCurrentTopWebView();
1121 }
1122
1123 @Override
1124 public boolean onCreateOptionsMenu(Menu menu) {
1125 super.onCreateOptionsMenu(menu);
1126
1127 MenuInflater inflater = getMenuInflater();
1128 inflater.inflate(R.menu.browser, menu);
1129 mMenu = menu;
1130 updateInLoadMenuItems();
1131 return true;
1132 }
1133
1134 /**
1135 * As the menu can be open when loading state changes
1136 * we must manually update the state of the stop/reload menu
1137 * item
1138 */
1139 private void updateInLoadMenuItems() {
1140 if (mMenu == null) {
1141 return;
1142 }
1143 MenuItem src = mInLoad ?
1144 mMenu.findItem(R.id.stop_menu_id):
1145 mMenu.findItem(R.id.reload_menu_id);
1146 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1147 dest.setIcon(src.getIcon());
1148 dest.setTitle(src.getTitle());
1149 }
1150
1151 @Override
1152 public boolean onContextItemSelected(MenuItem item) {
1153 // chording is not an issue with context menus, but we use the same
1154 // options selector, so set mCanChord to true so we can access them.
1155 mCanChord = true;
1156 int id = item.getItemId();
1157 final WebView webView = getTopWindow();
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001158 if (null == webView) {
1159 return false;
1160 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001161 final HashMap hrefMap = new HashMap();
1162 hrefMap.put("webview", webView);
1163 final Message msg = mHandler.obtainMessage(
1164 FOCUS_NODE_HREF, id, 0, hrefMap);
1165 switch (id) {
1166 // -- Browser context menu
1167 case R.id.open_context_menu_id:
1168 case R.id.open_newtab_context_menu_id:
1169 case R.id.bookmark_context_menu_id:
1170 case R.id.save_link_context_menu_id:
1171 case R.id.share_link_context_menu_id:
1172 case R.id.copy_link_context_menu_id:
1173 webView.requestFocusNodeHref(msg);
1174 break;
1175
1176 default:
1177 // For other context menus
1178 return onOptionsItemSelected(item);
1179 }
1180 mCanChord = false;
1181 return true;
1182 }
1183
1184 private Bundle createGoogleSearchSourceBundle(String source) {
1185 Bundle bundle = new Bundle();
1186 bundle.putString(SearchManager.SOURCE, source);
1187 return bundle;
1188 }
1189
1190 /**
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001191 * Overriding this to insert a local information bundle
The Android Open Source Project0c908882009-03-03 19:32:16 -08001192 */
1193 @Override
1194 public boolean onSearchRequested() {
Leon Scroggins5bbe9802009-07-31 13:10:55 -04001195 String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
Grace Kloba83f47342009-07-20 10:44:31 -07001196 startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001197 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001198 return true;
1199 }
1200
1201 @Override
1202 public void startSearch(String initialQuery, boolean selectInitialQuery,
1203 Bundle appSearchData, boolean globalSearch) {
1204 if (appSearchData == null) {
1205 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1206 }
1207 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1208 }
1209
Leon Scroggins1f005d32009-08-10 17:36:42 -04001210 /**
1211 * Switch tabs. Called by the TitleBarSet when sliding the title bar
1212 * results in changing tabs.
1213 */
1214 /* package */ void switchToTab(int index) {
1215 TabControl.Tab tab = mTabControl.getTab(index);
1216 TabControl.Tab currentTab = mTabControl.getCurrentTab();
1217 if (tab == null || tab == currentTab) {
1218 return;
1219 }
1220 if (currentTab != null) {
1221 // currentTab may be null if it was just removed. In that case,
1222 // we do not need to remove it
1223 removeTabFromContentView(currentTab);
1224 }
1225 removeTabFromContentView(tab);
1226 mTabControl.setCurrentTab(tab);
1227 attachTabToContentView(tab);
1228 }
1229
1230 /* package */ void closeCurrentWindow() {
1231 final int currentIndex = mTabControl.getCurrentIndex();
1232 final TabControl.Tab current = mTabControl.getCurrentTab();
1233 final TabControl.Tab parent = current.getParentTab();
1234 // FIXME: With the new tabbed title bar, we will want to move to the
1235 // next tab to the right
1236 int indexToShow = -1;
1237 if (parent != null) {
1238 indexToShow = mTabControl.getTabIndex(parent);
1239 } else {
1240 // Get the last tab in the list. If it is the current tab,
1241 // subtract 1 more.
1242 indexToShow = mTabControl.getTabCount() - 1;
1243 if (currentIndex == indexToShow) {
1244 indexToShow--;
1245 }
1246 }
1247 switchToTab(indexToShow);
1248 // Close window
1249 closeTab(current);
1250 }
1251
The Android Open Source Project0c908882009-03-03 19:32:16 -08001252 @Override
1253 public boolean onOptionsItemSelected(MenuItem item) {
1254 if (!mCanChord) {
1255 // The user has already fired a shortcut with this hold down of the
1256 // menu key.
1257 return false;
1258 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001259 if (null == getTopWindow()) {
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001260 return false;
1261 }
Grace Kloba6ee9c492009-07-13 10:04:34 -07001262 if (mMenuIsDown) {
1263 // The shortcut action consumes the MENU. Even if it is still down,
1264 // it won't trigger the next shortcut action. In the case of the
1265 // shortcut action triggering a new activity, like Bookmarks, we
1266 // won't get onKeyUp for MENU. So it is important to reset it here.
1267 mMenuIsDown = false;
1268 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001269 switch (item.getItemId()) {
1270 // -- Main menu
Leon Scroggins64b80f32009-08-07 12:03:34 -04001271 case R.id.goto_menu_id:
The Android Open Source Project0c908882009-03-03 19:32:16 -08001272 bookmarksOrHistoryPicker(false);
1273 break;
1274
Leon Scroggins1f005d32009-08-10 17:36:42 -04001275 case R.id.add_bookmark_menu_id:
1276 Intent i = new Intent(BrowserActivity.this,
1277 AddBookmarkPage.class);
1278 WebView w = getTopWindow();
1279 i.putExtra("url", w.getUrl());
1280 i.putExtra("title", w.getTitle());
1281 startActivity(i);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001282 break;
1283
1284 case R.id.stop_reload_menu_id:
1285 if (mInLoad) {
1286 stopLoading();
1287 } else {
1288 getTopWindow().reload();
1289 }
1290 break;
1291
1292 case R.id.back_menu_id:
1293 getTopWindow().goBack();
1294 break;
1295
1296 case R.id.forward_menu_id:
1297 getTopWindow().goForward();
1298 break;
1299
1300 case R.id.close_menu_id:
1301 // Close the subwindow if it exists.
1302 if (mTabControl.getCurrentSubWindow() != null) {
1303 dismissSubWindow(mTabControl.getCurrentTab());
1304 break;
1305 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001306 closeCurrentWindow();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001307 break;
1308
1309 case R.id.homepage_menu_id:
1310 TabControl.Tab current = mTabControl.getCurrentTab();
1311 if (current != null) {
1312 dismissSubWindow(current);
1313 current.getWebView().loadUrl(mSettings.getHomePage());
1314 }
1315 break;
1316
1317 case R.id.preferences_menu_id:
1318 Intent intent = new Intent(this,
1319 BrowserPreferencesPage.class);
1320 startActivityForResult(intent, PREFERENCES_PAGE);
1321 break;
1322
1323 case R.id.find_menu_id:
1324 if (null == mFindDialog) {
1325 mFindDialog = new FindDialog(this);
1326 }
1327 mFindDialog.setWebView(getTopWindow());
1328 mFindDialog.show();
1329 mMenuState = EMPTY_MENU;
1330 break;
1331
1332 case R.id.select_text_id:
1333 getTopWindow().emulateShiftHeld();
1334 break;
1335 case R.id.page_info_menu_id:
1336 showPageInfo(mTabControl.getCurrentTab(), false);
1337 break;
1338
1339 case R.id.classic_history_menu_id:
1340 bookmarksOrHistoryPicker(true);
1341 break;
1342
1343 case R.id.share_page_menu_id:
1344 Browser.sendString(this, getTopWindow().getUrl());
1345 break;
1346
1347 case R.id.dump_nav_menu_id:
1348 getTopWindow().debugDump();
1349 break;
1350
1351 case R.id.zoom_in_menu_id:
1352 getTopWindow().zoomIn();
1353 break;
1354
1355 case R.id.zoom_out_menu_id:
1356 getTopWindow().zoomOut();
1357 break;
1358
1359 case R.id.view_downloads_menu_id:
1360 viewDownloads(null);
1361 break;
1362
The Android Open Source Project0c908882009-03-03 19:32:16 -08001363 case R.id.window_one_menu_id:
1364 case R.id.window_two_menu_id:
1365 case R.id.window_three_menu_id:
1366 case R.id.window_four_menu_id:
1367 case R.id.window_five_menu_id:
1368 case R.id.window_six_menu_id:
1369 case R.id.window_seven_menu_id:
1370 case R.id.window_eight_menu_id:
1371 {
1372 int menuid = item.getItemId();
1373 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1374 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1375 TabControl.Tab desiredTab = mTabControl.getTab(id);
1376 if (desiredTab != null &&
1377 desiredTab != mTabControl.getCurrentTab()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001378 switchToTab(id);
1379 mTitleBar.setCurrentTab(id);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001380 }
1381 break;
1382 }
1383 }
1384 }
1385 break;
1386
1387 default:
1388 if (!super.onOptionsItemSelected(item)) {
1389 return false;
1390 }
1391 // Otherwise fall through.
1392 }
1393 mCanChord = false;
1394 return true;
1395 }
1396
1397 public void closeFind() {
1398 mMenuState = R.id.MAIN_MENU;
1399 }
1400
1401 @Override public boolean onPrepareOptionsMenu(Menu menu)
1402 {
1403 // This happens when the user begins to hold down the menu key, so
1404 // allow them to chord to get a shortcut.
1405 mCanChord = true;
1406 // Note: setVisible will decide whether an item is visible; while
1407 // setEnabled() will decide whether an item is enabled, which also means
1408 // whether the matching shortcut key will function.
1409 super.onPrepareOptionsMenu(menu);
1410 switch (mMenuState) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001411 case EMPTY_MENU:
1412 if (mCurrentMenuState != mMenuState) {
1413 menu.setGroupVisible(R.id.MAIN_MENU, false);
1414 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1415 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001416 }
1417 break;
1418 default:
1419 if (mCurrentMenuState != mMenuState) {
1420 menu.setGroupVisible(R.id.MAIN_MENU, true);
1421 menu.setGroupEnabled(R.id.MAIN_MENU, true);
1422 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001423 }
1424 final WebView w = getTopWindow();
1425 boolean canGoBack = false;
1426 boolean canGoForward = false;
1427 boolean isHome = false;
1428 if (w != null) {
1429 canGoBack = w.canGoBack();
1430 canGoForward = w.canGoForward();
1431 isHome = mSettings.getHomePage().equals(w.getUrl());
1432 }
1433 final MenuItem back = menu.findItem(R.id.back_menu_id);
1434 back.setEnabled(canGoBack);
1435
1436 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1437 home.setEnabled(!isHome);
1438
1439 menu.findItem(R.id.forward_menu_id)
1440 .setEnabled(canGoForward);
1441
1442 // decide whether to show the share link option
1443 PackageManager pm = getPackageManager();
1444 Intent send = new Intent(Intent.ACTION_SEND);
1445 send.setType("text/plain");
1446 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1447 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1448
The Android Open Source Project0c908882009-03-03 19:32:16 -08001449 boolean isNavDump = mSettings.isNavDump();
1450 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1451 nav.setVisible(isNavDump);
1452 nav.setEnabled(isNavDump);
1453 break;
1454 }
1455 mCurrentMenuState = mMenuState;
1456 return true;
1457 }
1458
1459 @Override
1460 public void onCreateContextMenu(ContextMenu menu, View v,
1461 ContextMenuInfo menuInfo) {
1462 WebView webview = (WebView) v;
1463 WebView.HitTestResult result = webview.getHitTestResult();
1464 if (result == null) {
1465 return;
1466 }
1467
1468 int type = result.getType();
1469 if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1470 Log.w(LOGTAG,
1471 "We should not show context menu when nothing is touched");
1472 return;
1473 }
1474 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1475 // let TextView handles context menu
1476 return;
1477 }
1478
1479 // Note, http://b/issue?id=1106666 is requesting that
1480 // an inflated menu can be used again. This is not available
1481 // yet, so inflate each time (yuk!)
1482 MenuInflater inflater = getMenuInflater();
1483 inflater.inflate(R.menu.browsercontext, menu);
1484
1485 // Show the correct menu group
1486 String extra = result.getExtra();
1487 menu.setGroupVisible(R.id.PHONE_MENU,
1488 type == WebView.HitTestResult.PHONE_TYPE);
1489 menu.setGroupVisible(R.id.EMAIL_MENU,
1490 type == WebView.HitTestResult.EMAIL_TYPE);
1491 menu.setGroupVisible(R.id.GEO_MENU,
1492 type == WebView.HitTestResult.GEO_TYPE);
1493 menu.setGroupVisible(R.id.IMAGE_MENU,
1494 type == WebView.HitTestResult.IMAGE_TYPE
1495 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1496 menu.setGroupVisible(R.id.ANCHOR_MENU,
1497 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1498 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1499
1500 // Setup custom handling depending on the type
1501 switch (type) {
1502 case WebView.HitTestResult.PHONE_TYPE:
1503 menu.setHeaderTitle(Uri.decode(extra));
1504 menu.findItem(R.id.dial_context_menu_id).setIntent(
1505 new Intent(Intent.ACTION_VIEW, Uri
1506 .parse(WebView.SCHEME_TEL + extra)));
1507 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1508 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1509 addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1510 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1511 addIntent);
1512 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1513 new Copy(extra));
1514 break;
1515
1516 case WebView.HitTestResult.EMAIL_TYPE:
1517 menu.setHeaderTitle(extra);
1518 menu.findItem(R.id.email_context_menu_id).setIntent(
1519 new Intent(Intent.ACTION_VIEW, Uri
1520 .parse(WebView.SCHEME_MAILTO + extra)));
1521 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1522 new Copy(extra));
1523 break;
1524
1525 case WebView.HitTestResult.GEO_TYPE:
1526 menu.setHeaderTitle(extra);
1527 menu.findItem(R.id.map_context_menu_id).setIntent(
1528 new Intent(Intent.ACTION_VIEW, Uri
1529 .parse(WebView.SCHEME_GEO
1530 + URLEncoder.encode(extra))));
1531 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1532 new Copy(extra));
1533 break;
1534
1535 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1536 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1537 TextView titleView = (TextView) LayoutInflater.from(this)
1538 .inflate(android.R.layout.browser_link_context_header,
1539 null);
1540 titleView.setText(extra);
1541 menu.setHeaderView(titleView);
1542 // decide whether to show the open link in new tab option
1543 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1544 mTabControl.getTabCount() < TabControl.MAX_TABS);
1545 PackageManager pm = getPackageManager();
1546 Intent send = new Intent(Intent.ACTION_SEND);
1547 send.setType("text/plain");
1548 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1549 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1550 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1551 break;
1552 }
1553 // otherwise fall through to handle image part
1554 case WebView.HitTestResult.IMAGE_TYPE:
1555 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1556 menu.setHeaderTitle(extra);
1557 }
1558 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1559 new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1560 menu.findItem(R.id.download_context_menu_id).
1561 setOnMenuItemClickListener(new Download(extra));
1562 break;
1563
1564 default:
1565 Log.w(LOGTAG, "We should not get here.");
1566 break;
1567 }
1568 }
1569
The Android Open Source Project0c908882009-03-03 19:32:16 -08001570 // Attach the given tab to the content view.
1571 private void attachTabToContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001572 // Attach the container that contains the main WebView and any other UI
1573 // associated with the tab.
1574 mContentView.addView(t.getContainer(), COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +01001575
1576 if (mShouldShowErrorConsole) {
1577 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
1578 if (errorConsole.numberOfErrors() == 0) {
1579 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1580 } else {
1581 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1582 }
1583
1584 mErrorConsoleContainer.addView(errorConsole,
1585 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
1586 ViewGroup.LayoutParams.WRAP_CONTENT));
1587 }
1588
The Android Open Source Project0c908882009-03-03 19:32:16 -08001589 // Attach the sub window if necessary
1590 attachSubWindow(t);
1591 // Request focus on the top window.
1592 t.getTopWindow().requestFocus();
1593 }
1594
1595 // Attach a sub window to the main WebView of the given tab.
1596 private void attachSubWindow(TabControl.Tab t) {
1597 // If a sub window exists, attach it to the content view.
1598 final WebView subView = t.getSubWebView();
1599 if (subView != null) {
1600 final View container = t.getSubWebViewContainer();
1601 mContentView.addView(container, COVER_SCREEN_PARAMS);
1602 subView.requestFocus();
1603 }
1604 }
1605
1606 // Remove the given tab from the content view.
1607 private void removeTabFromContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001608 // Remove the container that contains the main WebView.
1609 mContentView.removeView(t.getContainer());
Ben Murdochbff2d602009-07-01 20:19:05 +01001610
1611 if (mTabControl.getCurrentErrorConsole(false) != null) {
1612 mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
1613 }
1614
The Android Open Source Project0c908882009-03-03 19:32:16 -08001615 // Remove the sub window if it exists.
1616 if (t.getSubWebView() != null) {
1617 mContentView.removeView(t.getSubWebViewContainer());
1618 }
1619 }
1620
1621 // Remove the sub window if it exists. Also called by TabControl when the
1622 // user clicks the 'X' to dismiss a sub window.
1623 /* package */ void dismissSubWindow(TabControl.Tab t) {
1624 final WebView mainView = t.getWebView();
1625 if (t.getSubWebView() != null) {
1626 // Remove the container view and request focus on the main WebView.
1627 mContentView.removeView(t.getSubWebViewContainer());
1628 mainView.requestFocus();
1629 // Tell the TabControl to dismiss the subwindow. This will destroy
1630 // the WebView.
1631 mTabControl.dismissSubWindow(t);
1632 }
1633 }
1634
Leon Scroggins1f005d32009-08-10 17:36:42 -04001635 // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001636 // that accepts url as string.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001637 private TabControl.Tab openTabAndShow(String url, boolean closeOnExit,
1638 String appId) {
1639 return openTabAndShow(new UrlData(url), closeOnExit, appId);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001640 }
1641
1642 // This method does a ton of stuff. It will attempt to create a new tab
1643 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
Leon Scroggins1f005d32009-08-10 17:36:42 -04001644 // url isn't null, it will load the given url.
1645 /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001646 boolean closeOnExit, String appId) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001647 final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1648 final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1649 if (newTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001650 final TabControl.Tab tab = mTabControl.createNewTab(
1651 closeOnExit, appId, urlData.mUrl);
1652 WebView webview = tab.getWebView();
1653 if (CUSTOM_BROWSER_BAR) {
1654 mTitleBar.addTab(webview, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001655 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001656 removeTabFromContentView(currentTab);
1657 attachTabToContentView(tab);
1658 if (urlData.isEmpty()) {
1659 bookmarksOrHistoryPicker(false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001660 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001661 urlData.loadIn(webview);
1662 }
1663 return tab;
1664 } else {
1665 // Get rid of the subwindow if it exists
1666 dismissSubWindow(currentTab);
1667 if (!urlData.isEmpty()) {
1668 // Load the given url.
1669 urlData.loadIn(currentTab.getWebView());
1670 } else {
1671 bookmarksOrHistoryPicker(false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001672 }
1673 }
Grace Klobac9181842009-04-14 08:53:22 -07001674 return currentTab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001675 }
1676
Grace Klobac9181842009-04-14 08:53:22 -07001677 private TabControl.Tab openTab(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001678 if (mSettings.openInBackground()) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001679 TabControl.Tab t = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001680 if (t != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001681 WebView view = t.getWebView();
1682 if (CUSTOM_BROWSER_BAR) {
1683 mTitleBar.addTab(view, false);
1684 }
1685 view.loadUrl(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001686 }
Grace Klobac9181842009-04-14 08:53:22 -07001687 return t;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001688 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001689 return openTabAndShow(url, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001690 }
1691 }
1692
1693 private class Copy implements OnMenuItemClickListener {
1694 private CharSequence mText;
1695
1696 public boolean onMenuItemClick(MenuItem item) {
1697 copy(mText);
1698 return true;
1699 }
1700
1701 public Copy(CharSequence toCopy) {
1702 mText = toCopy;
1703 }
1704 }
1705
1706 private class Download implements OnMenuItemClickListener {
1707 private String mText;
1708
1709 public boolean onMenuItemClick(MenuItem item) {
1710 onDownloadStartNoStream(mText, null, null, null, -1);
1711 return true;
1712 }
1713
1714 public Download(String toDownload) {
1715 mText = toDownload;
1716 }
1717 }
1718
1719 private void copy(CharSequence text) {
1720 try {
1721 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
1722 if (clip != null) {
1723 clip.setClipboardText(text);
1724 }
1725 } catch (android.os.RemoteException e) {
1726 Log.e(LOGTAG, "Copy failed", e);
1727 }
1728 }
1729
1730 /**
1731 * Resets the browser title-view to whatever it must be (for example, if we
1732 * load a page from history).
1733 */
1734 private void resetTitle() {
1735 resetLockIcon();
1736 resetTitleIconAndProgress();
1737 }
1738
1739 /**
1740 * Resets the browser title-view to whatever it must be
1741 * (for example, if we had a loading error)
1742 * When we have a new page, we call resetTitle, when we
1743 * have to reset the titlebar to whatever it used to be
1744 * (for example, if the user chose to stop loading), we
1745 * call resetTitleAndRevertLockIcon.
1746 */
1747 /* package */ void resetTitleAndRevertLockIcon() {
1748 revertLockIcon();
1749 resetTitleIconAndProgress();
1750 }
1751
1752 /**
1753 * Reset the title, favicon, and progress.
1754 */
1755 private void resetTitleIconAndProgress() {
1756 WebView current = mTabControl.getCurrentWebView();
1757 if (current == null) {
1758 return;
1759 }
1760 resetTitleAndIcon(current);
1761 int progress = current.getProgress();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001762 mWebChromeClient.onProgressChanged(current, progress);
1763 }
1764
1765 // Reset the title and the icon based on the given item.
1766 private void resetTitleAndIcon(WebView view) {
1767 WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
1768 if (item != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001769 setUrlTitle(item.getUrl(), item.getTitle(), view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001770 setFavicon(item.getFavicon());
1771 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001772 setUrlTitle(null, null, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001773 setFavicon(null);
1774 }
1775 }
1776
1777 /**
1778 * Sets a title composed of the URL and the title string.
1779 * @param url The URL of the site being loaded.
1780 * @param title The title of the site being loaded.
1781 */
Leon Scroggins1f005d32009-08-10 17:36:42 -04001782 private void setUrlTitle(String url, String title, WebView view) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001783 mUrl = url;
1784 mTitle = title;
1785
Leon Scroggins1f005d32009-08-10 17:36:42 -04001786 if (CUSTOM_BROWSER_BAR) {
1787 mTitleBar.setTitleAndUrl(title, url, view);
1788 } else {
1789 setTitle(buildUrlTitle(url, title));
The Android Open Source Project0c908882009-03-03 19:32:16 -08001790 }
1791 }
1792
1793 /**
1794 * Builds and returns the page title, which is some
1795 * combination of the page URL and title.
1796 * @param url The URL of the site being loaded.
1797 * @param title The title of the site being loaded.
1798 * @return The page title.
1799 */
1800 private String buildUrlTitle(String url, String title) {
1801 String urlTitle = "";
1802
1803 if (url != null) {
1804 String titleUrl = buildTitleUrl(url);
1805
1806 if (title != null && 0 < title.length()) {
1807 if (titleUrl != null && 0 < titleUrl.length()) {
1808 urlTitle = titleUrl + ": " + title;
1809 } else {
1810 urlTitle = title;
1811 }
1812 } else {
1813 if (titleUrl != null) {
1814 urlTitle = titleUrl;
1815 }
1816 }
1817 }
1818
1819 return urlTitle;
1820 }
1821
1822 /**
1823 * @param url The URL to build a title version of the URL from.
1824 * @return The title version of the URL or null if fails.
1825 * The title version of the URL can be either the URL hostname,
1826 * or the hostname with an "https://" prefix (for secure URLs),
1827 * or an empty string if, for example, the URL in question is a
1828 * file:// URL with no hostname.
1829 */
Leon Scroggins32e14a62009-06-11 10:26:34 -04001830 /* package */ static String buildTitleUrl(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001831 String titleUrl = null;
1832
1833 if (url != null) {
1834 try {
1835 // parse the url string
1836 URL urlObj = new URL(url);
1837 if (urlObj != null) {
1838 titleUrl = "";
1839
1840 String protocol = urlObj.getProtocol();
1841 String host = urlObj.getHost();
1842
1843 if (host != null && 0 < host.length()) {
1844 titleUrl = host;
1845 if (protocol != null) {
1846 // if a secure site, add an "https://" prefix!
1847 if (protocol.equalsIgnoreCase("https")) {
1848 titleUrl = protocol + "://" + host;
1849 }
1850 }
1851 }
1852 }
1853 } catch (MalformedURLException e) {}
1854 }
1855
1856 return titleUrl;
1857 }
1858
1859 // Set the favicon in the title bar.
1860 private void setFavicon(Bitmap icon) {
Leon Scroggins81db3662009-06-04 17:45:11 -04001861 if (CUSTOM_BROWSER_BAR) {
1862 Drawable[] array = new Drawable[3];
1863 array[0] = new PaintDrawable(Color.BLACK);
1864 PaintDrawable p = new PaintDrawable(Color.WHITE);
1865 array[1] = p;
1866 if (icon == null) {
1867 array[2] = mGenericFavicon;
1868 } else {
1869 array[2] = new BitmapDrawable(icon);
1870 }
1871 LayerDrawable d = new LayerDrawable(array);
1872 d.setLayerInset(1, 1, 1, 1, 1);
1873 d.setLayerInset(2, 2, 2, 2, 2);
Leon Scroggins1f005d32009-08-10 17:36:42 -04001874 mTitleBar.setFavicon(d, getTopWindow());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001875 } else {
Leon Scroggins81db3662009-06-04 17:45:11 -04001876 Drawable[] array = new Drawable[2];
1877 PaintDrawable p = new PaintDrawable(Color.WHITE);
1878 p.setCornerRadius(3f);
1879 array[0] = p;
1880 if (icon == null) {
1881 array[1] = mGenericFavicon;
1882 } else {
1883 array[1] = new BitmapDrawable(icon);
1884 }
1885 LayerDrawable d = new LayerDrawable(array);
1886 d.setLayerInset(1, 2, 2, 2, 2);
1887 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001888 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001889 }
1890
1891 /**
1892 * Saves the current lock-icon state before resetting
1893 * the lock icon. If we have an error, we may need to
1894 * roll back to the previous state.
1895 */
1896 private void saveLockIcon() {
1897 mPrevLockType = mLockIconType;
1898 }
1899
1900 /**
1901 * Reverts the lock-icon state to the last saved state,
1902 * for example, if we had an error, and need to cancel
1903 * the load.
1904 */
1905 private void revertLockIcon() {
1906 mLockIconType = mPrevLockType;
1907
Dave Bort31a6d1c2009-04-13 15:56:49 -07001908 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001909 Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
1910 " revert lock icon to " + mLockIconType);
1911 }
1912
1913 updateLockIconImage(mLockIconType);
1914 }
1915
Leon Scroggins1f005d32009-08-10 17:36:42 -04001916 /**
1917 * Close the tab after removing its associated title bar.
1918 */
1919 private void closeTab(TabControl.Tab t) {
1920 mTitleBar.removeTab(mTabControl.getTabIndex(t));
1921 mTabControl.removeTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001922 }
1923
1924 private void goBackOnePageOrQuit() {
1925 TabControl.Tab current = mTabControl.getCurrentTab();
1926 if (current == null) {
1927 /*
1928 * Instead of finishing the activity, simply push this to the back
1929 * of the stack and let ActivityManager to choose the foreground
1930 * activity. As BrowserActivity is singleTask, it will be always the
1931 * root of the task. So we can use either true or false for
1932 * moveTaskToBack().
1933 */
1934 moveTaskToBack(true);
1935 }
1936 WebView w = current.getWebView();
1937 if (w.canGoBack()) {
1938 w.goBack();
1939 } else {
1940 // Check to see if we are closing a window that was created by
1941 // another window. If so, we switch back to that window.
1942 TabControl.Tab parent = current.getParentTab();
1943 if (parent != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001944 switchToTab(mTabControl.getTabIndex(parent));
1945 // Now we close the other tab
1946 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001947 } else {
1948 if (current.closeOnExit()) {
1949 if (mTabControl.getTabCount() == 1) {
1950 finish();
1951 return;
1952 }
Mike Reed7bfa63b2009-05-28 11:08:32 -04001953 // call pauseWebViewTimers() now, we won't be able to call
1954 // it in onPause() as the WebView won't be valid.
1955 pauseWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001956 removeTabFromContentView(current);
1957 mTabControl.removeTab(current);
1958 }
1959 /*
1960 * Instead of finishing the activity, simply push this to the back
1961 * of the stack and let ActivityManager to choose the foreground
1962 * activity. As BrowserActivity is singleTask, it will be always the
1963 * root of the task. So we can use either true or false for
1964 * moveTaskToBack().
1965 */
1966 moveTaskToBack(true);
1967 }
1968 }
1969 }
1970
1971 public KeyTracker.State onKeyTracker(int keyCode,
1972 KeyEvent event,
1973 KeyTracker.Stage stage,
1974 int duration) {
1975 // if onKeyTracker() is called after activity onStop()
1976 // because of accumulated key events,
1977 // we should ignore it as browser is not active any more.
1978 WebView topWindow = getTopWindow();
Andrei Popescuadc008d2009-06-26 14:11:30 +01001979 if (topWindow == null && mCustomView == null)
The Android Open Source Project0c908882009-03-03 19:32:16 -08001980 return KeyTracker.State.NOT_TRACKING;
1981
1982 if (keyCode == KeyEvent.KEYCODE_BACK) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01001983 // Check if a custom view is currently showing and, if it is, hide it.
1984 if (mCustomView != null) {
1985 mWebChromeClient.onHideCustomView();
1986 return KeyTracker.State.DONE_TRACKING;
1987 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001988 if (stage == KeyTracker.Stage.LONG_REPEAT) {
1989 bookmarksOrHistoryPicker(true);
1990 return KeyTracker.State.DONE_TRACKING;
1991 } else if (stage == KeyTracker.Stage.UP) {
1992 // FIXME: Currently, we do not have a notion of the
1993 // history picker for the subwindow, but maybe we
1994 // should?
1995 WebView subwindow = mTabControl.getCurrentSubWindow();
1996 if (subwindow != null) {
1997 if (subwindow.canGoBack()) {
1998 subwindow.goBack();
1999 } else {
2000 dismissSubWindow(mTabControl.getCurrentTab());
2001 }
2002 } else {
2003 goBackOnePageOrQuit();
2004 }
2005 return KeyTracker.State.DONE_TRACKING;
2006 }
2007 return KeyTracker.State.KEEP_TRACKING;
2008 }
2009 return KeyTracker.State.NOT_TRACKING;
2010 }
2011
2012 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2013 if (keyCode == KeyEvent.KEYCODE_MENU) {
2014 mMenuIsDown = true;
Grace Kloba6ee9c492009-07-13 10:04:34 -07002015 } else if (mMenuIsDown) {
2016 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2017 // still down, we don't want to trigger the search. Pretend to
2018 // consume the key and do nothing.
2019 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002020 }
2021 boolean handled = mKeyTracker.doKeyDown(keyCode, event);
2022 if (!handled) {
2023 switch (keyCode) {
2024 case KeyEvent.KEYCODE_SPACE:
2025 if (event.isShiftPressed()) {
2026 getTopWindow().pageUp(false);
2027 } else {
2028 getTopWindow().pageDown(false);
2029 }
2030 handled = true;
2031 break;
2032
2033 default:
2034 break;
2035 }
2036 }
2037 return handled || super.onKeyDown(keyCode, event);
2038 }
2039
2040 @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2041 if (keyCode == KeyEvent.KEYCODE_MENU) {
2042 mMenuIsDown = false;
2043 }
2044 return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2045 }
2046
2047 private void stopLoading() {
2048 resetTitleAndRevertLockIcon();
2049 WebView w = getTopWindow();
2050 w.stopLoading();
2051 mWebViewClient.onPageFinished(w, w.getUrl());
2052
2053 cancelStopToast();
2054 mStopToast = Toast
2055 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2056 mStopToast.show();
2057 }
2058
2059 private void cancelStopToast() {
2060 if (mStopToast != null) {
2061 mStopToast.cancel();
2062 mStopToast = null;
2063 }
2064 }
2065
2066 // called by a non-UI thread to post the message
2067 public void postMessage(int what, int arg1, int arg2, Object obj) {
2068 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2069 }
2070
2071 // public message ids
2072 public final static int LOAD_URL = 1001;
2073 public final static int STOP_LOAD = 1002;
2074
2075 // Message Ids
2076 private static final int FOCUS_NODE_HREF = 102;
2077 private static final int CANCEL_CREDS_REQUEST = 103;
Grace Kloba92c18a52009-07-31 23:48:32 -07002078 private static final int RELEASE_WAKELOCK = 107;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002079
2080 // Private handler for handling javascript and saving passwords
2081 private Handler mHandler = new Handler() {
2082
2083 public void handleMessage(Message msg) {
2084 switch (msg.what) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002085 case FOCUS_NODE_HREF:
2086 String url = (String) msg.getData().get("url");
2087 if (url == null || url.length() == 0) {
2088 break;
2089 }
2090 HashMap focusNodeMap = (HashMap) msg.obj;
2091 WebView view = (WebView) focusNodeMap.get("webview");
2092 // Only apply the action if the top window did not change.
2093 if (getTopWindow() != view) {
2094 break;
2095 }
2096 switch (msg.arg1) {
2097 case R.id.open_context_menu_id:
2098 case R.id.view_image_context_menu_id:
2099 loadURL(getTopWindow(), url);
2100 break;
2101 case R.id.open_newtab_context_menu_id:
Grace Klobac9181842009-04-14 08:53:22 -07002102 final TabControl.Tab parent = mTabControl
2103 .getCurrentTab();
2104 final TabControl.Tab newTab = openTab(url);
2105 if (newTab != parent) {
2106 parent.addChildTab(newTab);
2107 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002108 break;
2109 case R.id.bookmark_context_menu_id:
2110 Intent intent = new Intent(BrowserActivity.this,
2111 AddBookmarkPage.class);
2112 intent.putExtra("url", url);
2113 startActivity(intent);
2114 break;
2115 case R.id.share_link_context_menu_id:
2116 Browser.sendString(BrowserActivity.this, url);
2117 break;
2118 case R.id.copy_link_context_menu_id:
2119 copy(url);
2120 break;
2121 case R.id.save_link_context_menu_id:
2122 case R.id.download_context_menu_id:
2123 onDownloadStartNoStream(url, null, null, null, -1);
2124 break;
2125 }
2126 break;
2127
2128 case LOAD_URL:
2129 loadURL(getTopWindow(), (String) msg.obj);
2130 break;
2131
2132 case STOP_LOAD:
2133 stopLoading();
2134 break;
2135
2136 case CANCEL_CREDS_REQUEST:
2137 resumeAfterCredentials();
2138 break;
2139
The Android Open Source Project0c908882009-03-03 19:32:16 -08002140 case RELEASE_WAKELOCK:
2141 if (mWakeLock.isHeld()) {
2142 mWakeLock.release();
2143 }
2144 break;
2145 }
2146 }
2147 };
2148
Leon Scroggins89c6d362009-07-15 16:54:37 -04002149 private void updateScreenshot(WebView view) {
2150 // If this is a bookmarked site, add a screenshot to the database.
2151 // FIXME: When should we update? Every time?
2152 // FIXME: Would like to make sure there is actually something to
2153 // draw, but the API for that (WebViewCore.pictureReady()) is not
2154 // currently accessible here.
Patrick Scott3918d442009-08-04 13:22:29 -04002155 ContentResolver cr = getContentResolver();
2156 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04002157 cr, view.getOriginalUrl(), view.getUrl(), false);
Patrick Scott3918d442009-08-04 13:22:29 -04002158 if (c != null) {
Leon Scroggins89c6d362009-07-15 16:54:37 -04002159 boolean succeed = c.moveToFirst();
2160 ContentValues values = null;
2161 while (succeed) {
2162 if (values == null) {
2163 final ByteArrayOutputStream os
2164 = new ByteArrayOutputStream();
2165 Picture thumbnail = view.capturePicture();
2166 // Keep width and height in sync with BrowserBookmarksPage
2167 // and bookmark_thumb
2168 Bitmap bm = Bitmap.createBitmap(100, 80,
2169 Bitmap.Config.ARGB_4444);
2170 Canvas canvas = new Canvas(bm);
2171 // May need to tweak these values to determine what is the
2172 // best scale factor
2173 canvas.scale(.5f, .5f);
2174 thumbnail.draw(canvas);
2175 bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2176 values = new ContentValues();
2177 values.put(Browser.BookmarkColumns.THUMBNAIL,
2178 os.toByteArray());
2179 }
2180 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2181 c.getInt(0)), values, null, null);
2182 succeed = c.moveToNext();
2183 }
2184 c.close();
2185 }
2186 }
2187
The Android Open Source Project0c908882009-03-03 19:32:16 -08002188 // -------------------------------------------------------------------------
2189 // WebViewClient implementation.
2190 //-------------------------------------------------------------------------
2191
2192 // Use in overrideUrlLoading
2193 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2194 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2195 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2196 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2197
2198 /* package */ WebViewClient getWebViewClient() {
2199 return mWebViewClient;
2200 }
2201
Patrick Scott3918d442009-08-04 13:22:29 -04002202 private void updateIcon(WebView view, Bitmap icon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002203 if (icon != null) {
2204 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
Patrick Scott3918d442009-08-04 13:22:29 -04002205 view, icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002206 }
2207 setFavicon(icon);
2208 }
2209
2210 private final WebViewClient mWebViewClient = new WebViewClient() {
2211 @Override
2212 public void onPageStarted(WebView view, String url, Bitmap favicon) {
2213 resetLockIcon(url);
Leon Scroggins1f005d32009-08-10 17:36:42 -04002214 setUrlTitle(url, null, view);
Ben Murdochbff2d602009-07-01 20:19:05 +01002215
2216 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2217 if (errorConsole != null) {
2218 errorConsole.clearErrorMessages();
2219 if (mShouldShowErrorConsole) {
2220 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2221 }
2222 }
2223
The Android Open Source Project0c908882009-03-03 19:32:16 -08002224 // Call updateIcon instead of setFavicon so the bookmark
2225 // database can be updated.
Patrick Scott3918d442009-08-04 13:22:29 -04002226 updateIcon(view, favicon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002227
Grace Kloba4d7880f2009-08-12 09:35:42 -07002228 if (mSettings.isTracing()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002229 String host;
2230 try {
2231 WebAddress uri = new WebAddress(url);
2232 host = uri.mHost;
2233 } catch (android.net.ParseException ex) {
Grace Kloba4d7880f2009-08-12 09:35:42 -07002234 host = "browser";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002235 }
2236 host = host.replace('.', '_');
Grace Kloba4d7880f2009-08-12 09:35:42 -07002237 host += ".trace";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002238 mInTrace = true;
Grace Kloba4d7880f2009-08-12 09:35:42 -07002239 Debug.startMethodTracing(host, 20 * 1024 * 1024);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002240 }
2241
2242 // Performance probe
2243 if (false) {
2244 mStart = SystemClock.uptimeMillis();
2245 mProcessStart = Process.getElapsedCpuTime();
2246 long[] sysCpu = new long[7];
2247 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2248 sysCpu, null)) {
2249 mUserStart = sysCpu[0] + sysCpu[1];
2250 mSystemStart = sysCpu[2];
2251 mIdleStart = sysCpu[3];
2252 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2253 }
2254 mUiStart = SystemClock.currentThreadTimeMillis();
2255 }
2256
2257 if (!mPageStarted) {
2258 mPageStarted = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002259 // if onResume() has been called, resumeWebViewTimers() does
2260 // nothing.
2261 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002262 }
2263
2264 // reset sync timer to avoid sync starts during loading a page
2265 CookieSyncManager.getInstance().resetSync();
2266
2267 mInLoad = true;
2268 updateInLoadMenuItems();
2269 if (!mIsNetworkUp) {
2270 if ( mAlertDialog == null) {
2271 mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
2272 .setTitle(R.string.loadSuspendedTitle)
2273 .setMessage(R.string.loadSuspended)
2274 .setPositiveButton(R.string.ok, null)
2275 .show();
2276 }
2277 if (view != null) {
2278 view.setNetworkAvailable(false);
2279 }
2280 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002281 }
2282
2283 @Override
2284 public void onPageFinished(WebView view, String url) {
2285 // Reset the title and icon in case we stopped a provisional
2286 // load.
2287 resetTitleAndIcon(view);
2288
2289 // Update the lock icon image only once we are done loading
2290 updateLockIconImage(mLockIconType);
Leon Scroggins89c6d362009-07-15 16:54:37 -04002291 updateScreenshot(view);
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -04002292
The Android Open Source Project0c908882009-03-03 19:32:16 -08002293 // Performance probe
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002294 if (false) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002295 long[] sysCpu = new long[7];
2296 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2297 sysCpu, null)) {
2298 String uiInfo = "UI thread used "
2299 + (SystemClock.currentThreadTimeMillis() - mUiStart)
2300 + " ms";
Dave Bort31a6d1c2009-04-13 15:56:49 -07002301 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002302 Log.d(LOGTAG, uiInfo);
2303 }
2304 //The string that gets written to the log
2305 String performanceString = "It took total "
2306 + (SystemClock.uptimeMillis() - mStart)
2307 + " ms clock time to load the page."
2308 + "\nbrowser process used "
2309 + (Process.getElapsedCpuTime() - mProcessStart)
2310 + " ms, user processes used "
2311 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2312 + " ms, kernel used "
2313 + (sysCpu[2] - mSystemStart) * 10
2314 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2315 + " ms and irq took "
2316 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2317 * 10 + " ms, " + uiInfo;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002318 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002319 Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2320 }
2321 if (url != null) {
2322 // strip the url to maintain consistency
2323 String newUrl = new String(url);
2324 if (newUrl.startsWith("http://www.")) {
2325 newUrl = newUrl.substring(11);
2326 } else if (newUrl.startsWith("http://")) {
2327 newUrl = newUrl.substring(7);
2328 } else if (newUrl.startsWith("https://www.")) {
2329 newUrl = newUrl.substring(12);
2330 } else if (newUrl.startsWith("https://")) {
2331 newUrl = newUrl.substring(8);
2332 }
Dave Bort31a6d1c2009-04-13 15:56:49 -07002333 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002334 Log.d(LOGTAG, newUrl + " loaded");
2335 }
2336 /*
2337 if (sWhiteList.contains(newUrl)) {
2338 // The string that gets pushed to the statistcs
2339 // service
2340 performanceString = performanceString
2341 + "\nWebpage: "
2342 + newUrl
2343 + "\nCarrier: "
2344 + android.os.SystemProperties
2345 .get("gsm.sim.operator.alpha");
2346 if (mWebView != null
2347 && mWebView.getContext() != null
2348 && mWebView.getContext().getSystemService(
2349 Context.CONNECTIVITY_SERVICE) != null) {
2350 ConnectivityManager cManager =
2351 (ConnectivityManager) mWebView
2352 .getContext().getSystemService(
2353 Context.CONNECTIVITY_SERVICE);
2354 NetworkInfo nInfo = cManager
2355 .getActiveNetworkInfo();
2356 if (nInfo != null) {
2357 performanceString = performanceString
2358 + "\nNetwork Type: "
2359 + nInfo.getType().toString();
2360 }
2361 }
2362 Checkin.logEvent(mResolver,
2363 Checkin.Events.Tag.WEBPAGE_LOAD,
2364 performanceString);
2365 Log.w(LOGTAG, "pushed to the statistics service");
2366 }
2367 */
2368 }
2369 }
2370 }
2371
2372 if (mInTrace) {
2373 mInTrace = false;
2374 Debug.stopMethodTracing();
2375 }
2376
2377 if (mPageStarted) {
2378 mPageStarted = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002379 // pauseWebViewTimers() will do nothing and return false if
2380 // onPause() is not called yet.
2381 if (pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002382 if (mWakeLock.isHeld()) {
2383 mHandler.removeMessages(RELEASE_WAKELOCK);
2384 mWakeLock.release();
2385 }
2386 }
2387 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002388 }
2389
2390 // return true if want to hijack the url to let another app to handle it
2391 @Override
2392 public boolean shouldOverrideUrlLoading(WebView view, String url) {
2393 if (url.startsWith(SCHEME_WTAI)) {
2394 // wtai://wp/mc;number
2395 // number=string(phone-number)
2396 if (url.startsWith(SCHEME_WTAI_MC)) {
2397 Intent intent = new Intent(Intent.ACTION_VIEW,
2398 Uri.parse(WebView.SCHEME_TEL +
2399 url.substring(SCHEME_WTAI_MC.length())));
2400 startActivity(intent);
2401 return true;
2402 }
2403 // wtai://wp/sd;dtmf
2404 // dtmf=string(dialstring)
2405 if (url.startsWith(SCHEME_WTAI_SD)) {
2406 // TODO
2407 // only send when there is active voice connection
2408 return false;
2409 }
2410 // wtai://wp/ap;number;name
2411 // number=string(phone-number)
2412 // name=string
2413 if (url.startsWith(SCHEME_WTAI_AP)) {
2414 // TODO
2415 return false;
2416 }
2417 }
2418
Dianne Hackborn99189432009-06-17 18:06:18 -07002419 // The "about:" schemes are internal to the browser; don't
2420 // want these to be dispatched to other apps.
2421 if (url.startsWith("about:")) {
2422 return false;
2423 }
Ben Murdochbff2d602009-07-01 20:19:05 +01002424
Dianne Hackborn99189432009-06-17 18:06:18 -07002425 Intent intent;
Ben Murdochbff2d602009-07-01 20:19:05 +01002426
Dianne Hackborn99189432009-06-17 18:06:18 -07002427 // perform generic parsing of the URI to turn it into an Intent.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002428 try {
Dianne Hackborn99189432009-06-17 18:06:18 -07002429 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2430 } catch (URISyntaxException ex) {
2431 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
The Android Open Source Project0c908882009-03-03 19:32:16 -08002432 return false;
2433 }
2434
Grace Kloba5b078b52009-06-24 20:23:41 -07002435 // check whether the intent can be resolved. If not, we will see
2436 // whether we can download it from the Market.
2437 if (getPackageManager().resolveActivity(intent, 0) == null) {
2438 String packagename = intent.getPackage();
2439 if (packagename != null) {
2440 intent = new Intent(Intent.ACTION_VIEW, Uri
2441 .parse("market://search?q=pname:" + packagename));
2442 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2443 startActivity(intent);
2444 return true;
2445 } else {
2446 return false;
2447 }
2448 }
2449
Dianne Hackborn99189432009-06-17 18:06:18 -07002450 // sanitize the Intent, ensuring web pages can not bypass browser
2451 // security (only access to BROWSABLE activities).
The Android Open Source Project0c908882009-03-03 19:32:16 -08002452 intent.addCategory(Intent.CATEGORY_BROWSABLE);
Dianne Hackborn99189432009-06-17 18:06:18 -07002453 intent.setComponent(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002454 try {
2455 if (startActivityIfNeeded(intent, -1)) {
2456 return true;
2457 }
2458 } catch (ActivityNotFoundException ex) {
2459 // ignore the error. If no application can handle the URL,
2460 // eg about:blank, assume the browser can handle it.
2461 }
2462
2463 if (mMenuIsDown) {
2464 openTab(url);
2465 closeOptionsMenu();
2466 return true;
2467 }
2468
2469 return false;
2470 }
2471
2472 /**
2473 * Updates the lock icon. This method is called when we discover another
2474 * resource to be loaded for this page (for example, javascript). While
2475 * we update the icon type, we do not update the lock icon itself until
2476 * we are done loading, it is slightly more secure this way.
2477 */
2478 @Override
2479 public void onLoadResource(WebView view, String url) {
2480 if (url != null && url.length() > 0) {
2481 // It is only if the page claims to be secure
2482 // that we may have to update the lock:
2483 if (mLockIconType == LOCK_ICON_SECURE) {
2484 // If NOT a 'safe' url, change the lock to mixed content!
2485 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2486 mLockIconType = LOCK_ICON_MIXED;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002487 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002488 Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2489 " updated lock icon to " + mLockIconType + " due to " + url);
2490 }
2491 }
2492 }
2493 }
2494 }
2495
2496 /**
2497 * Show the dialog, asking the user if they would like to continue after
2498 * an excessive number of HTTP redirects.
2499 */
2500 @Override
2501 public void onTooManyRedirects(WebView view, final Message cancelMsg,
2502 final Message continueMsg) {
2503 new AlertDialog.Builder(BrowserActivity.this)
2504 .setTitle(R.string.browserFrameRedirect)
2505 .setMessage(R.string.browserFrame307Post)
2506 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2507 public void onClick(DialogInterface dialog, int which) {
2508 continueMsg.sendToTarget();
2509 }})
2510 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2511 public void onClick(DialogInterface dialog, int which) {
2512 cancelMsg.sendToTarget();
2513 }})
2514 .setOnCancelListener(new OnCancelListener() {
2515 public void onCancel(DialogInterface dialog) {
2516 cancelMsg.sendToTarget();
2517 }})
2518 .show();
2519 }
2520
Patrick Scott37911c72009-03-24 18:02:58 -07002521 // Container class for the next error dialog that needs to be
2522 // displayed.
2523 class ErrorDialog {
2524 public final int mTitle;
2525 public final String mDescription;
2526 public final int mError;
2527 ErrorDialog(int title, String desc, int error) {
2528 mTitle = title;
2529 mDescription = desc;
2530 mError = error;
2531 }
2532 };
2533
2534 private void processNextError() {
2535 if (mQueuedErrors == null) {
2536 return;
2537 }
2538 // The first one is currently displayed so just remove it.
2539 mQueuedErrors.removeFirst();
2540 if (mQueuedErrors.size() == 0) {
2541 mQueuedErrors = null;
2542 return;
2543 }
2544 showError(mQueuedErrors.getFirst());
2545 }
2546
2547 private DialogInterface.OnDismissListener mDialogListener =
2548 new DialogInterface.OnDismissListener() {
2549 public void onDismiss(DialogInterface d) {
2550 processNextError();
2551 }
2552 };
2553 private LinkedList<ErrorDialog> mQueuedErrors;
2554
2555 private void queueError(int err, String desc) {
2556 if (mQueuedErrors == null) {
2557 mQueuedErrors = new LinkedList<ErrorDialog>();
2558 }
2559 for (ErrorDialog d : mQueuedErrors) {
2560 if (d.mError == err) {
2561 // Already saw a similar error, ignore the new one.
2562 return;
2563 }
2564 }
2565 ErrorDialog errDialog = new ErrorDialog(
2566 err == EventHandler.FILE_NOT_FOUND_ERROR ?
2567 R.string.browserFrameFileErrorLabel :
2568 R.string.browserFrameNetworkErrorLabel,
2569 desc, err);
2570 mQueuedErrors.addLast(errDialog);
2571
2572 // Show the dialog now if the queue was empty.
2573 if (mQueuedErrors.size() == 1) {
2574 showError(errDialog);
2575 }
2576 }
2577
2578 private void showError(ErrorDialog errDialog) {
2579 AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
2580 .setTitle(errDialog.mTitle)
2581 .setMessage(errDialog.mDescription)
2582 .setPositiveButton(R.string.ok, null)
2583 .create();
2584 d.setOnDismissListener(mDialogListener);
2585 d.show();
2586 }
2587
The Android Open Source Project0c908882009-03-03 19:32:16 -08002588 /**
2589 * Show a dialog informing the user of the network error reported by
2590 * WebCore.
2591 */
2592 @Override
2593 public void onReceivedError(WebView view, int errorCode,
2594 String description, String failingUrl) {
2595 if (errorCode != EventHandler.ERROR_LOOKUP &&
2596 errorCode != EventHandler.ERROR_CONNECT &&
2597 errorCode != EventHandler.ERROR_BAD_URL &&
2598 errorCode != EventHandler.ERROR_UNSUPPORTED_SCHEME &&
2599 errorCode != EventHandler.FILE_ERROR) {
Patrick Scott37911c72009-03-24 18:02:58 -07002600 queueError(errorCode, description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002601 }
Patrick Scott37911c72009-03-24 18:02:58 -07002602 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
2603 + " " + description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002604
2605 // We need to reset the title after an error.
2606 resetTitleAndRevertLockIcon();
2607 }
2608
2609 /**
2610 * Check with the user if it is ok to resend POST data as the page they
2611 * are trying to navigate to is the result of a POST.
2612 */
2613 @Override
2614 public void onFormResubmission(WebView view, final Message dontResend,
2615 final Message resend) {
2616 new AlertDialog.Builder(BrowserActivity.this)
2617 .setTitle(R.string.browserFrameFormResubmitLabel)
2618 .setMessage(R.string.browserFrameFormResubmitMessage)
2619 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2620 public void onClick(DialogInterface dialog, int which) {
2621 resend.sendToTarget();
2622 }})
2623 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2624 public void onClick(DialogInterface dialog, int which) {
2625 dontResend.sendToTarget();
2626 }})
2627 .setOnCancelListener(new OnCancelListener() {
2628 public void onCancel(DialogInterface dialog) {
2629 dontResend.sendToTarget();
2630 }})
2631 .show();
2632 }
2633
2634 /**
2635 * Insert the url into the visited history database.
2636 * @param url The url to be inserted.
2637 * @param isReload True if this url is being reloaded.
2638 * FIXME: Not sure what to do when reloading the page.
2639 */
2640 @Override
2641 public void doUpdateVisitedHistory(WebView view, String url,
2642 boolean isReload) {
2643 if (url.regionMatches(true, 0, "about:", 0, 6)) {
2644 return;
2645 }
2646 Browser.updateVisitedHistory(mResolver, url, true);
2647 WebIconDatabase.getInstance().retainIconForPageUrl(url);
2648 }
2649
2650 /**
2651 * Displays SSL error(s) dialog to the user.
2652 */
2653 @Override
2654 public void onReceivedSslError(
2655 final WebView view, final SslErrorHandler handler, final SslError error) {
2656
2657 if (mSettings.showSecurityWarnings()) {
2658 final LayoutInflater factory =
2659 LayoutInflater.from(BrowserActivity.this);
2660 final View warningsView =
2661 factory.inflate(R.layout.ssl_warnings, null);
2662 final LinearLayout placeholder =
2663 (LinearLayout)warningsView.findViewById(R.id.placeholder);
2664
2665 if (error.hasError(SslError.SSL_UNTRUSTED)) {
2666 LinearLayout ll = (LinearLayout)factory
2667 .inflate(R.layout.ssl_warning, null);
2668 ((TextView)ll.findViewById(R.id.warning))
2669 .setText(R.string.ssl_untrusted);
2670 placeholder.addView(ll);
2671 }
2672
2673 if (error.hasError(SslError.SSL_IDMISMATCH)) {
2674 LinearLayout ll = (LinearLayout)factory
2675 .inflate(R.layout.ssl_warning, null);
2676 ((TextView)ll.findViewById(R.id.warning))
2677 .setText(R.string.ssl_mismatch);
2678 placeholder.addView(ll);
2679 }
2680
2681 if (error.hasError(SslError.SSL_EXPIRED)) {
2682 LinearLayout ll = (LinearLayout)factory
2683 .inflate(R.layout.ssl_warning, null);
2684 ((TextView)ll.findViewById(R.id.warning))
2685 .setText(R.string.ssl_expired);
2686 placeholder.addView(ll);
2687 }
2688
2689 if (error.hasError(SslError.SSL_NOTYETVALID)) {
2690 LinearLayout ll = (LinearLayout)factory
2691 .inflate(R.layout.ssl_warning, null);
2692 ((TextView)ll.findViewById(R.id.warning))
2693 .setText(R.string.ssl_not_yet_valid);
2694 placeholder.addView(ll);
2695 }
2696
2697 new AlertDialog.Builder(BrowserActivity.this)
2698 .setTitle(R.string.security_warning)
2699 .setIcon(android.R.drawable.ic_dialog_alert)
2700 .setView(warningsView)
2701 .setPositiveButton(R.string.ssl_continue,
2702 new DialogInterface.OnClickListener() {
2703 public void onClick(DialogInterface dialog, int whichButton) {
2704 handler.proceed();
2705 }
2706 })
2707 .setNeutralButton(R.string.view_certificate,
2708 new DialogInterface.OnClickListener() {
2709 public void onClick(DialogInterface dialog, int whichButton) {
2710 showSSLCertificateOnError(view, handler, error);
2711 }
2712 })
2713 .setNegativeButton(R.string.cancel,
2714 new DialogInterface.OnClickListener() {
2715 public void onClick(DialogInterface dialog, int whichButton) {
2716 handler.cancel();
2717 BrowserActivity.this.resetTitleAndRevertLockIcon();
2718 }
2719 })
2720 .setOnCancelListener(
2721 new DialogInterface.OnCancelListener() {
2722 public void onCancel(DialogInterface dialog) {
2723 handler.cancel();
2724 BrowserActivity.this.resetTitleAndRevertLockIcon();
2725 }
2726 })
2727 .show();
2728 } else {
2729 handler.proceed();
2730 }
2731 }
2732
2733 /**
2734 * Handles an HTTP authentication request.
2735 *
2736 * @param handler The authentication handler
2737 * @param host The host
2738 * @param realm The realm
2739 */
2740 @Override
2741 public void onReceivedHttpAuthRequest(WebView view,
2742 final HttpAuthHandler handler, final String host, final String realm) {
2743 String username = null;
2744 String password = null;
2745
2746 boolean reuseHttpAuthUsernamePassword =
2747 handler.useHttpAuthUsernamePassword();
2748
2749 if (reuseHttpAuthUsernamePassword &&
2750 (mTabControl.getCurrentWebView() != null)) {
2751 String[] credentials =
2752 mTabControl.getCurrentWebView()
2753 .getHttpAuthUsernamePassword(host, realm);
2754 if (credentials != null && credentials.length == 2) {
2755 username = credentials[0];
2756 password = credentials[1];
2757 }
2758 }
2759
2760 if (username != null && password != null) {
2761 handler.proceed(username, password);
2762 } else {
2763 showHttpAuthentication(handler, host, realm, null, null, null, 0);
2764 }
2765 }
2766
2767 @Override
2768 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
2769 if (mMenuIsDown) {
2770 // only check shortcut key when MENU is held
2771 return getWindow().isShortcutKey(event.getKeyCode(), event);
2772 } else {
2773 return false;
2774 }
2775 }
2776
2777 @Override
2778 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
2779 if (view != mTabControl.getCurrentTopWebView()) {
2780 return;
2781 }
2782 if (event.isDown()) {
2783 BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
2784 } else {
2785 BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
2786 }
2787 }
2788 };
2789
2790 //--------------------------------------------------------------------------
2791 // WebChromeClient implementation
2792 //--------------------------------------------------------------------------
2793
2794 /* package */ WebChromeClient getWebChromeClient() {
2795 return mWebChromeClient;
2796 }
2797
2798 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
2799 // Helper method to create a new tab or sub window.
2800 private void createWindow(final boolean dialog, final Message msg) {
2801 if (dialog) {
2802 mTabControl.createSubWindow();
2803 final TabControl.Tab t = mTabControl.getCurrentTab();
2804 attachSubWindow(t);
2805 WebView.WebViewTransport transport =
2806 (WebView.WebViewTransport) msg.obj;
2807 transport.setWebView(t.getSubWebView());
2808 msg.sendToTarget();
2809 } else {
2810 final TabControl.Tab parent = mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04002811 final TabControl.Tab newTab
2812 = openTabAndShow(EMPTY_URL_DATA, false, null);
Grace Klobac9181842009-04-14 08:53:22 -07002813 if (newTab != parent) {
2814 parent.addChildTab(newTab);
2815 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002816 WebView.WebViewTransport transport =
2817 (WebView.WebViewTransport) msg.obj;
2818 transport.setWebView(mTabControl.getCurrentWebView());
Leon Scroggins1f005d32009-08-10 17:36:42 -04002819 msg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002820 }
2821 }
2822
2823 @Override
2824 public boolean onCreateWindow(WebView view, final boolean dialog,
2825 final boolean userGesture, final Message resultMsg) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002826 // Short-circuit if we can't create any more tabs or sub windows.
2827 if (dialog && mTabControl.getCurrentSubWindow() != null) {
2828 new AlertDialog.Builder(BrowserActivity.this)
2829 .setTitle(R.string.too_many_subwindows_dialog_title)
2830 .setIcon(android.R.drawable.ic_dialog_alert)
2831 .setMessage(R.string.too_many_subwindows_dialog_message)
2832 .setPositiveButton(R.string.ok, null)
2833 .show();
2834 return false;
2835 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
2836 new AlertDialog.Builder(BrowserActivity.this)
2837 .setTitle(R.string.too_many_windows_dialog_title)
2838 .setIcon(android.R.drawable.ic_dialog_alert)
2839 .setMessage(R.string.too_many_windows_dialog_message)
2840 .setPositiveButton(R.string.ok, null)
2841 .show();
2842 return false;
2843 }
2844
2845 // Short-circuit if this was a user gesture.
2846 if (userGesture) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002847 createWindow(dialog, resultMsg);
2848 return true;
2849 }
2850
2851 // Allow the popup and create the appropriate window.
2852 final AlertDialog.OnClickListener allowListener =
2853 new AlertDialog.OnClickListener() {
2854 public void onClick(DialogInterface d,
2855 int which) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002856 createWindow(dialog, resultMsg);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002857 }
2858 };
2859
2860 // Block the popup by returning a null WebView.
2861 final AlertDialog.OnClickListener blockListener =
2862 new AlertDialog.OnClickListener() {
2863 public void onClick(DialogInterface d, int which) {
2864 resultMsg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002865 }
2866 };
2867
2868 // Build a confirmation dialog to display to the user.
2869 final AlertDialog d =
2870 new AlertDialog.Builder(BrowserActivity.this)
2871 .setTitle(R.string.attention)
2872 .setIcon(android.R.drawable.ic_dialog_alert)
2873 .setMessage(R.string.popup_window_attempt)
2874 .setPositiveButton(R.string.allow, allowListener)
2875 .setNegativeButton(R.string.block, blockListener)
2876 .setCancelable(false)
2877 .create();
2878
2879 // Show the confirmation dialog.
2880 d.show();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002881 return true;
2882 }
2883
2884 @Override
2885 public void onCloseWindow(WebView window) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002886 final TabControl.Tab current = mTabControl.getCurrentTab();
2887 final TabControl.Tab parent = current.getParentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002888 if (parent != null) {
2889 // JavaScript can only close popup window.
Leon Scroggins1f005d32009-08-10 17:36:42 -04002890 switchToTab(mTabControl.getTabIndex(parent));
2891 // Now we need to close the window
2892 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002893 }
2894 }
2895
2896 @Override
2897 public void onProgressChanged(WebView view, int newProgress) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002898 if (CUSTOM_BROWSER_BAR) {
2899 mTitleBar.setProgress(newProgress, view);
2900 } else {
2901 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
2902 newProgress * 100);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002903 }
2904
2905 if (newProgress == 100) {
2906 // onProgressChanged() is called for sub-frame too while
2907 // onPageFinished() is only called for the main frame. sync
2908 // cookie and cache promptly here.
2909 CookieSyncManager.getInstance().sync();
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002910 if (mInLoad) {
2911 mInLoad = false;
2912 updateInLoadMenuItems();
2913 }
2914 } else {
2915 // onPageFinished may have already been called but a subframe
2916 // is still loading and updating the progress. Reset mInLoad
2917 // and update the menu items.
2918 if (!mInLoad) {
2919 mInLoad = true;
2920 updateInLoadMenuItems();
2921 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002922 }
2923 }
2924
2925 @Override
2926 public void onReceivedTitle(WebView view, String title) {
Patrick Scott598c9cc2009-06-04 11:10:38 -04002927 String url = view.getUrl();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002928
2929 // here, if url is null, we want to reset the title
Leon Scroggins1f005d32009-08-10 17:36:42 -04002930 setUrlTitle(url, title, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002931
2932 if (url == null ||
2933 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
2934 return;
2935 }
Leon Scrogginsfce182b2009-05-08 13:54:52 -04002936 // See if we can find the current url in our history database and
2937 // add the new title to it.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002938 if (url.startsWith("http://www.")) {
2939 url = url.substring(11);
2940 } else if (url.startsWith("http://")) {
2941 url = url.substring(4);
2942 }
2943 try {
2944 url = "%" + url;
2945 String [] selArgs = new String[] { url };
2946
2947 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
2948 + Browser.BookmarkColumns.BOOKMARK + " = 0";
2949 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
2950 Browser.HISTORY_PROJECTION, where, selArgs, null);
2951 if (c.moveToFirst()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002952 // Current implementation of database only has one entry per
2953 // url.
Leon Scrogginsfce182b2009-05-08 13:54:52 -04002954 ContentValues map = new ContentValues();
2955 map.put(Browser.BookmarkColumns.TITLE, title);
2956 mResolver.update(Browser.BOOKMARKS_URI, map,
2957 "_id = " + c.getInt(0), null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002958 }
2959 c.close();
2960 } catch (IllegalStateException e) {
2961 Log.e(LOGTAG, "BrowserActivity onReceived title", e);
2962 } catch (SQLiteException ex) {
2963 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
2964 }
2965 }
2966
2967 @Override
2968 public void onReceivedIcon(WebView view, Bitmap icon) {
Patrick Scott3918d442009-08-04 13:22:29 -04002969 updateIcon(view, icon);
2970 }
2971
2972 @Override
2973 public void onReceivedTouchIconUrl(WebView view, String url) {
2974 final ContentResolver cr = getContentResolver();
2975 final Cursor c =
2976 BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04002977 view.getOriginalUrl(), view.getUrl(), true);
Patrick Scott3918d442009-08-04 13:22:29 -04002978 if (c != null) {
2979 if (c.getCount() > 0) {
2980 new DownloadTouchIcon(cr, c, view).execute(url);
2981 } else {
2982 c.close();
2983 }
2984 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002985 }
Ben Murdoch092dd5d2009-04-22 12:34:12 +01002986
Andrei Popescuadc008d2009-06-26 14:11:30 +01002987 @Override
Andrei Popescuc9b55562009-07-07 10:51:15 +01002988 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01002989 if (mCustomView != null)
2990 return;
2991
2992 // Add the custom view to its container.
2993 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
2994 mCustomView = view;
Andrei Popescuc9b55562009-07-07 10:51:15 +01002995 mCustomViewCallback = callback;
Andrei Popescuadc008d2009-06-26 14:11:30 +01002996 // Save the menu state and set it to empty while the custom
2997 // view is showing.
2998 mOldMenuState = mMenuState;
2999 mMenuState = EMPTY_MENU;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003000 // Hide the content view.
3001 mContentView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003002 // Finally show the custom view container.
Andrei Popescuc9b55562009-07-07 10:51:15 +01003003 mCustomViewContainer.setVisibility(View.VISIBLE);
3004 mCustomViewContainer.bringToFront();
Andrei Popescuadc008d2009-06-26 14:11:30 +01003005 }
3006
3007 @Override
3008 public void onHideCustomView() {
3009 if (mCustomView == null)
3010 return;
3011
Andrei Popescuc9b55562009-07-07 10:51:15 +01003012 // Hide the custom view.
3013 mCustomView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003014 // Remove the custom view from its container.
3015 mCustomViewContainer.removeView(mCustomView);
3016 mCustomView = null;
3017 // Reset the old menu state.
3018 mMenuState = mOldMenuState;
3019 mOldMenuState = EMPTY_MENU;
3020 mCustomViewContainer.setVisibility(View.GONE);
Andrei Popescuc9b55562009-07-07 10:51:15 +01003021 mCustomViewCallback.onCustomViewHidden();
3022 // Show the content view.
3023 mContentView.setVisibility(View.VISIBLE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003024 }
3025
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003026 /**
Andrei Popescu79e82b72009-07-27 12:01:59 +01003027 * The origin has exceeded its database quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003028 * @param url the URL that exceeded the quota
3029 * @param databaseIdentifier the identifier of the database on
3030 * which the transaction that caused the quota overflow was run
3031 * @param currentQuota the current quota for the origin.
Andrei Popescu79e82b72009-07-27 12:01:59 +01003032 * @param totalUsedQuota is the sum of all origins' quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003033 * @param quotaUpdater The callback to run when a decision to allow or
3034 * deny quota has been made. Don't forget to call this!
3035 */
3036 @Override
3037 public void onExceededDatabaseQuota(String url,
Andrei Popescu79e82b72009-07-27 12:01:59 +01003038 String databaseIdentifier, long currentQuota, long totalUsedQuota,
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003039 WebStorage.QuotaUpdater quotaUpdater) {
Andrei Popescu79e82b72009-07-27 12:01:59 +01003040 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
3041 url, databaseIdentifier, currentQuota, totalUsedQuota,
3042 quotaUpdater);
3043 }
3044
3045 /**
3046 * The Application Cache has exceeded its max size.
3047 * @param spaceNeeded is the amount of disk space that would be needed
3048 * in order for the last appcache operation to succeed.
3049 * @param totalUsedQuota is the sum of all origins' quota.
3050 * @param quotaUpdater A callback to inform the WebCore thread that a new
3051 * app cache size is available. This callback must always be executed at
3052 * some point to ensure that the sleeping WebCore thread is woken up.
3053 */
3054 @Override
3055 public void onReachedMaxAppCacheSize(long spaceNeeded,
3056 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3057 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3058 spaceNeeded, totalUsedQuota, quotaUpdater);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003059 }
Ben Murdoch7db26342009-06-03 18:21:19 +01003060
Steve Block2bc69912009-07-30 14:45:13 +01003061 /**
3062 * Instructs the browser to show a prompt to ask the user to set the
3063 * Geolocation permission state for the specified origin.
3064 * @param origin The origin for which Geolocation permissions are
3065 * requested.
3066 * @param callback The callback to call once the user has set the
3067 * Geolocation permission state.
3068 */
3069 @Override
3070 public void onGeolocationPermissionsShowPrompt(String origin,
3071 GeolocationPermissions.Callback callback) {
3072 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3073 origin, callback);
3074 }
3075
3076 /**
3077 * Instructs the browser to hide the Geolocation permissions prompt.
3078 */
3079 @Override
3080 public void onGeolocationPermissionsHidePrompt() {
3081 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3082 }
3083
Ben Murdoch7db26342009-06-03 18:21:19 +01003084 /* Adds a JavaScript error message to the system log.
3085 * @param message The error message to report.
3086 * @param lineNumber The line number of the error.
3087 * @param sourceID The name of the source file that caused the error.
3088 */
3089 @Override
3090 public void addMessageToConsole(String message, int lineNumber, String sourceID) {
Ben Murdochbff2d602009-07-01 20:19:05 +01003091 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3092 errorConsole.addErrorMessage(message, sourceID, lineNumber);
3093 if (mShouldShowErrorConsole &&
3094 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3095 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3096 }
3097 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
Ben Murdoch7db26342009-06-03 18:21:19 +01003098 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003099 };
3100
3101 /**
3102 * Notify the host application a download should be done, or that
3103 * the data should be streamed if a streaming viewer is available.
3104 * @param url The full url to the content that should be downloaded
3105 * @param contentDisposition Content-disposition http header, if
3106 * present.
3107 * @param mimetype The mimetype of the content reported by the server
3108 * @param contentLength The file size reported by the server
3109 */
3110 public void onDownloadStart(String url, String userAgent,
3111 String contentDisposition, String mimetype, long contentLength) {
3112 // if we're dealing wih A/V content that's not explicitly marked
3113 // for download, check if it's streamable.
3114 if (contentDisposition == null
3115 || !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) {
3116 // query the package manager to see if there's a registered handler
3117 // that matches.
3118 Intent intent = new Intent(Intent.ACTION_VIEW);
3119 intent.setDataAndType(Uri.parse(url), mimetype);
3120 if (getPackageManager().resolveActivity(intent,
3121 PackageManager.MATCH_DEFAULT_ONLY) != null) {
3122 // someone knows how to handle this mime type with this scheme, don't download.
3123 try {
3124 startActivity(intent);
3125 return;
3126 } catch (ActivityNotFoundException ex) {
Dave Bort31a6d1c2009-04-13 15:56:49 -07003127 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003128 Log.d(LOGTAG, "activity not found for " + mimetype
3129 + " over " + Uri.parse(url).getScheme(), ex);
3130 }
3131 // Best behavior is to fall back to a download in this case
3132 }
3133 }
3134 }
3135 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3136 }
3137
3138 /**
3139 * Notify the host application a download should be done, even if there
3140 * is a streaming viewer available for thise type.
3141 * @param url The full url to the content that should be downloaded
3142 * @param contentDisposition Content-disposition http header, if
3143 * present.
3144 * @param mimetype The mimetype of the content reported by the server
3145 * @param contentLength The file size reported by the server
3146 */
3147 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3148 String contentDisposition, String mimetype, long contentLength) {
3149
3150 String filename = URLUtil.guessFileName(url,
3151 contentDisposition, mimetype);
3152
3153 // Check to see if we have an SDCard
3154 String status = Environment.getExternalStorageState();
3155 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3156 int title;
3157 String msg;
3158
3159 // Check to see if the SDCard is busy, same as the music app
3160 if (status.equals(Environment.MEDIA_SHARED)) {
3161 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3162 title = R.string.download_sdcard_busy_dlg_title;
3163 } else {
3164 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3165 title = R.string.download_no_sdcard_dlg_title;
3166 }
3167
3168 new AlertDialog.Builder(this)
3169 .setTitle(title)
3170 .setIcon(android.R.drawable.ic_dialog_alert)
3171 .setMessage(msg)
3172 .setPositiveButton(R.string.ok, null)
3173 .show();
3174 return;
3175 }
3176
3177 // java.net.URI is a lot stricter than KURL so we have to undo
3178 // KURL's percent-encoding and redo the encoding using java.net.URI.
3179 URI uri = null;
3180 try {
3181 // Undo the percent-encoding that KURL may have done.
3182 String newUrl = new String(URLUtil.decode(url.getBytes()));
3183 // Parse the url into pieces
3184 WebAddress w = new WebAddress(newUrl);
3185 String frag = null;
3186 String query = null;
3187 String path = w.mPath;
3188 // Break the path into path, query, and fragment
3189 if (path.length() > 0) {
3190 // Strip the fragment
3191 int idx = path.lastIndexOf('#');
3192 if (idx != -1) {
3193 frag = path.substring(idx + 1);
3194 path = path.substring(0, idx);
3195 }
3196 idx = path.lastIndexOf('?');
3197 if (idx != -1) {
3198 query = path.substring(idx + 1);
3199 path = path.substring(0, idx);
3200 }
3201 }
3202 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3203 query, frag);
3204 } catch (Exception e) {
3205 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3206 return;
3207 }
3208
3209 // XXX: Have to use the old url since the cookies were stored using the
3210 // old percent-encoded url.
3211 String cookies = CookieManager.getInstance().getCookie(url);
3212
3213 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003214 values.put(Downloads.COLUMN_URI, uri.toString());
3215 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3216 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3217 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003218 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003219 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003220 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003221 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3222 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3223 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3224 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003225 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003226 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003227 }
3228 if (mimetype == null) {
3229 // We must have long pressed on a link or image to download it. We
3230 // are not sure of the mimetype in this case, so do a head request
3231 new FetchUrlMimeType(this).execute(values);
3232 } else {
3233 final Uri contentUri =
3234 getContentResolver().insert(Downloads.CONTENT_URI, values);
3235 viewDownloads(contentUri);
3236 }
3237
3238 }
3239
3240 /**
3241 * Resets the lock icon. This method is called when we start a new load and
3242 * know the url to be loaded.
3243 */
3244 private void resetLockIcon(String url) {
3245 // Save the lock-icon state (we revert to it if the load gets cancelled)
3246 saveLockIcon();
3247
3248 mLockIconType = LOCK_ICON_UNSECURE;
3249 if (URLUtil.isHttpsUrl(url)) {
3250 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003251 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003252 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3253 " reset lock icon to " + mLockIconType);
3254 }
3255 }
3256
3257 updateLockIconImage(LOCK_ICON_UNSECURE);
3258 }
3259
3260 /**
3261 * Resets the lock icon. This method is called when the icon needs to be
3262 * reset but we do not know whether we are loading a secure or not secure
3263 * page.
3264 */
3265 private void resetLockIcon() {
3266 // Save the lock-icon state (we revert to it if the load gets cancelled)
3267 saveLockIcon();
3268
3269 mLockIconType = LOCK_ICON_UNSECURE;
3270
Dave Bort31a6d1c2009-04-13 15:56:49 -07003271 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003272 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3273 " reset lock icon to " + mLockIconType);
3274 }
3275
3276 updateLockIconImage(LOCK_ICON_UNSECURE);
3277 }
3278
3279 /**
3280 * Updates the lock-icon image in the title-bar.
3281 */
3282 private void updateLockIconImage(int lockIconType) {
3283 Drawable d = null;
3284 if (lockIconType == LOCK_ICON_SECURE) {
3285 d = mSecLockIcon;
3286 } else if (lockIconType == LOCK_ICON_MIXED) {
3287 d = mMixLockIcon;
3288 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04003289 if (CUSTOM_BROWSER_BAR) {
3290 mTitleBar.setLock(d, getTopWindow());
3291 } else {
3292 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003293 }
3294 }
3295
3296 /**
3297 * Displays a page-info dialog.
3298 * @param tab The tab to show info about
3299 * @param fromShowSSLCertificateOnError The flag that indicates whether
3300 * this dialog was opened from the SSL-certificate-on-error dialog or
3301 * not. This is important, since we need to know whether to return to
3302 * the parent dialog or simply dismiss.
3303 */
3304 private void showPageInfo(final TabControl.Tab tab,
3305 final boolean fromShowSSLCertificateOnError) {
3306 final LayoutInflater factory = LayoutInflater
3307 .from(this);
3308
3309 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3310
3311 final WebView view = tab.getWebView();
3312
3313 String url = null;
3314 String title = null;
3315
3316 if (view == null) {
3317 url = tab.getUrl();
3318 title = tab.getTitle();
3319 } else if (view == mTabControl.getCurrentWebView()) {
3320 // Use the cached title and url if this is the current WebView
3321 url = mUrl;
3322 title = mTitle;
3323 } else {
3324 url = view.getUrl();
3325 title = view.getTitle();
3326 }
3327
3328 if (url == null) {
3329 url = "";
3330 }
3331 if (title == null) {
3332 title = "";
3333 }
3334
3335 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3336 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3337
3338 mPageInfoView = tab;
3339 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3340
3341 AlertDialog.Builder alertDialogBuilder =
3342 new AlertDialog.Builder(this)
3343 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3344 .setView(pageInfoView)
3345 .setPositiveButton(
3346 R.string.ok,
3347 new DialogInterface.OnClickListener() {
3348 public void onClick(DialogInterface dialog,
3349 int whichButton) {
3350 mPageInfoDialog = null;
3351 mPageInfoView = null;
3352 mPageInfoFromShowSSLCertificateOnError = null;
3353
3354 // if we came here from the SSL error dialog
3355 if (fromShowSSLCertificateOnError) {
3356 // go back to the SSL error dialog
3357 showSSLCertificateOnError(
3358 mSSLCertificateOnErrorView,
3359 mSSLCertificateOnErrorHandler,
3360 mSSLCertificateOnErrorError);
3361 }
3362 }
3363 })
3364 .setOnCancelListener(
3365 new DialogInterface.OnCancelListener() {
3366 public void onCancel(DialogInterface dialog) {
3367 mPageInfoDialog = null;
3368 mPageInfoView = null;
3369 mPageInfoFromShowSSLCertificateOnError = null;
3370
3371 // if we came here from the SSL error dialog
3372 if (fromShowSSLCertificateOnError) {
3373 // go back to the SSL error dialog
3374 showSSLCertificateOnError(
3375 mSSLCertificateOnErrorView,
3376 mSSLCertificateOnErrorHandler,
3377 mSSLCertificateOnErrorError);
3378 }
3379 }
3380 });
3381
3382 // if we have a main top-level page SSL certificate set or a certificate
3383 // error
3384 if (fromShowSSLCertificateOnError ||
3385 (view != null && view.getCertificate() != null)) {
3386 // add a 'View Certificate' button
3387 alertDialogBuilder.setNeutralButton(
3388 R.string.view_certificate,
3389 new DialogInterface.OnClickListener() {
3390 public void onClick(DialogInterface dialog,
3391 int whichButton) {
3392 mPageInfoDialog = null;
3393 mPageInfoView = null;
3394 mPageInfoFromShowSSLCertificateOnError = null;
3395
3396 // if we came here from the SSL error dialog
3397 if (fromShowSSLCertificateOnError) {
3398 // go back to the SSL error dialog
3399 showSSLCertificateOnError(
3400 mSSLCertificateOnErrorView,
3401 mSSLCertificateOnErrorHandler,
3402 mSSLCertificateOnErrorError);
3403 } else {
3404 // otherwise, display the top-most certificate from
3405 // the chain
3406 if (view.getCertificate() != null) {
3407 showSSLCertificate(tab);
3408 }
3409 }
3410 }
3411 });
3412 }
3413
3414 mPageInfoDialog = alertDialogBuilder.show();
3415 }
3416
3417 /**
3418 * Displays the main top-level page SSL certificate dialog
3419 * (accessible from the Page-Info dialog).
3420 * @param tab The tab to show certificate for.
3421 */
3422 private void showSSLCertificate(final TabControl.Tab tab) {
3423 final View certificateView =
3424 inflateCertificateView(tab.getWebView().getCertificate());
3425 if (certificateView == null) {
3426 return;
3427 }
3428
3429 LayoutInflater factory = LayoutInflater.from(this);
3430
3431 final LinearLayout placeholder =
3432 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3433
3434 LinearLayout ll = (LinearLayout) factory.inflate(
3435 R.layout.ssl_success, placeholder);
3436 ((TextView)ll.findViewById(R.id.success))
3437 .setText(R.string.ssl_certificate_is_valid);
3438
3439 mSSLCertificateView = tab;
3440 mSSLCertificateDialog =
3441 new AlertDialog.Builder(this)
3442 .setTitle(R.string.ssl_certificate).setIcon(
3443 R.drawable.ic_dialog_browser_certificate_secure)
3444 .setView(certificateView)
3445 .setPositiveButton(R.string.ok,
3446 new DialogInterface.OnClickListener() {
3447 public void onClick(DialogInterface dialog,
3448 int whichButton) {
3449 mSSLCertificateDialog = null;
3450 mSSLCertificateView = null;
3451
3452 showPageInfo(tab, false);
3453 }
3454 })
3455 .setOnCancelListener(
3456 new DialogInterface.OnCancelListener() {
3457 public void onCancel(DialogInterface dialog) {
3458 mSSLCertificateDialog = null;
3459 mSSLCertificateView = null;
3460
3461 showPageInfo(tab, false);
3462 }
3463 })
3464 .show();
3465 }
3466
3467 /**
3468 * Displays the SSL error certificate dialog.
3469 * @param view The target web-view.
3470 * @param handler The SSL error handler responsible for cancelling the
3471 * connection that resulted in an SSL error or proceeding per user request.
3472 * @param error The SSL error object.
3473 */
3474 private void showSSLCertificateOnError(
3475 final WebView view, final SslErrorHandler handler, final SslError error) {
3476
3477 final View certificateView =
3478 inflateCertificateView(error.getCertificate());
3479 if (certificateView == null) {
3480 return;
3481 }
3482
3483 LayoutInflater factory = LayoutInflater.from(this);
3484
3485 final LinearLayout placeholder =
3486 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3487
3488 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3489 LinearLayout ll = (LinearLayout)factory
3490 .inflate(R.layout.ssl_warning, placeholder);
3491 ((TextView)ll.findViewById(R.id.warning))
3492 .setText(R.string.ssl_untrusted);
3493 }
3494
3495 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3496 LinearLayout ll = (LinearLayout)factory
3497 .inflate(R.layout.ssl_warning, placeholder);
3498 ((TextView)ll.findViewById(R.id.warning))
3499 .setText(R.string.ssl_mismatch);
3500 }
3501
3502 if (error.hasError(SslError.SSL_EXPIRED)) {
3503 LinearLayout ll = (LinearLayout)factory
3504 .inflate(R.layout.ssl_warning, placeholder);
3505 ((TextView)ll.findViewById(R.id.warning))
3506 .setText(R.string.ssl_expired);
3507 }
3508
3509 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3510 LinearLayout ll = (LinearLayout)factory
3511 .inflate(R.layout.ssl_warning, placeholder);
3512 ((TextView)ll.findViewById(R.id.warning))
3513 .setText(R.string.ssl_not_yet_valid);
3514 }
3515
3516 mSSLCertificateOnErrorHandler = handler;
3517 mSSLCertificateOnErrorView = view;
3518 mSSLCertificateOnErrorError = error;
3519 mSSLCertificateOnErrorDialog =
3520 new AlertDialog.Builder(this)
3521 .setTitle(R.string.ssl_certificate).setIcon(
3522 R.drawable.ic_dialog_browser_certificate_partially_secure)
3523 .setView(certificateView)
3524 .setPositiveButton(R.string.ok,
3525 new DialogInterface.OnClickListener() {
3526 public void onClick(DialogInterface dialog,
3527 int whichButton) {
3528 mSSLCertificateOnErrorDialog = null;
3529 mSSLCertificateOnErrorView = null;
3530 mSSLCertificateOnErrorHandler = null;
3531 mSSLCertificateOnErrorError = null;
3532
3533 mWebViewClient.onReceivedSslError(
3534 view, handler, error);
3535 }
3536 })
3537 .setNeutralButton(R.string.page_info_view,
3538 new DialogInterface.OnClickListener() {
3539 public void onClick(DialogInterface dialog,
3540 int whichButton) {
3541 mSSLCertificateOnErrorDialog = null;
3542
3543 // do not clear the dialog state: we will
3544 // need to show the dialog again once the
3545 // user is done exploring the page-info details
3546
3547 showPageInfo(mTabControl.getTabFromView(view),
3548 true);
3549 }
3550 })
3551 .setOnCancelListener(
3552 new DialogInterface.OnCancelListener() {
3553 public void onCancel(DialogInterface dialog) {
3554 mSSLCertificateOnErrorDialog = null;
3555 mSSLCertificateOnErrorView = null;
3556 mSSLCertificateOnErrorHandler = null;
3557 mSSLCertificateOnErrorError = null;
3558
3559 mWebViewClient.onReceivedSslError(
3560 view, handler, error);
3561 }
3562 })
3563 .show();
3564 }
3565
3566 /**
3567 * Inflates the SSL certificate view (helper method).
3568 * @param certificate The SSL certificate.
3569 * @return The resultant certificate view with issued-to, issued-by,
3570 * issued-on, expires-on, and possibly other fields set.
3571 * If the input certificate is null, returns null.
3572 */
3573 private View inflateCertificateView(SslCertificate certificate) {
3574 if (certificate == null) {
3575 return null;
3576 }
3577
3578 LayoutInflater factory = LayoutInflater.from(this);
3579
3580 View certificateView = factory.inflate(
3581 R.layout.ssl_certificate, null);
3582
3583 // issued to:
3584 SslCertificate.DName issuedTo = certificate.getIssuedTo();
3585 if (issuedTo != null) {
3586 ((TextView) certificateView.findViewById(R.id.to_common))
3587 .setText(issuedTo.getCName());
3588 ((TextView) certificateView.findViewById(R.id.to_org))
3589 .setText(issuedTo.getOName());
3590 ((TextView) certificateView.findViewById(R.id.to_org_unit))
3591 .setText(issuedTo.getUName());
3592 }
3593
3594 // issued by:
3595 SslCertificate.DName issuedBy = certificate.getIssuedBy();
3596 if (issuedBy != null) {
3597 ((TextView) certificateView.findViewById(R.id.by_common))
3598 .setText(issuedBy.getCName());
3599 ((TextView) certificateView.findViewById(R.id.by_org))
3600 .setText(issuedBy.getOName());
3601 ((TextView) certificateView.findViewById(R.id.by_org_unit))
3602 .setText(issuedBy.getUName());
3603 }
3604
3605 // issued on:
3606 String issuedOn = reformatCertificateDate(
3607 certificate.getValidNotBefore());
3608 ((TextView) certificateView.findViewById(R.id.issued_on))
3609 .setText(issuedOn);
3610
3611 // expires on:
3612 String expiresOn = reformatCertificateDate(
3613 certificate.getValidNotAfter());
3614 ((TextView) certificateView.findViewById(R.id.expires_on))
3615 .setText(expiresOn);
3616
3617 return certificateView;
3618 }
3619
3620 /**
3621 * Re-formats the certificate date (Date.toString()) string to
3622 * a properly localized date string.
3623 * @return Properly localized version of the certificate date string and
3624 * the original certificate date string if fails to localize.
3625 * If the original string is null, returns an empty string "".
3626 */
3627 private String reformatCertificateDate(String certificateDate) {
3628 String reformattedDate = null;
3629
3630 if (certificateDate != null) {
3631 Date date = null;
3632 try {
3633 date = java.text.DateFormat.getInstance().parse(certificateDate);
3634 } catch (ParseException e) {
3635 date = null;
3636 }
3637
3638 if (date != null) {
3639 reformattedDate =
3640 DateFormat.getDateFormat(this).format(date);
3641 }
3642 }
3643
3644 return reformattedDate != null ? reformattedDate :
3645 (certificateDate != null ? certificateDate : "");
3646 }
3647
3648 /**
3649 * Displays an http-authentication dialog.
3650 */
3651 private void showHttpAuthentication(final HttpAuthHandler handler,
3652 final String host, final String realm, final String title,
3653 final String name, final String password, int focusId) {
3654 LayoutInflater factory = LayoutInflater.from(this);
3655 final View v = factory
3656 .inflate(R.layout.http_authentication, null);
3657 if (name != null) {
3658 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3659 }
3660 if (password != null) {
3661 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3662 }
3663
3664 String titleText = title;
3665 if (titleText == null) {
3666 titleText = getText(R.string.sign_in_to).toString().replace(
3667 "%s1", host).replace("%s2", realm);
3668 }
3669
3670 mHttpAuthHandler = handler;
3671 AlertDialog dialog = new AlertDialog.Builder(this)
3672 .setTitle(titleText)
3673 .setIcon(android.R.drawable.ic_dialog_alert)
3674 .setView(v)
3675 .setPositiveButton(R.string.action,
3676 new DialogInterface.OnClickListener() {
3677 public void onClick(DialogInterface dialog,
3678 int whichButton) {
3679 String nm = ((EditText) v
3680 .findViewById(R.id.username_edit))
3681 .getText().toString();
3682 String pw = ((EditText) v
3683 .findViewById(R.id.password_edit))
3684 .getText().toString();
3685 BrowserActivity.this.setHttpAuthUsernamePassword
3686 (host, realm, nm, pw);
3687 handler.proceed(nm, pw);
3688 mHttpAuthenticationDialog = null;
3689 mHttpAuthHandler = null;
3690 }})
3691 .setNegativeButton(R.string.cancel,
3692 new DialogInterface.OnClickListener() {
3693 public void onClick(DialogInterface dialog,
3694 int whichButton) {
3695 handler.cancel();
3696 BrowserActivity.this.resetTitleAndRevertLockIcon();
3697 mHttpAuthenticationDialog = null;
3698 mHttpAuthHandler = null;
3699 }})
3700 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3701 public void onCancel(DialogInterface dialog) {
3702 handler.cancel();
3703 BrowserActivity.this.resetTitleAndRevertLockIcon();
3704 mHttpAuthenticationDialog = null;
3705 mHttpAuthHandler = null;
3706 }})
3707 .create();
3708 // Make the IME appear when the dialog is displayed if applicable.
3709 dialog.getWindow().setSoftInputMode(
3710 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3711 dialog.show();
3712 if (focusId != 0) {
3713 dialog.findViewById(focusId).requestFocus();
3714 } else {
3715 v.findViewById(R.id.username_edit).requestFocus();
3716 }
3717 mHttpAuthenticationDialog = dialog;
3718 }
3719
3720 public int getProgress() {
3721 WebView w = mTabControl.getCurrentWebView();
3722 if (w != null) {
3723 return w.getProgress();
3724 } else {
3725 return 100;
3726 }
3727 }
3728
3729 /**
3730 * Set HTTP authentication password.
3731 *
3732 * @param host The host for the password
3733 * @param realm The realm for the password
3734 * @param username The username for the password. If it is null, it means
3735 * password can't be saved.
3736 * @param password The password
3737 */
3738 public void setHttpAuthUsernamePassword(String host, String realm,
3739 String username,
3740 String password) {
3741 WebView w = mTabControl.getCurrentWebView();
3742 if (w != null) {
3743 w.setHttpAuthUsernamePassword(host, realm, username, password);
3744 }
3745 }
3746
3747 /**
3748 * connectivity manager says net has come or gone... inform the user
3749 * @param up true if net has come up, false if net has gone down
3750 */
3751 public void onNetworkToggle(boolean up) {
3752 if (up == mIsNetworkUp) {
3753 return;
3754 } else if (up) {
3755 mIsNetworkUp = true;
3756 if (mAlertDialog != null) {
3757 mAlertDialog.cancel();
3758 mAlertDialog = null;
3759 }
3760 } else {
3761 mIsNetworkUp = false;
3762 if (mInLoad && mAlertDialog == null) {
3763 mAlertDialog = new AlertDialog.Builder(this)
3764 .setTitle(R.string.loadSuspendedTitle)
3765 .setMessage(R.string.loadSuspended)
3766 .setPositiveButton(R.string.ok, null)
3767 .show();
3768 }
3769 }
3770 WebView w = mTabControl.getCurrentWebView();
3771 if (w != null) {
3772 w.setNetworkAvailable(up);
3773 }
3774 }
3775
3776 @Override
3777 protected void onActivityResult(int requestCode, int resultCode,
3778 Intent intent) {
3779 switch (requestCode) {
3780 case COMBO_PAGE:
3781 if (resultCode == RESULT_OK && intent != null) {
3782 String data = intent.getAction();
3783 Bundle extras = intent.getExtras();
3784 if (extras != null && extras.getBoolean("new_window", false)) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003785 final TabControl.Tab newTab = openTab(data);
3786 if (mSettings.openInBackground() &&
Leon Scroggins1f005d32009-08-10 17:36:42 -04003787 newTab != null) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003788 mTabControl.populatePickerData(newTab);
3789 mTabControl.setCurrentTab(newTab);
Leon Scroggins1f005d32009-08-10 17:36:42 -04003790 int newIndex = mTabControl.getCurrentIndex();
3791 if (CUSTOM_BROWSER_BAR) {
3792 mTitleBar.setCurrentTab(newIndex);
3793 }
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003794 }
Leon Scroggins64b80f32009-08-07 12:03:34 -04003795 } else if (intent.getBooleanExtra("open_search", false)) {
3796 onSearchRequested();
The Android Open Source Project0c908882009-03-03 19:32:16 -08003797 } else {
3798 final TabControl.Tab currentTab =
3799 mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04003800 dismissSubWindow(currentTab);
3801 if (data != null && data.length() != 0) {
3802 getTopWindow().loadUrl(data);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003803 }
3804 }
3805 }
3806 break;
3807 default:
3808 break;
3809 }
3810 getTopWindow().requestFocus();
3811 }
3812
3813 /*
3814 * This method is called as a result of the user selecting the options
3815 * menu to see the download window, or when a download changes state. It
3816 * shows the download window ontop of the current window.
3817 */
3818 /* package */ void viewDownloads(Uri downloadRecord) {
3819 Intent intent = new Intent(this,
3820 BrowserDownloadPage.class);
3821 intent.setData(downloadRecord);
3822 startActivityForResult(intent, this.DOWNLOAD_PAGE);
3823
3824 }
3825
Leon Scrogginse4b3bda2009-06-09 15:46:41 -04003826 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003827 WebView current = mTabControl.getCurrentWebView();
3828 if (current == null) {
3829 return;
3830 }
3831 Intent intent = new Intent(this,
3832 CombinedBookmarkHistoryActivity.class);
3833 String title = current.getTitle();
3834 String url = current.getUrl();
3835 // Just in case the user opens bookmarks before a page finishes loading
3836 // so the current history item, and therefore the page, is null.
3837 if (null == url) {
3838 url = mLastEnteredUrl;
3839 // This can happen.
3840 if (null == url) {
3841 url = mSettings.getHomePage();
3842 }
3843 }
3844 // In case the web page has not yet received its associated title.
3845 if (title == null) {
3846 title = url;
3847 }
3848 intent.putExtra("title", title);
3849 intent.putExtra("url", url);
3850 intent.putExtra("maxTabsOpen",
3851 mTabControl.getTabCount() >= TabControl.MAX_TABS);
Patrick Scott3918d442009-08-04 13:22:29 -04003852 intent.putExtra("touch_icon_url", current.getTouchIconUrl());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003853 if (startWithHistory) {
3854 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
3855 CombinedBookmarkHistoryActivity.HISTORY_TAB);
3856 }
3857 startActivityForResult(intent, COMBO_PAGE);
3858 }
3859
3860 // Called when loading from context menu or LOAD_URL message
3861 private void loadURL(WebView view, String url) {
3862 // In case the user enters nothing.
3863 if (url != null && url.length() != 0 && view != null) {
3864 url = smartUrlFilter(url);
3865 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
3866 view.loadUrl(url);
3867 }
3868 }
3869 }
3870
The Android Open Source Project0c908882009-03-03 19:32:16 -08003871 private String smartUrlFilter(Uri inUri) {
3872 if (inUri != null) {
3873 return smartUrlFilter(inUri.toString());
3874 }
3875 return null;
3876 }
3877
3878
3879 // get window count
3880
3881 int getWindowCount(){
3882 if(mTabControl != null){
3883 return mTabControl.getTabCount();
3884 }
3885 return 0;
3886 }
3887
Feng Qianb34f87a2009-03-24 21:27:26 -07003888 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08003889 "(?i)" + // switch on case insensitive matching
3890 "(" + // begin group for schema
3891 "(?:http|https|file):\\/\\/" +
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07003892 "|(?:inline|data|about|content|javascript):" +
The Android Open Source Project0c908882009-03-03 19:32:16 -08003893 ")" +
3894 "(.*)" );
3895
3896 /**
3897 * Attempts to determine whether user input is a URL or search
3898 * terms. Anything with a space is passed to search.
3899 *
3900 * Converts to lowercase any mistakenly uppercased schema (i.e.,
3901 * "Http://" converts to "http://"
3902 *
3903 * @return Original or modified URL
3904 *
3905 */
3906 String smartUrlFilter(String url) {
3907
3908 String inUrl = url.trim();
3909 boolean hasSpace = inUrl.indexOf(' ') != -1;
3910
3911 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
3912 if (matcher.matches()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003913 // force scheme to lowercase
3914 String scheme = matcher.group(1);
3915 String lcScheme = scheme.toLowerCase();
3916 if (!lcScheme.equals(scheme)) {
Mitsuru Oshima123ecfb2009-05-18 19:11:14 -07003917 inUrl = lcScheme + matcher.group(2);
3918 }
3919 if (hasSpace) {
3920 inUrl = inUrl.replace(" ", "%20");
The Android Open Source Project0c908882009-03-03 19:32:16 -08003921 }
3922 return inUrl;
3923 }
3924 if (hasSpace) {
Satish Sampath565505b2009-05-29 15:37:27 +01003925 // FIXME: Is this the correct place to add to searches?
3926 // what if someone else calls this function?
3927 int shortcut = parseUrlShortcut(inUrl);
3928 if (shortcut != SHORTCUT_INVALID) {
3929 Browser.addSearchUrl(mResolver, inUrl);
3930 String query = inUrl.substring(2);
3931 switch (shortcut) {
3932 case SHORTCUT_GOOGLE_SEARCH:
Grace Kloba47fdfdb2009-06-30 11:15:34 -07003933 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
Satish Sampath565505b2009-05-29 15:37:27 +01003934 case SHORTCUT_WIKIPEDIA_SEARCH:
3935 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
3936 case SHORTCUT_DICTIONARY_SEARCH:
3937 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
3938 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
The Android Open Source Project0c908882009-03-03 19:32:16 -08003939 // FIXME: we need location in this case
Satish Sampath565505b2009-05-29 15:37:27 +01003940 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003941 }
3942 }
3943 } else {
3944 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
3945 return URLUtil.guessUrl(inUrl);
3946 }
3947 }
3948
3949 Browser.addSearchUrl(mResolver, inUrl);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07003950 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003951 }
3952
Ben Murdochbff2d602009-07-01 20:19:05 +01003953 /* package */ void setShouldShowErrorConsole(boolean flag) {
3954 if (flag == mShouldShowErrorConsole) {
3955 // Nothing to do.
3956 return;
3957 }
3958
3959 mShouldShowErrorConsole = flag;
3960
3961 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3962
3963 if (flag) {
3964 // Setting the show state of the console will cause it's the layout to be inflated.
3965 if (errorConsole.numberOfErrors() > 0) {
3966 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3967 } else {
3968 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
3969 }
3970
3971 // Now we can add it to the main view.
3972 mErrorConsoleContainer.addView(errorConsole,
3973 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
3974 ViewGroup.LayoutParams.WRAP_CONTENT));
3975 } else {
3976 mErrorConsoleContainer.removeView(errorConsole);
3977 }
3978
3979 }
3980
The Android Open Source Project0c908882009-03-03 19:32:16 -08003981 private final static int LOCK_ICON_UNSECURE = 0;
3982 private final static int LOCK_ICON_SECURE = 1;
3983 private final static int LOCK_ICON_MIXED = 2;
3984
3985 private int mLockIconType = LOCK_ICON_UNSECURE;
3986 private int mPrevLockType = LOCK_ICON_UNSECURE;
3987
3988 private BrowserSettings mSettings;
3989 private TabControl mTabControl;
3990 private ContentResolver mResolver;
3991 private FrameLayout mContentView;
Andrei Popescuadc008d2009-06-26 14:11:30 +01003992 private View mCustomView;
3993 private FrameLayout mCustomViewContainer;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003994 private WebChromeClient.CustomViewCallback mCustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -08003995
3996 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
3997 // view, we should rewrite this.
3998 private int mCurrentMenuState = 0;
3999 private int mMenuState = R.id.MAIN_MENU;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004000 private int mOldMenuState = EMPTY_MENU;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004001 private static final int EMPTY_MENU = -1;
4002 private Menu mMenu;
4003
4004 private FindDialog mFindDialog;
4005 // Used to prevent chording to result in firing two shortcuts immediately
4006 // one after another. Fixes bug 1211714.
4007 boolean mCanChord;
4008
4009 private boolean mInLoad;
4010 private boolean mIsNetworkUp;
4011
4012 private boolean mPageStarted;
4013 private boolean mActivityInPause = true;
4014
4015 private boolean mMenuIsDown;
4016
4017 private final KeyTracker mKeyTracker = new KeyTracker(this);
4018
4019 // As trackball doesn't send repeat down, we have to track it ourselves
4020 private boolean mTrackTrackball;
4021
4022 private static boolean mInTrace;
4023
4024 // Performance probe
4025 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4026 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4027 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4028 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4029 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4030 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4031 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4032 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4033 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4034 };
4035
4036 private long mStart;
4037 private long mProcessStart;
4038 private long mUserStart;
4039 private long mSystemStart;
4040 private long mIdleStart;
4041 private long mIrqStart;
4042
4043 private long mUiStart;
4044
4045 private Drawable mMixLockIcon;
4046 private Drawable mSecLockIcon;
4047 private Drawable mGenericFavicon;
4048
4049 /* hold a ref so we can auto-cancel if necessary */
4050 private AlertDialog mAlertDialog;
4051
4052 // Wait for credentials before loading google.com
4053 private ProgressDialog mCredsDlg;
4054
4055 // The up-to-date URL and title (these can be different from those stored
4056 // in WebView, since it takes some time for the information in WebView to
4057 // get updated)
4058 private String mUrl;
4059 private String mTitle;
4060
4061 // As PageInfo has different style for landscape / portrait, we have
4062 // to re-open it when configuration changed
4063 private AlertDialog mPageInfoDialog;
4064 private TabControl.Tab mPageInfoView;
4065 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4066 // dialog, we should not just dismiss it, but should get back to the
4067 // SSL-certificate-on-error dialog. This flag is used to store this state
4068 private Boolean mPageInfoFromShowSSLCertificateOnError;
4069
4070 // as SSLCertificateOnError has different style for landscape / portrait,
4071 // we have to re-open it when configuration changed
4072 private AlertDialog mSSLCertificateOnErrorDialog;
4073 private WebView mSSLCertificateOnErrorView;
4074 private SslErrorHandler mSSLCertificateOnErrorHandler;
4075 private SslError mSSLCertificateOnErrorError;
4076
4077 // as SSLCertificate has different style for landscape / portrait, we
4078 // have to re-open it when configuration changed
4079 private AlertDialog mSSLCertificateDialog;
4080 private TabControl.Tab mSSLCertificateView;
4081
4082 // as HttpAuthentication has different style for landscape / portrait, we
4083 // have to re-open it when configuration changed
4084 private AlertDialog mHttpAuthenticationDialog;
4085 private HttpAuthHandler mHttpAuthHandler;
4086
4087 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4088 new FrameLayout.LayoutParams(
4089 ViewGroup.LayoutParams.FILL_PARENT,
4090 ViewGroup.LayoutParams.FILL_PARENT);
Andrei Popescuadc008d2009-06-26 14:11:30 +01004091 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4092 new FrameLayout.LayoutParams(
4093 ViewGroup.LayoutParams.FILL_PARENT,
4094 ViewGroup.LayoutParams.FILL_PARENT,
4095 Gravity.CENTER);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004096 // Google search
4097 final static String QuickSearch_G = "http://www.google.com/m?q=%s";
The Android Open Source Project0c908882009-03-03 19:32:16 -08004098 // Wikipedia search
4099 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4100 // Dictionary search
4101 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4102 // Google Mobile Local search
4103 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4104
4105 final static String QUERY_PLACE_HOLDER = "%s";
4106
4107 // "source" parameter for Google search through search key
4108 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4109 // "source" parameter for Google search through goto menu
4110 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4111 // "source" parameter for Google search through simplily type
4112 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4113 // "source" parameter for Google search suggested by the browser
4114 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4115 // "source" parameter for Google search from unknown source
4116 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4117
4118 private final static String LOGTAG = "browser";
4119
The Android Open Source Project0c908882009-03-03 19:32:16 -08004120 private String mLastEnteredUrl;
4121
4122 private PowerManager.WakeLock mWakeLock;
4123 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4124
4125 private Toast mStopToast;
4126
Leon Scroggins1f005d32009-08-10 17:36:42 -04004127 private TitleBarSet mTitleBar;
Leon Scroggins81db3662009-06-04 17:45:11 -04004128
Ben Murdochbff2d602009-07-01 20:19:05 +01004129 private LinearLayout mErrorConsoleContainer = null;
4130 private boolean mShouldShowErrorConsole = false;
4131
The Android Open Source Project0c908882009-03-03 19:32:16 -08004132 // As the ids are dynamically created, we can't guarantee that they will
4133 // be in sequence, so this static array maps ids to a window number.
4134 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4135 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4136 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4137 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4138
4139 // monitor platform changes
4140 private IntentFilter mNetworkStateChangedFilter;
4141 private BroadcastReceiver mNetworkStateIntentReceiver;
4142
Grace Klobab4da0ad2009-05-14 14:45:40 -07004143 private BroadcastReceiver mPackageInstallationReceiver;
4144
The Android Open Source Project0c908882009-03-03 19:32:16 -08004145 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004146 final static int COMBO_PAGE = 1;
4147 final static int DOWNLOAD_PAGE = 2;
4148 final static int PREFERENCES_PAGE = 3;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004149
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004150 /**
4151 * A UrlData class to abstract how the content will be set to WebView.
4152 * This base class uses loadUrl to show the content.
4153 */
4154 private static class UrlData {
4155 String mUrl;
Grace Kloba60e095c2009-06-16 11:50:55 -07004156 byte[] mPostData;
4157
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004158 UrlData(String url) {
4159 this.mUrl = url;
4160 }
Grace Kloba60e095c2009-06-16 11:50:55 -07004161
4162 void setPostData(byte[] postData) {
4163 mPostData = postData;
4164 }
4165
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004166 boolean isEmpty() {
4167 return mUrl == null || mUrl.length() == 0;
4168 }
4169
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004170 public void loadIn(WebView webView) {
Grace Kloba60e095c2009-06-16 11:50:55 -07004171 if (mPostData != null) {
4172 webView.postUrl(mUrl, mPostData);
4173 } else {
4174 webView.loadUrl(mUrl);
4175 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004176 }
4177 };
4178
4179 /**
4180 * A subclass of UrlData class that can display inlined content using
4181 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4182 */
4183 private static class InlinedUrlData extends UrlData {
4184 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4185 super(failUrl);
4186 mInlined = inlined;
4187 mMimeType = mimeType;
4188 mEncoding = encoding;
4189 }
4190 String mMimeType;
4191 String mInlined;
4192 String mEncoding;
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004193 @Override
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004194 boolean isEmpty() {
Ben Murdochbff2d602009-07-01 20:19:05 +01004195 return mInlined == null || mInlined.length() == 0 || super.isEmpty();
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004196 }
4197
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004198 @Override
4199 public void loadIn(WebView webView) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004200 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4201 }
4202 }
4203
Leon Scroggins1f005d32009-08-10 17:36:42 -04004204 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004205}