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