blob: e0368c39bb59dc87ce6375ed52def63660d76849 [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 Scroggins1f005d32009-08-10 17:36:42 -0400596 if (appTab != null && current != appTab) {
597 switchToTab(mTabControl.getTabIndex(appTab));
Patrick Scottcd115892009-07-16 09:42:58 -0400598 } else {
599 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
600 // will be opened in a new tab unless we have reached
601 // MAX_TABS. Then the url will be opened in the current
602 // tab. If a new tab is created, it will have "true" for
603 // exit on close.
Leon Scroggins1f005d32009-08-10 17:36:42 -0400604 openTabAndShow(urlData, true, appId);
Patrick Scottcd115892009-07-16 09:42:58 -0400605 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700606 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800607 } else {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700608 if ("about:debug".equals(urlData.mUrl)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800609 mSettings.toggleDebugSettings();
610 return;
611 }
Leon Scroggins1f005d32009-08-10 17:36:42 -0400612 // Get rid of the subwindow if it exists
613 dismissSubWindow(current);
614 urlData.loadIn(current.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800615 }
616 }
617 }
618
Satish Sampath565505b2009-05-29 15:37:27 +0100619 private int parseUrlShortcut(String url) {
620 if (url == null) return SHORTCUT_INVALID;
621
622 // FIXME: quick search, need to be customized by setting
623 if (url.length() > 2 && url.charAt(1) == ' ') {
624 switch (url.charAt(0)) {
625 case 'g': return SHORTCUT_GOOGLE_SEARCH;
626 case 'w': return SHORTCUT_WIKIPEDIA_SEARCH;
627 case 'd': return SHORTCUT_DICTIONARY_SEARCH;
628 case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH;
629 }
630 }
631 return SHORTCUT_INVALID;
632 }
633
634 /**
635 * Launches the default web search activity with the query parameters if the given intent's data
636 * are identified as plain search terms and not URLs/shortcuts.
637 * @return true if the intent was handled and web search activity was launched, false if not.
638 */
639 private boolean handleWebSearchIntent(Intent intent) {
640 if (intent == null) return false;
641
642 String url = null;
643 final String action = intent.getAction();
644 if (Intent.ACTION_VIEW.equals(action)) {
645 url = intent.getData().toString();
646 } else if (Intent.ACTION_SEARCH.equals(action)
647 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
648 || Intent.ACTION_WEB_SEARCH.equals(action)) {
649 url = intent.getStringExtra(SearchManager.QUERY);
650 }
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100651 return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA));
Satish Sampath565505b2009-05-29 15:37:27 +0100652 }
653
654 /**
655 * Launches the default web search activity with the query parameters if the given url string
656 * was identified as plain search terms and not URL/shortcut.
657 * @return true if the request was handled and web search activity was launched, false if not.
658 */
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100659 private boolean handleWebSearchRequest(String inUrl, Bundle appData) {
Satish Sampath565505b2009-05-29 15:37:27 +0100660 if (inUrl == null) return false;
661
662 // In general, we shouldn't modify URL from Intent.
663 // But currently, we get the user-typed URL from search box as well.
664 String url = fixUrl(inUrl).trim();
665
666 // URLs and site specific search shortcuts are handled by the regular flow of control, so
667 // return early.
668 if (Regex.WEB_URL_PATTERN.matcher(url).matches()
Satish Sampathbc5b9f32009-06-04 18:21:40 +0100669 || ACCEPTED_URI_SCHEMA.matcher(url).matches()
Satish Sampath565505b2009-05-29 15:37:27 +0100670 || parseUrlShortcut(url) != SHORTCUT_INVALID) {
671 return false;
672 }
673
674 Browser.updateVisitedHistory(mResolver, url, false);
675 Browser.addSearchUrl(mResolver, url);
676
677 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
678 intent.addCategory(Intent.CATEGORY_DEFAULT);
679 intent.putExtra(SearchManager.QUERY, url);
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100680 if (appData != null) {
681 intent.putExtra(SearchManager.APP_DATA, appData);
682 }
Grace Klobacc634032009-07-28 15:58:19 -0700683 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
Satish Sampath565505b2009-05-29 15:37:27 +0100684 startActivity(intent);
685
686 return true;
687 }
688
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700689 private UrlData getUrlDataFromIntent(Intent intent) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800690 String url = null;
691 if (intent != null) {
692 final String action = intent.getAction();
693 if (Intent.ACTION_VIEW.equals(action)) {
694 url = smartUrlFilter(intent.getData());
695 if (url != null && url.startsWith("content:")) {
696 /* Append mimetype so webview knows how to display */
697 String mimeType = intent.resolveType(getContentResolver());
698 if (mimeType != null) {
699 url += "?" + mimeType;
700 }
701 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700702 if ("inline:".equals(url)) {
703 return new InlinedUrlData(
704 intent.getStringExtra(Browser.EXTRA_INLINE_CONTENT),
705 intent.getType(),
706 intent.getStringExtra(Browser.EXTRA_INLINE_ENCODING),
707 intent.getStringExtra(Browser.EXTRA_INLINE_FAILURL));
708 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800709 } else if (Intent.ACTION_SEARCH.equals(action)
710 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
711 || Intent.ACTION_WEB_SEARCH.equals(action)) {
712 url = intent.getStringExtra(SearchManager.QUERY);
713 if (url != null) {
714 mLastEnteredUrl = url;
715 // Don't add Urls, just search terms.
716 // Urls will get added when the page is loaded.
717 if (!Regex.WEB_URL_PATTERN.matcher(url).matches()) {
718 Browser.updateVisitedHistory(mResolver, url, false);
719 }
720 // In general, we shouldn't modify URL from Intent.
721 // But currently, we get the user-typed URL from search box as well.
722 url = fixUrl(url);
723 url = smartUrlFilter(url);
724 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
725 if (url.contains(searchSource)) {
726 String source = null;
727 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
728 if (appData != null) {
729 source = appData.getString(SearchManager.SOURCE);
730 }
731 if (TextUtils.isEmpty(source)) {
732 source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
733 }
734 url = url.replace(searchSource, "&source=android-"+source+"&");
735 }
736 }
737 }
738 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700739 return new UrlData(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800740 }
741
742 /* package */ static String fixUrl(String inUrl) {
743 if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
744 return inUrl;
745 if (inUrl.startsWith("http:") ||
746 inUrl.startsWith("https:")) {
747 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
748 inUrl = inUrl.replaceFirst("/", "//");
749 } else inUrl = inUrl.replaceFirst(":", "://");
750 }
751 return inUrl;
752 }
753
754 /**
755 * Looking for the pattern like this
756 *
757 * *
758 * * *
759 * *** * *******
760 * * *
761 * * *
762 * *
763 */
764 private final SensorListener mSensorListener = new SensorListener() {
765 private long mLastGestureTime;
766 private float[] mPrev = new float[3];
767 private float[] mPrevDiff = new float[3];
768 private float[] mDiff = new float[3];
769 private float[] mRevertDiff = new float[3];
770
771 public void onSensorChanged(int sensor, float[] values) {
772 boolean show = false;
773 float[] diff = new float[3];
774
775 for (int i = 0; i < 3; i++) {
776 diff[i] = values[i] - mPrev[i];
777 if (Math.abs(diff[i]) > 1) {
778 show = true;
779 }
780 if ((diff[i] > 1.0 && mDiff[i] < 0.2)
781 || (diff[i] < -1.0 && mDiff[i] > -0.2)) {
782 // start track when there is a big move, or revert
783 mRevertDiff[i] = mDiff[i];
784 mDiff[i] = 0;
785 } else if (diff[i] > -0.2 && diff[i] < 0.2) {
786 // reset when it is flat
787 mDiff[i] = mRevertDiff[i] = 0;
788 }
789 mDiff[i] += diff[i];
790 mPrevDiff[i] = diff[i];
791 mPrev[i] = values[i];
792 }
793
794 if (false) {
795 // only shows if we think the delta is big enough, in an attempt
796 // to detect "serious" moves left/right or up/down
797 Log.d("BrowserSensorHack", "sensorChanged " + sensor + " ("
798 + values[0] + ", " + values[1] + ", " + values[2] + ")"
799 + " diff(" + diff[0] + " " + diff[1] + " " + diff[2]
800 + ")");
801 Log.d("BrowserSensorHack", " mDiff(" + mDiff[0] + " "
802 + mDiff[1] + " " + mDiff[2] + ")" + " mRevertDiff("
803 + mRevertDiff[0] + " " + mRevertDiff[1] + " "
804 + mRevertDiff[2] + ")");
805 }
806
807 long now = android.os.SystemClock.uptimeMillis();
808 if (now - mLastGestureTime > 1000) {
809 mLastGestureTime = 0;
810
811 float y = mDiff[1];
812 float z = mDiff[2];
813 float ay = Math.abs(y);
814 float az = Math.abs(z);
815 float ry = mRevertDiff[1];
816 float rz = mRevertDiff[2];
817 float ary = Math.abs(ry);
818 float arz = Math.abs(rz);
819 boolean gestY = ay > 2.5f && ary > 1.0f && ay > ary;
820 boolean gestZ = az > 3.5f && arz > 1.0f && az > arz;
821
822 if ((gestY || gestZ) && !(gestY && gestZ)) {
823 WebView view = mTabControl.getCurrentWebView();
824
825 if (view != null) {
826 if (gestZ) {
827 if (z < 0) {
828 view.zoomOut();
829 } else {
830 view.zoomIn();
831 }
832 } else {
833 view.flingScroll(0, Math.round(y * 100));
834 }
835 }
836 mLastGestureTime = now;
837 }
838 }
839 }
840
841 public void onAccuracyChanged(int sensor, int accuracy) {
842 // TODO Auto-generated method stub
843
844 }
845 };
846
847 @Override protected void onResume() {
848 super.onResume();
Dave Bort31a6d1c2009-04-13 15:56:49 -0700849 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800850 Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
851 }
852
853 if (!mActivityInPause) {
854 Log.e(LOGTAG, "BrowserActivity is already resumed.");
855 return;
856 }
857
Mike Reed7bfa63b2009-05-28 11:08:32 -0400858 mTabControl.resumeCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800859 mActivityInPause = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400860 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800861
862 if (mWakeLock.isHeld()) {
863 mHandler.removeMessages(RELEASE_WAKELOCK);
864 mWakeLock.release();
865 }
866
867 if (mCredsDlg != null) {
868 if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
869 // In case credential request never comes back
870 mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
871 }
872 }
873
874 registerReceiver(mNetworkStateIntentReceiver,
875 mNetworkStateChangedFilter);
876 WebView.enablePlatformNotifications();
877
878 if (mSettings.doFlick()) {
879 if (mSensorManager == null) {
880 mSensorManager = (SensorManager) getSystemService(
881 Context.SENSOR_SERVICE);
882 }
883 mSensorManager.registerListener(mSensorListener,
884 SensorManager.SENSOR_ACCELEROMETER,
885 SensorManager.SENSOR_DELAY_FASTEST);
886 } else {
887 mSensorManager = null;
888 }
889 }
890
891 /**
892 * onSaveInstanceState(Bundle map)
893 * onSaveInstanceState is called right before onStop(). The map contains
894 * the saved state.
895 */
896 @Override protected void onSaveInstanceState(Bundle outState) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700897 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800898 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
899 }
900 // the default implementation requires each view to have an id. As the
901 // browser handles the state itself and it doesn't use id for the views,
902 // don't call the default implementation. Otherwise it will trigger the
903 // warning like this, "couldn't save which view has focus because the
904 // focused view XXX has no id".
905
906 // Save all the tabs
907 mTabControl.saveState(outState);
908 }
909
910 @Override protected void onPause() {
911 super.onPause();
912
913 if (mActivityInPause) {
914 Log.e(LOGTAG, "BrowserActivity is already paused.");
915 return;
916 }
917
Mike Reed7bfa63b2009-05-28 11:08:32 -0400918 mTabControl.pauseCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800919 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400920 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800921 mWakeLock.acquire();
922 mHandler.sendMessageDelayed(mHandler
923 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
924 }
925
926 // Clear the credentials toast if it is up
927 if (mCredsDlg != null && mCredsDlg.isShowing()) {
928 mCredsDlg.dismiss();
929 }
930 mCredsDlg = null;
931
932 cancelStopToast();
933
934 // unregister network state listener
935 unregisterReceiver(mNetworkStateIntentReceiver);
936 WebView.disablePlatformNotifications();
937
938 if (mSensorManager != null) {
939 mSensorManager.unregisterListener(mSensorListener);
940 }
941 }
942
943 @Override protected void onDestroy() {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700944 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800945 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
946 }
947 super.onDestroy();
948 // Remove the current tab and sub window
949 TabControl.Tab t = mTabControl.getCurrentTab();
Patrick Scottfb5e77f2009-04-08 19:17:37 -0700950 if (t != null) {
951 dismissSubWindow(t);
952 removeTabFromContentView(t);
953 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800954 // Destroy all the tabs
955 mTabControl.destroy();
956 WebIconDatabase.getInstance().close();
957 if (mGlsConnection != null) {
958 unbindService(mGlsConnection);
959 mGlsConnection = null;
960 }
961
962 //
963 // stop MASF proxy service
964 //
965 //Intent proxyServiceIntent = new Intent();
966 //proxyServiceIntent.setComponent
967 // (new ComponentName(
968 // "com.android.masfproxyservice",
969 // "com.android.masfproxyservice.MasfProxyService"));
970 //stopService(proxyServiceIntent);
Grace Klobab4da0ad2009-05-14 14:45:40 -0700971
972 unregisterReceiver(mPackageInstallationReceiver);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800973 }
974
975 @Override
976 public void onConfigurationChanged(Configuration newConfig) {
977 super.onConfigurationChanged(newConfig);
978
979 if (mPageInfoDialog != null) {
980 mPageInfoDialog.dismiss();
981 showPageInfo(
982 mPageInfoView,
983 mPageInfoFromShowSSLCertificateOnError.booleanValue());
984 }
985 if (mSSLCertificateDialog != null) {
986 mSSLCertificateDialog.dismiss();
987 showSSLCertificate(
988 mSSLCertificateView);
989 }
990 if (mSSLCertificateOnErrorDialog != null) {
991 mSSLCertificateOnErrorDialog.dismiss();
992 showSSLCertificateOnError(
993 mSSLCertificateOnErrorView,
994 mSSLCertificateOnErrorHandler,
995 mSSLCertificateOnErrorError);
996 }
997 if (mHttpAuthenticationDialog != null) {
998 String title = ((TextView) mHttpAuthenticationDialog
999 .findViewById(com.android.internal.R.id.alertTitle)).getText()
1000 .toString();
1001 String name = ((TextView) mHttpAuthenticationDialog
1002 .findViewById(R.id.username_edit)).getText().toString();
1003 String password = ((TextView) mHttpAuthenticationDialog
1004 .findViewById(R.id.password_edit)).getText().toString();
1005 int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1006 .getId();
1007 mHttpAuthenticationDialog.dismiss();
1008 showHttpAuthentication(mHttpAuthHandler, null, null, title,
1009 name, password, focusId);
1010 }
1011 if (mFindDialog != null && mFindDialog.isShowing()) {
1012 mFindDialog.onConfigurationChanged(newConfig);
1013 }
1014 }
1015
1016 @Override public void onLowMemory() {
1017 super.onLowMemory();
1018 mTabControl.freeMemory();
1019 }
1020
Mike Reed7bfa63b2009-05-28 11:08:32 -04001021 private boolean resumeWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001022 if ((!mActivityInPause && !mPageStarted) ||
1023 (mActivityInPause && mPageStarted)) {
1024 CookieSyncManager.getInstance().startSync();
1025 WebView w = mTabControl.getCurrentWebView();
1026 if (w != null) {
1027 w.resumeTimers();
1028 }
1029 return true;
1030 } else {
1031 return false;
1032 }
1033 }
1034
Mike Reed7bfa63b2009-05-28 11:08:32 -04001035 private boolean pauseWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001036 if (mActivityInPause && !mPageStarted) {
1037 CookieSyncManager.getInstance().stopSync();
1038 WebView w = mTabControl.getCurrentWebView();
1039 if (w != null) {
1040 w.pauseTimers();
1041 }
1042 return true;
1043 } else {
1044 return false;
1045 }
1046 }
1047
Leon Scroggins1f005d32009-08-10 17:36:42 -04001048 // FIXME: Do we want to call this when loading google for the first time?
The Android Open Source Project0c908882009-03-03 19:32:16 -08001049 /*
1050 * This function is called when we are launching for the first time. We
1051 * are waiting for the login credentials before loading Google home
1052 * pages. This way the user will be logged in straight away.
1053 */
1054 private void waitForCredentials() {
1055 // Show a toast
1056 mCredsDlg = new ProgressDialog(this);
1057 mCredsDlg.setIndeterminate(true);
1058 mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1059 // If the user cancels the operation, then cancel the Google
1060 // Credentials request.
1061 mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1062 mCredsDlg.show();
1063
1064 // We set a timeout for the retrieval of credentials in onResume()
1065 // as that is when we have freed up some CPU time to get
1066 // the login credentials.
1067 }
1068
1069 /*
1070 * If we have received the credentials or we have timed out and we are
1071 * showing the credentials dialog, then it is time to move on.
1072 */
1073 private void resumeAfterCredentials() {
1074 if (mCredsDlg == null) {
1075 return;
1076 }
1077
1078 // Clear the toast
1079 if (mCredsDlg.isShowing()) {
1080 mCredsDlg.dismiss();
1081 }
1082 mCredsDlg = null;
1083
1084 // Clear any pending timeout
1085 mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1086
1087 // Load the page
1088 WebView w = mTabControl.getCurrentWebView();
1089 if (w != null) {
1090 w.loadUrl(mSettings.getHomePage());
1091 }
1092
1093 // Update the settings, need to do this last as it can take a moment
1094 // to persist the settings. In the mean time we could be loading
1095 // content.
1096 mSettings.setLoginInitialized(this);
1097 }
1098
1099 // Open the icon database and retain all the icons for visited sites.
1100 private void retainIconsOnStartup() {
1101 final WebIconDatabase db = WebIconDatabase.getInstance();
1102 db.open(getDir("icons", 0).getPath());
1103 try {
1104 Cursor c = Browser.getAllBookmarks(mResolver);
1105 if (!c.moveToFirst()) {
1106 c.deactivate();
1107 return;
1108 }
1109 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1110 do {
1111 String url = c.getString(urlIndex);
1112 db.retainIconForPageUrl(url);
1113 } while (c.moveToNext());
1114 c.deactivate();
1115 } catch (IllegalStateException e) {
1116 Log.e(LOGTAG, "retainIconsOnStartup", e);
1117 }
1118 }
1119
1120 // Helper method for getting the top window.
1121 WebView getTopWindow() {
1122 return mTabControl.getCurrentTopWebView();
1123 }
1124
1125 @Override
1126 public boolean onCreateOptionsMenu(Menu menu) {
1127 super.onCreateOptionsMenu(menu);
1128
1129 MenuInflater inflater = getMenuInflater();
1130 inflater.inflate(R.menu.browser, menu);
1131 mMenu = menu;
1132 updateInLoadMenuItems();
1133 return true;
1134 }
1135
1136 /**
1137 * As the menu can be open when loading state changes
1138 * we must manually update the state of the stop/reload menu
1139 * item
1140 */
1141 private void updateInLoadMenuItems() {
1142 if (mMenu == null) {
1143 return;
1144 }
1145 MenuItem src = mInLoad ?
1146 mMenu.findItem(R.id.stop_menu_id):
1147 mMenu.findItem(R.id.reload_menu_id);
1148 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1149 dest.setIcon(src.getIcon());
1150 dest.setTitle(src.getTitle());
1151 }
1152
1153 @Override
1154 public boolean onContextItemSelected(MenuItem item) {
1155 // chording is not an issue with context menus, but we use the same
1156 // options selector, so set mCanChord to true so we can access them.
1157 mCanChord = true;
1158 int id = item.getItemId();
1159 final WebView webView = getTopWindow();
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001160 if (null == webView) {
1161 return false;
1162 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001163 final HashMap hrefMap = new HashMap();
1164 hrefMap.put("webview", webView);
1165 final Message msg = mHandler.obtainMessage(
1166 FOCUS_NODE_HREF, id, 0, hrefMap);
1167 switch (id) {
1168 // -- Browser context menu
1169 case R.id.open_context_menu_id:
1170 case R.id.open_newtab_context_menu_id:
1171 case R.id.bookmark_context_menu_id:
1172 case R.id.save_link_context_menu_id:
1173 case R.id.share_link_context_menu_id:
1174 case R.id.copy_link_context_menu_id:
1175 webView.requestFocusNodeHref(msg);
1176 break;
1177
1178 default:
1179 // For other context menus
1180 return onOptionsItemSelected(item);
1181 }
1182 mCanChord = false;
1183 return true;
1184 }
1185
1186 private Bundle createGoogleSearchSourceBundle(String source) {
1187 Bundle bundle = new Bundle();
1188 bundle.putString(SearchManager.SOURCE, source);
1189 return bundle;
1190 }
1191
1192 /**
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001193 * Overriding this to insert a local information bundle
The Android Open Source Project0c908882009-03-03 19:32:16 -08001194 */
1195 @Override
1196 public boolean onSearchRequested() {
Leon Scroggins5bbe9802009-07-31 13:10:55 -04001197 String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
Grace Kloba83f47342009-07-20 10:44:31 -07001198 startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001199 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001200 return true;
1201 }
1202
1203 @Override
1204 public void startSearch(String initialQuery, boolean selectInitialQuery,
1205 Bundle appSearchData, boolean globalSearch) {
1206 if (appSearchData == null) {
1207 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1208 }
1209 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1210 }
1211
Leon Scroggins1f005d32009-08-10 17:36:42 -04001212 /**
1213 * Switch tabs. Called by the TitleBarSet when sliding the title bar
1214 * results in changing tabs.
Leon Scroggins160a7e72009-08-14 18:28:01 -04001215 * @param index Index of the tab to change to, as defined by
1216 * mTabControl.getTabIndex(Tab t).
1217 * @return boolean True if we successfully switched to a different tab. If
1218 * the indexth tab is null, or if that tab is the same as
1219 * the current one, return false.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001220 */
Leon Scroggins160a7e72009-08-14 18:28:01 -04001221 /* package */ boolean switchToTab(int index) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001222 TabControl.Tab tab = mTabControl.getTab(index);
1223 TabControl.Tab currentTab = mTabControl.getCurrentTab();
1224 if (tab == null || tab == currentTab) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001225 return false;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001226 }
1227 if (currentTab != null) {
1228 // currentTab may be null if it was just removed. In that case,
1229 // we do not need to remove it
1230 removeTabFromContentView(currentTab);
1231 }
1232 removeTabFromContentView(tab);
1233 mTabControl.setCurrentTab(tab);
1234 attachTabToContentView(tab);
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001235 if (CUSTOM_BROWSER_BAR) {
1236 mTitleBar.setCurrentTab(index);
1237 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001238 return true;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001239 }
1240
1241 /* package */ void closeCurrentWindow() {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001242 final TabControl.Tab current = mTabControl.getCurrentTab();
Leon Scroggins160a7e72009-08-14 18:28:01 -04001243 if (mTabControl.getTabCount() == 1) {
1244 // This is the last tab. Open a new one, as well as the history
1245 // picker, and close the current one.
1246 TabControl.Tab newTab = openTabAndShow(
1247 BrowserActivity.EMPTY_URL_DATA, false, null);
1248 bookmarksOrHistoryPicker(false, true);
1249 closeTab(current);
1250 mTabControl.setCurrentTab(newTab);
1251 return;
1252 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001253 final TabControl.Tab parent = current.getParentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04001254 int indexToShow = -1;
1255 if (parent != null) {
1256 indexToShow = mTabControl.getTabIndex(parent);
1257 } else {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001258 final int currentIndex = mTabControl.getCurrentIndex();
1259 // Try to move to the tab to the right
1260 indexToShow = currentIndex + 1;
1261 if (indexToShow > mTabControl.getTabCount() - 1) {
1262 // Try to move to the tab to the left
1263 indexToShow = currentIndex - 1;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001264 }
1265 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001266 if (switchToTab(indexToShow)) {
1267 // Close window
1268 closeTab(current);
1269 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001270 }
1271
The Android Open Source Project0c908882009-03-03 19:32:16 -08001272 @Override
1273 public boolean onOptionsItemSelected(MenuItem item) {
1274 if (!mCanChord) {
1275 // The user has already fired a shortcut with this hold down of the
1276 // menu key.
1277 return false;
1278 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001279 if (null == getTopWindow()) {
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001280 return false;
1281 }
Grace Kloba6ee9c492009-07-13 10:04:34 -07001282 if (mMenuIsDown) {
1283 // The shortcut action consumes the MENU. Even if it is still down,
1284 // it won't trigger the next shortcut action. In the case of the
1285 // shortcut action triggering a new activity, like Bookmarks, we
1286 // won't get onKeyUp for MENU. So it is important to reset it here.
1287 mMenuIsDown = false;
1288 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001289 switch (item.getItemId()) {
1290 // -- Main menu
Leon Scroggins64b80f32009-08-07 12:03:34 -04001291 case R.id.goto_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001292 bookmarksOrHistoryPicker(false, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001293 break;
1294
Leon Scroggins1f005d32009-08-10 17:36:42 -04001295 case R.id.add_bookmark_menu_id:
1296 Intent i = new Intent(BrowserActivity.this,
1297 AddBookmarkPage.class);
1298 WebView w = getTopWindow();
1299 i.putExtra("url", w.getUrl());
1300 i.putExtra("title", w.getTitle());
1301 startActivity(i);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001302 break;
1303
1304 case R.id.stop_reload_menu_id:
1305 if (mInLoad) {
1306 stopLoading();
1307 } else {
1308 getTopWindow().reload();
1309 }
1310 break;
1311
1312 case R.id.back_menu_id:
1313 getTopWindow().goBack();
1314 break;
1315
1316 case R.id.forward_menu_id:
1317 getTopWindow().goForward();
1318 break;
1319
1320 case R.id.close_menu_id:
1321 // Close the subwindow if it exists.
1322 if (mTabControl.getCurrentSubWindow() != null) {
1323 dismissSubWindow(mTabControl.getCurrentTab());
1324 break;
1325 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001326 closeCurrentWindow();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001327 break;
1328
1329 case R.id.homepage_menu_id:
1330 TabControl.Tab current = mTabControl.getCurrentTab();
1331 if (current != null) {
1332 dismissSubWindow(current);
1333 current.getWebView().loadUrl(mSettings.getHomePage());
1334 }
1335 break;
1336
1337 case R.id.preferences_menu_id:
1338 Intent intent = new Intent(this,
1339 BrowserPreferencesPage.class);
1340 startActivityForResult(intent, PREFERENCES_PAGE);
1341 break;
1342
1343 case R.id.find_menu_id:
1344 if (null == mFindDialog) {
1345 mFindDialog = new FindDialog(this);
1346 }
1347 mFindDialog.setWebView(getTopWindow());
1348 mFindDialog.show();
1349 mMenuState = EMPTY_MENU;
1350 break;
1351
1352 case R.id.select_text_id:
1353 getTopWindow().emulateShiftHeld();
1354 break;
1355 case R.id.page_info_menu_id:
1356 showPageInfo(mTabControl.getCurrentTab(), false);
1357 break;
1358
1359 case R.id.classic_history_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001360 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001361 break;
1362
1363 case R.id.share_page_menu_id:
1364 Browser.sendString(this, getTopWindow().getUrl());
1365 break;
1366
1367 case R.id.dump_nav_menu_id:
1368 getTopWindow().debugDump();
1369 break;
1370
1371 case R.id.zoom_in_menu_id:
1372 getTopWindow().zoomIn();
1373 break;
1374
1375 case R.id.zoom_out_menu_id:
1376 getTopWindow().zoomOut();
1377 break;
1378
1379 case R.id.view_downloads_menu_id:
1380 viewDownloads(null);
1381 break;
1382
The Android Open Source Project0c908882009-03-03 19:32:16 -08001383 case R.id.window_one_menu_id:
1384 case R.id.window_two_menu_id:
1385 case R.id.window_three_menu_id:
1386 case R.id.window_four_menu_id:
1387 case R.id.window_five_menu_id:
1388 case R.id.window_six_menu_id:
1389 case R.id.window_seven_menu_id:
1390 case R.id.window_eight_menu_id:
1391 {
1392 int menuid = item.getItemId();
1393 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1394 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1395 TabControl.Tab desiredTab = mTabControl.getTab(id);
1396 if (desiredTab != null &&
1397 desiredTab != mTabControl.getCurrentTab()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001398 switchToTab(id);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001399 }
1400 break;
1401 }
1402 }
1403 }
1404 break;
1405
1406 default:
1407 if (!super.onOptionsItemSelected(item)) {
1408 return false;
1409 }
1410 // Otherwise fall through.
1411 }
1412 mCanChord = false;
1413 return true;
1414 }
1415
1416 public void closeFind() {
1417 mMenuState = R.id.MAIN_MENU;
1418 }
1419
1420 @Override public boolean onPrepareOptionsMenu(Menu menu)
1421 {
1422 // This happens when the user begins to hold down the menu key, so
1423 // allow them to chord to get a shortcut.
1424 mCanChord = true;
1425 // Note: setVisible will decide whether an item is visible; while
1426 // setEnabled() will decide whether an item is enabled, which also means
1427 // whether the matching shortcut key will function.
1428 super.onPrepareOptionsMenu(menu);
1429 switch (mMenuState) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001430 case EMPTY_MENU:
1431 if (mCurrentMenuState != mMenuState) {
1432 menu.setGroupVisible(R.id.MAIN_MENU, false);
1433 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1434 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001435 }
1436 break;
1437 default:
1438 if (mCurrentMenuState != mMenuState) {
1439 menu.setGroupVisible(R.id.MAIN_MENU, true);
1440 menu.setGroupEnabled(R.id.MAIN_MENU, true);
1441 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001442 }
1443 final WebView w = getTopWindow();
1444 boolean canGoBack = false;
1445 boolean canGoForward = false;
1446 boolean isHome = false;
1447 if (w != null) {
1448 canGoBack = w.canGoBack();
1449 canGoForward = w.canGoForward();
1450 isHome = mSettings.getHomePage().equals(w.getUrl());
1451 }
1452 final MenuItem back = menu.findItem(R.id.back_menu_id);
1453 back.setEnabled(canGoBack);
1454
1455 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1456 home.setEnabled(!isHome);
1457
1458 menu.findItem(R.id.forward_menu_id)
1459 .setEnabled(canGoForward);
1460
1461 // decide whether to show the share link option
1462 PackageManager pm = getPackageManager();
1463 Intent send = new Intent(Intent.ACTION_SEND);
1464 send.setType("text/plain");
1465 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1466 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1467
The Android Open Source Project0c908882009-03-03 19:32:16 -08001468 boolean isNavDump = mSettings.isNavDump();
1469 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1470 nav.setVisible(isNavDump);
1471 nav.setEnabled(isNavDump);
1472 break;
1473 }
1474 mCurrentMenuState = mMenuState;
1475 return true;
1476 }
1477
1478 @Override
1479 public void onCreateContextMenu(ContextMenu menu, View v,
1480 ContextMenuInfo menuInfo) {
1481 WebView webview = (WebView) v;
1482 WebView.HitTestResult result = webview.getHitTestResult();
1483 if (result == null) {
1484 return;
1485 }
1486
1487 int type = result.getType();
1488 if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1489 Log.w(LOGTAG,
1490 "We should not show context menu when nothing is touched");
1491 return;
1492 }
1493 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1494 // let TextView handles context menu
1495 return;
1496 }
1497
1498 // Note, http://b/issue?id=1106666 is requesting that
1499 // an inflated menu can be used again. This is not available
1500 // yet, so inflate each time (yuk!)
1501 MenuInflater inflater = getMenuInflater();
1502 inflater.inflate(R.menu.browsercontext, menu);
1503
1504 // Show the correct menu group
1505 String extra = result.getExtra();
1506 menu.setGroupVisible(R.id.PHONE_MENU,
1507 type == WebView.HitTestResult.PHONE_TYPE);
1508 menu.setGroupVisible(R.id.EMAIL_MENU,
1509 type == WebView.HitTestResult.EMAIL_TYPE);
1510 menu.setGroupVisible(R.id.GEO_MENU,
1511 type == WebView.HitTestResult.GEO_TYPE);
1512 menu.setGroupVisible(R.id.IMAGE_MENU,
1513 type == WebView.HitTestResult.IMAGE_TYPE
1514 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1515 menu.setGroupVisible(R.id.ANCHOR_MENU,
1516 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1517 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1518
1519 // Setup custom handling depending on the type
1520 switch (type) {
1521 case WebView.HitTestResult.PHONE_TYPE:
1522 menu.setHeaderTitle(Uri.decode(extra));
1523 menu.findItem(R.id.dial_context_menu_id).setIntent(
1524 new Intent(Intent.ACTION_VIEW, Uri
1525 .parse(WebView.SCHEME_TEL + extra)));
1526 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1527 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1528 addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1529 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1530 addIntent);
1531 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1532 new Copy(extra));
1533 break;
1534
1535 case WebView.HitTestResult.EMAIL_TYPE:
1536 menu.setHeaderTitle(extra);
1537 menu.findItem(R.id.email_context_menu_id).setIntent(
1538 new Intent(Intent.ACTION_VIEW, Uri
1539 .parse(WebView.SCHEME_MAILTO + extra)));
1540 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1541 new Copy(extra));
1542 break;
1543
1544 case WebView.HitTestResult.GEO_TYPE:
1545 menu.setHeaderTitle(extra);
1546 menu.findItem(R.id.map_context_menu_id).setIntent(
1547 new Intent(Intent.ACTION_VIEW, Uri
1548 .parse(WebView.SCHEME_GEO
1549 + URLEncoder.encode(extra))));
1550 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1551 new Copy(extra));
1552 break;
1553
1554 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1555 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1556 TextView titleView = (TextView) LayoutInflater.from(this)
1557 .inflate(android.R.layout.browser_link_context_header,
1558 null);
1559 titleView.setText(extra);
1560 menu.setHeaderView(titleView);
1561 // decide whether to show the open link in new tab option
1562 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1563 mTabControl.getTabCount() < TabControl.MAX_TABS);
1564 PackageManager pm = getPackageManager();
1565 Intent send = new Intent(Intent.ACTION_SEND);
1566 send.setType("text/plain");
1567 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1568 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1569 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1570 break;
1571 }
1572 // otherwise fall through to handle image part
1573 case WebView.HitTestResult.IMAGE_TYPE:
1574 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1575 menu.setHeaderTitle(extra);
1576 }
1577 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1578 new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1579 menu.findItem(R.id.download_context_menu_id).
1580 setOnMenuItemClickListener(new Download(extra));
1581 break;
1582
1583 default:
1584 Log.w(LOGTAG, "We should not get here.");
1585 break;
1586 }
1587 }
1588
The Android Open Source Project0c908882009-03-03 19:32:16 -08001589 // Attach the given tab to the content view.
1590 private void attachTabToContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001591 // Attach the container that contains the main WebView and any other UI
1592 // associated with the tab.
1593 mContentView.addView(t.getContainer(), COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +01001594
1595 if (mShouldShowErrorConsole) {
1596 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
1597 if (errorConsole.numberOfErrors() == 0) {
1598 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1599 } else {
1600 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1601 }
1602
1603 mErrorConsoleContainer.addView(errorConsole,
1604 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
1605 ViewGroup.LayoutParams.WRAP_CONTENT));
1606 }
1607
The Android Open Source Project0c908882009-03-03 19:32:16 -08001608 // Attach the sub window if necessary
1609 attachSubWindow(t);
1610 // Request focus on the top window.
1611 t.getTopWindow().requestFocus();
1612 }
1613
1614 // Attach a sub window to the main WebView of the given tab.
1615 private void attachSubWindow(TabControl.Tab t) {
1616 // If a sub window exists, attach it to the content view.
1617 final WebView subView = t.getSubWebView();
1618 if (subView != null) {
1619 final View container = t.getSubWebViewContainer();
1620 mContentView.addView(container, COVER_SCREEN_PARAMS);
1621 subView.requestFocus();
1622 }
1623 }
1624
1625 // Remove the given tab from the content view.
1626 private void removeTabFromContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001627 // Remove the container that contains the main WebView.
1628 mContentView.removeView(t.getContainer());
Ben Murdochbff2d602009-07-01 20:19:05 +01001629
1630 if (mTabControl.getCurrentErrorConsole(false) != null) {
1631 mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
1632 }
1633
The Android Open Source Project0c908882009-03-03 19:32:16 -08001634 // Remove the sub window if it exists.
1635 if (t.getSubWebView() != null) {
1636 mContentView.removeView(t.getSubWebViewContainer());
1637 }
1638 }
1639
1640 // Remove the sub window if it exists. Also called by TabControl when the
1641 // user clicks the 'X' to dismiss a sub window.
1642 /* package */ void dismissSubWindow(TabControl.Tab t) {
1643 final WebView mainView = t.getWebView();
1644 if (t.getSubWebView() != null) {
1645 // Remove the container view and request focus on the main WebView.
1646 mContentView.removeView(t.getSubWebViewContainer());
1647 mainView.requestFocus();
1648 // Tell the TabControl to dismiss the subwindow. This will destroy
1649 // the WebView.
1650 mTabControl.dismissSubWindow(t);
1651 }
1652 }
1653
Leon Scroggins1f005d32009-08-10 17:36:42 -04001654 // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001655 // that accepts url as string.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001656 private TabControl.Tab openTabAndShow(String url, boolean closeOnExit,
1657 String appId) {
1658 return openTabAndShow(new UrlData(url), closeOnExit, appId);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001659 }
1660
1661 // This method does a ton of stuff. It will attempt to create a new tab
1662 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
Leon Scroggins1f005d32009-08-10 17:36:42 -04001663 // url isn't null, it will load the given url.
1664 /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001665 boolean closeOnExit, String appId) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001666 final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1667 final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1668 if (newTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001669 final TabControl.Tab tab = mTabControl.createNewTab(
1670 closeOnExit, appId, urlData.mUrl);
1671 WebView webview = tab.getWebView();
1672 if (CUSTOM_BROWSER_BAR) {
1673 mTitleBar.addTab(webview, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001674 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001675 removeTabFromContentView(currentTab);
1676 attachTabToContentView(tab);
Patrick Scott8bbd69f2009-08-14 13:35:53 -04001677 // We must set the new tab as the current tab to reflect the old
1678 // animation behavior.
1679 mTabControl.setCurrentTab(tab);
Leon Scroggins160a7e72009-08-14 18:28:01 -04001680 if (!urlData.isEmpty()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001681 urlData.loadIn(webview);
1682 }
1683 return tab;
1684 } else {
1685 // Get rid of the subwindow if it exists
1686 dismissSubWindow(currentTab);
1687 if (!urlData.isEmpty()) {
1688 // Load the given url.
1689 urlData.loadIn(currentTab.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001690 }
1691 }
Grace Klobac9181842009-04-14 08:53:22 -07001692 return currentTab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001693 }
1694
Grace Klobac9181842009-04-14 08:53:22 -07001695 private TabControl.Tab openTab(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001696 if (mSettings.openInBackground()) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001697 TabControl.Tab t = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001698 if (t != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001699 WebView view = t.getWebView();
1700 if (CUSTOM_BROWSER_BAR) {
1701 mTitleBar.addTab(view, false);
1702 }
1703 view.loadUrl(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001704 }
Grace Klobac9181842009-04-14 08:53:22 -07001705 return t;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001706 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001707 return openTabAndShow(url, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001708 }
1709 }
1710
1711 private class Copy implements OnMenuItemClickListener {
1712 private CharSequence mText;
1713
1714 public boolean onMenuItemClick(MenuItem item) {
1715 copy(mText);
1716 return true;
1717 }
1718
1719 public Copy(CharSequence toCopy) {
1720 mText = toCopy;
1721 }
1722 }
1723
1724 private class Download implements OnMenuItemClickListener {
1725 private String mText;
1726
1727 public boolean onMenuItemClick(MenuItem item) {
1728 onDownloadStartNoStream(mText, null, null, null, -1);
1729 return true;
1730 }
1731
1732 public Download(String toDownload) {
1733 mText = toDownload;
1734 }
1735 }
1736
1737 private void copy(CharSequence text) {
1738 try {
1739 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
1740 if (clip != null) {
1741 clip.setClipboardText(text);
1742 }
1743 } catch (android.os.RemoteException e) {
1744 Log.e(LOGTAG, "Copy failed", e);
1745 }
1746 }
1747
1748 /**
1749 * Resets the browser title-view to whatever it must be (for example, if we
1750 * load a page from history).
1751 */
1752 private void resetTitle() {
1753 resetLockIcon();
1754 resetTitleIconAndProgress();
1755 }
1756
1757 /**
1758 * Resets the browser title-view to whatever it must be
1759 * (for example, if we had a loading error)
1760 * When we have a new page, we call resetTitle, when we
1761 * have to reset the titlebar to whatever it used to be
1762 * (for example, if the user chose to stop loading), we
1763 * call resetTitleAndRevertLockIcon.
1764 */
1765 /* package */ void resetTitleAndRevertLockIcon() {
1766 revertLockIcon();
1767 resetTitleIconAndProgress();
1768 }
1769
1770 /**
1771 * Reset the title, favicon, and progress.
1772 */
1773 private void resetTitleIconAndProgress() {
1774 WebView current = mTabControl.getCurrentWebView();
1775 if (current == null) {
1776 return;
1777 }
1778 resetTitleAndIcon(current);
1779 int progress = current.getProgress();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001780 mWebChromeClient.onProgressChanged(current, progress);
1781 }
1782
1783 // Reset the title and the icon based on the given item.
1784 private void resetTitleAndIcon(WebView view) {
1785 WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
1786 if (item != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001787 setUrlTitle(item.getUrl(), item.getTitle(), view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001788 setFavicon(item.getFavicon());
1789 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001790 setUrlTitle(null, null, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001791 setFavicon(null);
1792 }
1793 }
1794
1795 /**
1796 * Sets a title composed of the URL and the title string.
1797 * @param url The URL of the site being loaded.
1798 * @param title The title of the site being loaded.
1799 */
Leon Scroggins1f005d32009-08-10 17:36:42 -04001800 private void setUrlTitle(String url, String title, WebView view) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001801 mUrl = url;
1802 mTitle = title;
1803
Leon Scroggins1f005d32009-08-10 17:36:42 -04001804 if (CUSTOM_BROWSER_BAR) {
1805 mTitleBar.setTitleAndUrl(title, url, view);
1806 } else {
1807 setTitle(buildUrlTitle(url, title));
The Android Open Source Project0c908882009-03-03 19:32:16 -08001808 }
1809 }
1810
1811 /**
1812 * Builds and returns the page title, which is some
1813 * combination of the page URL and title.
1814 * @param url The URL of the site being loaded.
1815 * @param title The title of the site being loaded.
1816 * @return The page title.
1817 */
1818 private String buildUrlTitle(String url, String title) {
1819 String urlTitle = "";
1820
1821 if (url != null) {
1822 String titleUrl = buildTitleUrl(url);
1823
1824 if (title != null && 0 < title.length()) {
1825 if (titleUrl != null && 0 < titleUrl.length()) {
1826 urlTitle = titleUrl + ": " + title;
1827 } else {
1828 urlTitle = title;
1829 }
1830 } else {
1831 if (titleUrl != null) {
1832 urlTitle = titleUrl;
1833 }
1834 }
1835 }
1836
1837 return urlTitle;
1838 }
1839
1840 /**
1841 * @param url The URL to build a title version of the URL from.
1842 * @return The title version of the URL or null if fails.
1843 * The title version of the URL can be either the URL hostname,
1844 * or the hostname with an "https://" prefix (for secure URLs),
1845 * or an empty string if, for example, the URL in question is a
1846 * file:// URL with no hostname.
1847 */
Leon Scroggins32e14a62009-06-11 10:26:34 -04001848 /* package */ static String buildTitleUrl(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001849 String titleUrl = null;
1850
1851 if (url != null) {
1852 try {
1853 // parse the url string
1854 URL urlObj = new URL(url);
1855 if (urlObj != null) {
1856 titleUrl = "";
1857
1858 String protocol = urlObj.getProtocol();
1859 String host = urlObj.getHost();
1860
1861 if (host != null && 0 < host.length()) {
1862 titleUrl = host;
1863 if (protocol != null) {
1864 // if a secure site, add an "https://" prefix!
1865 if (protocol.equalsIgnoreCase("https")) {
1866 titleUrl = protocol + "://" + host;
1867 }
1868 }
1869 }
1870 }
1871 } catch (MalformedURLException e) {}
1872 }
1873
1874 return titleUrl;
1875 }
1876
1877 // Set the favicon in the title bar.
1878 private void setFavicon(Bitmap icon) {
Leon Scroggins81db3662009-06-04 17:45:11 -04001879 if (CUSTOM_BROWSER_BAR) {
1880 Drawable[] array = new Drawable[3];
1881 array[0] = new PaintDrawable(Color.BLACK);
1882 PaintDrawable p = new PaintDrawable(Color.WHITE);
1883 array[1] = p;
1884 if (icon == null) {
1885 array[2] = mGenericFavicon;
1886 } else {
1887 array[2] = new BitmapDrawable(icon);
1888 }
1889 LayerDrawable d = new LayerDrawable(array);
1890 d.setLayerInset(1, 1, 1, 1, 1);
1891 d.setLayerInset(2, 2, 2, 2, 2);
Leon Scroggins1f005d32009-08-10 17:36:42 -04001892 mTitleBar.setFavicon(d, getTopWindow());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001893 } else {
Leon Scroggins81db3662009-06-04 17:45:11 -04001894 Drawable[] array = new Drawable[2];
1895 PaintDrawable p = new PaintDrawable(Color.WHITE);
1896 p.setCornerRadius(3f);
1897 array[0] = p;
1898 if (icon == null) {
1899 array[1] = mGenericFavicon;
1900 } else {
1901 array[1] = new BitmapDrawable(icon);
1902 }
1903 LayerDrawable d = new LayerDrawable(array);
1904 d.setLayerInset(1, 2, 2, 2, 2);
1905 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001906 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001907 }
1908
1909 /**
1910 * Saves the current lock-icon state before resetting
1911 * the lock icon. If we have an error, we may need to
1912 * roll back to the previous state.
1913 */
1914 private void saveLockIcon() {
1915 mPrevLockType = mLockIconType;
1916 }
1917
1918 /**
1919 * Reverts the lock-icon state to the last saved state,
1920 * for example, if we had an error, and need to cancel
1921 * the load.
1922 */
1923 private void revertLockIcon() {
1924 mLockIconType = mPrevLockType;
1925
Dave Bort31a6d1c2009-04-13 15:56:49 -07001926 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001927 Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
1928 " revert lock icon to " + mLockIconType);
1929 }
1930
1931 updateLockIconImage(mLockIconType);
1932 }
1933
Leon Scroggins1f005d32009-08-10 17:36:42 -04001934 /**
1935 * Close the tab after removing its associated title bar.
1936 */
1937 private void closeTab(TabControl.Tab t) {
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001938 if (CUSTOM_BROWSER_BAR) {
1939 mTitleBar.removeTab(mTabControl.getTabIndex(t));
1940 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001941 mTabControl.removeTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001942 }
1943
1944 private void goBackOnePageOrQuit() {
1945 TabControl.Tab current = mTabControl.getCurrentTab();
1946 if (current == null) {
1947 /*
1948 * Instead of finishing the activity, simply push this to the back
1949 * of the stack and let ActivityManager to choose the foreground
1950 * activity. As BrowserActivity is singleTask, it will be always the
1951 * root of the task. So we can use either true or false for
1952 * moveTaskToBack().
1953 */
1954 moveTaskToBack(true);
1955 }
1956 WebView w = current.getWebView();
1957 if (w.canGoBack()) {
1958 w.goBack();
1959 } else {
1960 // Check to see if we are closing a window that was created by
1961 // another window. If so, we switch back to that window.
1962 TabControl.Tab parent = current.getParentTab();
1963 if (parent != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001964 switchToTab(mTabControl.getTabIndex(parent));
1965 // Now we close the other tab
1966 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001967 } else {
1968 if (current.closeOnExit()) {
1969 if (mTabControl.getTabCount() == 1) {
1970 finish();
1971 return;
1972 }
Mike Reed7bfa63b2009-05-28 11:08:32 -04001973 // call pauseWebViewTimers() now, we won't be able to call
1974 // it in onPause() as the WebView won't be valid.
Grace Klobaec1b5ad2009-08-18 08:42:32 -07001975 // Temporarily change mActivityInPause to be true as
1976 // pauseWebViewTimers() will do nothing if mActivityInPause
1977 // is false.
Grace Kloba918e1d72009-08-13 14:55:06 -07001978 boolean savedState = mActivityInPause;
1979 if (savedState) {
Grace Klobaec1b5ad2009-08-18 08:42:32 -07001980 Log.e(LOGTAG, "BrowserActivity is already paused "
1981 + "while handing goBackOnePageOrQuit.");
Grace Kloba918e1d72009-08-13 14:55:06 -07001982 }
1983 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04001984 pauseWebViewTimers();
Grace Kloba918e1d72009-08-13 14:55:06 -07001985 mActivityInPause = savedState;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001986 removeTabFromContentView(current);
1987 mTabControl.removeTab(current);
1988 }
1989 /*
1990 * Instead of finishing the activity, simply push this to the back
1991 * of the stack and let ActivityManager to choose the foreground
1992 * activity. As BrowserActivity is singleTask, it will be always the
1993 * root of the task. So we can use either true or false for
1994 * moveTaskToBack().
1995 */
1996 moveTaskToBack(true);
1997 }
1998 }
1999 }
2000
2001 public KeyTracker.State onKeyTracker(int keyCode,
2002 KeyEvent event,
2003 KeyTracker.Stage stage,
2004 int duration) {
2005 // if onKeyTracker() is called after activity onStop()
2006 // because of accumulated key events,
2007 // we should ignore it as browser is not active any more.
2008 WebView topWindow = getTopWindow();
Andrei Popescuadc008d2009-06-26 14:11:30 +01002009 if (topWindow == null && mCustomView == null)
The Android Open Source Project0c908882009-03-03 19:32:16 -08002010 return KeyTracker.State.NOT_TRACKING;
2011
2012 if (keyCode == KeyEvent.KEYCODE_BACK) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01002013 // Check if a custom view is currently showing and, if it is, hide it.
2014 if (mCustomView != null) {
2015 mWebChromeClient.onHideCustomView();
2016 return KeyTracker.State.DONE_TRACKING;
2017 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002018 if (stage == KeyTracker.Stage.LONG_REPEAT) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04002019 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002020 return KeyTracker.State.DONE_TRACKING;
2021 } else if (stage == KeyTracker.Stage.UP) {
2022 // FIXME: Currently, we do not have a notion of the
2023 // history picker for the subwindow, but maybe we
2024 // should?
2025 WebView subwindow = mTabControl.getCurrentSubWindow();
2026 if (subwindow != null) {
2027 if (subwindow.canGoBack()) {
2028 subwindow.goBack();
2029 } else {
2030 dismissSubWindow(mTabControl.getCurrentTab());
2031 }
2032 } else {
2033 goBackOnePageOrQuit();
2034 }
2035 return KeyTracker.State.DONE_TRACKING;
2036 }
2037 return KeyTracker.State.KEEP_TRACKING;
2038 }
2039 return KeyTracker.State.NOT_TRACKING;
2040 }
2041
2042 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2043 if (keyCode == KeyEvent.KEYCODE_MENU) {
2044 mMenuIsDown = true;
Grace Kloba6ee9c492009-07-13 10:04:34 -07002045 } else if (mMenuIsDown) {
2046 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2047 // still down, we don't want to trigger the search. Pretend to
2048 // consume the key and do nothing.
2049 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002050 }
2051 boolean handled = mKeyTracker.doKeyDown(keyCode, event);
2052 if (!handled) {
2053 switch (keyCode) {
2054 case KeyEvent.KEYCODE_SPACE:
2055 if (event.isShiftPressed()) {
2056 getTopWindow().pageUp(false);
2057 } else {
2058 getTopWindow().pageDown(false);
2059 }
2060 handled = true;
2061 break;
2062
2063 default:
2064 break;
2065 }
2066 }
2067 return handled || super.onKeyDown(keyCode, event);
2068 }
2069
2070 @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2071 if (keyCode == KeyEvent.KEYCODE_MENU) {
2072 mMenuIsDown = false;
2073 }
2074 return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2075 }
2076
2077 private void stopLoading() {
2078 resetTitleAndRevertLockIcon();
2079 WebView w = getTopWindow();
2080 w.stopLoading();
2081 mWebViewClient.onPageFinished(w, w.getUrl());
2082
2083 cancelStopToast();
2084 mStopToast = Toast
2085 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2086 mStopToast.show();
2087 }
2088
2089 private void cancelStopToast() {
2090 if (mStopToast != null) {
2091 mStopToast.cancel();
2092 mStopToast = null;
2093 }
2094 }
2095
2096 // called by a non-UI thread to post the message
2097 public void postMessage(int what, int arg1, int arg2, Object obj) {
2098 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2099 }
2100
2101 // public message ids
2102 public final static int LOAD_URL = 1001;
2103 public final static int STOP_LOAD = 1002;
2104
2105 // Message Ids
2106 private static final int FOCUS_NODE_HREF = 102;
2107 private static final int CANCEL_CREDS_REQUEST = 103;
Grace Kloba92c18a52009-07-31 23:48:32 -07002108 private static final int RELEASE_WAKELOCK = 107;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002109
2110 // Private handler for handling javascript and saving passwords
2111 private Handler mHandler = new Handler() {
2112
2113 public void handleMessage(Message msg) {
2114 switch (msg.what) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002115 case FOCUS_NODE_HREF:
2116 String url = (String) msg.getData().get("url");
2117 if (url == null || url.length() == 0) {
2118 break;
2119 }
2120 HashMap focusNodeMap = (HashMap) msg.obj;
2121 WebView view = (WebView) focusNodeMap.get("webview");
2122 // Only apply the action if the top window did not change.
2123 if (getTopWindow() != view) {
2124 break;
2125 }
2126 switch (msg.arg1) {
2127 case R.id.open_context_menu_id:
2128 case R.id.view_image_context_menu_id:
2129 loadURL(getTopWindow(), url);
2130 break;
2131 case R.id.open_newtab_context_menu_id:
Grace Klobac9181842009-04-14 08:53:22 -07002132 final TabControl.Tab parent = mTabControl
2133 .getCurrentTab();
2134 final TabControl.Tab newTab = openTab(url);
2135 if (newTab != parent) {
2136 parent.addChildTab(newTab);
2137 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002138 break;
2139 case R.id.bookmark_context_menu_id:
2140 Intent intent = new Intent(BrowserActivity.this,
2141 AddBookmarkPage.class);
2142 intent.putExtra("url", url);
2143 startActivity(intent);
2144 break;
2145 case R.id.share_link_context_menu_id:
2146 Browser.sendString(BrowserActivity.this, url);
2147 break;
2148 case R.id.copy_link_context_menu_id:
2149 copy(url);
2150 break;
2151 case R.id.save_link_context_menu_id:
2152 case R.id.download_context_menu_id:
2153 onDownloadStartNoStream(url, null, null, null, -1);
2154 break;
2155 }
2156 break;
2157
2158 case LOAD_URL:
2159 loadURL(getTopWindow(), (String) msg.obj);
2160 break;
2161
2162 case STOP_LOAD:
2163 stopLoading();
2164 break;
2165
2166 case CANCEL_CREDS_REQUEST:
2167 resumeAfterCredentials();
2168 break;
2169
The Android Open Source Project0c908882009-03-03 19:32:16 -08002170 case RELEASE_WAKELOCK:
2171 if (mWakeLock.isHeld()) {
2172 mWakeLock.release();
2173 }
2174 break;
2175 }
2176 }
2177 };
2178
Leon Scroggins89c6d362009-07-15 16:54:37 -04002179 private void updateScreenshot(WebView view) {
2180 // If this is a bookmarked site, add a screenshot to the database.
2181 // FIXME: When should we update? Every time?
2182 // FIXME: Would like to make sure there is actually something to
2183 // draw, but the API for that (WebViewCore.pictureReady()) is not
2184 // currently accessible here.
Patrick Scott3918d442009-08-04 13:22:29 -04002185 ContentResolver cr = getContentResolver();
2186 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04002187 cr, view.getOriginalUrl(), view.getUrl(), false);
Patrick Scott3918d442009-08-04 13:22:29 -04002188 if (c != null) {
Leon Scroggins89c6d362009-07-15 16:54:37 -04002189 boolean succeed = c.moveToFirst();
2190 ContentValues values = null;
2191 while (succeed) {
2192 if (values == null) {
2193 final ByteArrayOutputStream os
2194 = new ByteArrayOutputStream();
2195 Picture thumbnail = view.capturePicture();
2196 // Keep width and height in sync with BrowserBookmarksPage
2197 // and bookmark_thumb
2198 Bitmap bm = Bitmap.createBitmap(100, 80,
2199 Bitmap.Config.ARGB_4444);
2200 Canvas canvas = new Canvas(bm);
2201 // May need to tweak these values to determine what is the
2202 // best scale factor
2203 canvas.scale(.5f, .5f);
2204 thumbnail.draw(canvas);
2205 bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2206 values = new ContentValues();
2207 values.put(Browser.BookmarkColumns.THUMBNAIL,
2208 os.toByteArray());
2209 }
2210 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2211 c.getInt(0)), values, null, null);
2212 succeed = c.moveToNext();
2213 }
2214 c.close();
2215 }
2216 }
2217
The Android Open Source Project0c908882009-03-03 19:32:16 -08002218 // -------------------------------------------------------------------------
2219 // WebViewClient implementation.
2220 //-------------------------------------------------------------------------
2221
2222 // Use in overrideUrlLoading
2223 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2224 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2225 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2226 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2227
2228 /* package */ WebViewClient getWebViewClient() {
2229 return mWebViewClient;
2230 }
2231
Patrick Scott3918d442009-08-04 13:22:29 -04002232 private void updateIcon(WebView view, Bitmap icon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002233 if (icon != null) {
2234 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
Patrick Scott3918d442009-08-04 13:22:29 -04002235 view, icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002236 }
2237 setFavicon(icon);
2238 }
2239
2240 private final WebViewClient mWebViewClient = new WebViewClient() {
2241 @Override
2242 public void onPageStarted(WebView view, String url, Bitmap favicon) {
2243 resetLockIcon(url);
Leon Scroggins1f005d32009-08-10 17:36:42 -04002244 setUrlTitle(url, null, view);
Ben Murdochbff2d602009-07-01 20:19:05 +01002245
2246 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2247 if (errorConsole != null) {
2248 errorConsole.clearErrorMessages();
2249 if (mShouldShowErrorConsole) {
2250 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2251 }
2252 }
2253
The Android Open Source Project0c908882009-03-03 19:32:16 -08002254 // Call updateIcon instead of setFavicon so the bookmark
2255 // database can be updated.
Patrick Scott3918d442009-08-04 13:22:29 -04002256 updateIcon(view, favicon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002257
Grace Kloba4d7880f2009-08-12 09:35:42 -07002258 if (mSettings.isTracing()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002259 String host;
2260 try {
2261 WebAddress uri = new WebAddress(url);
2262 host = uri.mHost;
2263 } catch (android.net.ParseException ex) {
Grace Kloba4d7880f2009-08-12 09:35:42 -07002264 host = "browser";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002265 }
2266 host = host.replace('.', '_');
Grace Kloba4d7880f2009-08-12 09:35:42 -07002267 host += ".trace";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002268 mInTrace = true;
Grace Kloba4d7880f2009-08-12 09:35:42 -07002269 Debug.startMethodTracing(host, 20 * 1024 * 1024);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002270 }
2271
2272 // Performance probe
2273 if (false) {
2274 mStart = SystemClock.uptimeMillis();
2275 mProcessStart = Process.getElapsedCpuTime();
2276 long[] sysCpu = new long[7];
2277 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2278 sysCpu, null)) {
2279 mUserStart = sysCpu[0] + sysCpu[1];
2280 mSystemStart = sysCpu[2];
2281 mIdleStart = sysCpu[3];
2282 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2283 }
2284 mUiStart = SystemClock.currentThreadTimeMillis();
2285 }
2286
2287 if (!mPageStarted) {
2288 mPageStarted = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002289 // if onResume() has been called, resumeWebViewTimers() does
2290 // nothing.
2291 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002292 }
2293
2294 // reset sync timer to avoid sync starts during loading a page
2295 CookieSyncManager.getInstance().resetSync();
2296
2297 mInLoad = true;
2298 updateInLoadMenuItems();
2299 if (!mIsNetworkUp) {
2300 if ( mAlertDialog == null) {
2301 mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
2302 .setTitle(R.string.loadSuspendedTitle)
2303 .setMessage(R.string.loadSuspended)
2304 .setPositiveButton(R.string.ok, null)
2305 .show();
2306 }
2307 if (view != null) {
2308 view.setNetworkAvailable(false);
2309 }
2310 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002311 }
2312
2313 @Override
2314 public void onPageFinished(WebView view, String url) {
2315 // Reset the title and icon in case we stopped a provisional
2316 // load.
2317 resetTitleAndIcon(view);
2318
2319 // Update the lock icon image only once we are done loading
2320 updateLockIconImage(mLockIconType);
Leon Scroggins89c6d362009-07-15 16:54:37 -04002321 updateScreenshot(view);
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -04002322
The Android Open Source Project0c908882009-03-03 19:32:16 -08002323 // Performance probe
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002324 if (false) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002325 long[] sysCpu = new long[7];
2326 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2327 sysCpu, null)) {
2328 String uiInfo = "UI thread used "
2329 + (SystemClock.currentThreadTimeMillis() - mUiStart)
2330 + " ms";
Dave Bort31a6d1c2009-04-13 15:56:49 -07002331 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002332 Log.d(LOGTAG, uiInfo);
2333 }
2334 //The string that gets written to the log
2335 String performanceString = "It took total "
2336 + (SystemClock.uptimeMillis() - mStart)
2337 + " ms clock time to load the page."
2338 + "\nbrowser process used "
2339 + (Process.getElapsedCpuTime() - mProcessStart)
2340 + " ms, user processes used "
2341 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2342 + " ms, kernel used "
2343 + (sysCpu[2] - mSystemStart) * 10
2344 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2345 + " ms and irq took "
2346 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2347 * 10 + " ms, " + uiInfo;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002348 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002349 Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2350 }
2351 if (url != null) {
2352 // strip the url to maintain consistency
2353 String newUrl = new String(url);
2354 if (newUrl.startsWith("http://www.")) {
2355 newUrl = newUrl.substring(11);
2356 } else if (newUrl.startsWith("http://")) {
2357 newUrl = newUrl.substring(7);
2358 } else if (newUrl.startsWith("https://www.")) {
2359 newUrl = newUrl.substring(12);
2360 } else if (newUrl.startsWith("https://")) {
2361 newUrl = newUrl.substring(8);
2362 }
Dave Bort31a6d1c2009-04-13 15:56:49 -07002363 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002364 Log.d(LOGTAG, newUrl + " loaded");
2365 }
2366 /*
2367 if (sWhiteList.contains(newUrl)) {
2368 // The string that gets pushed to the statistcs
2369 // service
2370 performanceString = performanceString
2371 + "\nWebpage: "
2372 + newUrl
2373 + "\nCarrier: "
2374 + android.os.SystemProperties
2375 .get("gsm.sim.operator.alpha");
2376 if (mWebView != null
2377 && mWebView.getContext() != null
2378 && mWebView.getContext().getSystemService(
2379 Context.CONNECTIVITY_SERVICE) != null) {
2380 ConnectivityManager cManager =
2381 (ConnectivityManager) mWebView
2382 .getContext().getSystemService(
2383 Context.CONNECTIVITY_SERVICE);
2384 NetworkInfo nInfo = cManager
2385 .getActiveNetworkInfo();
2386 if (nInfo != null) {
2387 performanceString = performanceString
2388 + "\nNetwork Type: "
2389 + nInfo.getType().toString();
2390 }
2391 }
2392 Checkin.logEvent(mResolver,
2393 Checkin.Events.Tag.WEBPAGE_LOAD,
2394 performanceString);
2395 Log.w(LOGTAG, "pushed to the statistics service");
2396 }
2397 */
2398 }
2399 }
2400 }
2401
2402 if (mInTrace) {
2403 mInTrace = false;
2404 Debug.stopMethodTracing();
2405 }
2406
2407 if (mPageStarted) {
2408 mPageStarted = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002409 // pauseWebViewTimers() will do nothing and return false if
2410 // onPause() is not called yet.
2411 if (pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002412 if (mWakeLock.isHeld()) {
2413 mHandler.removeMessages(RELEASE_WAKELOCK);
2414 mWakeLock.release();
2415 }
2416 }
2417 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002418 }
2419
2420 // return true if want to hijack the url to let another app to handle it
2421 @Override
2422 public boolean shouldOverrideUrlLoading(WebView view, String url) {
2423 if (url.startsWith(SCHEME_WTAI)) {
2424 // wtai://wp/mc;number
2425 // number=string(phone-number)
2426 if (url.startsWith(SCHEME_WTAI_MC)) {
2427 Intent intent = new Intent(Intent.ACTION_VIEW,
2428 Uri.parse(WebView.SCHEME_TEL +
2429 url.substring(SCHEME_WTAI_MC.length())));
2430 startActivity(intent);
2431 return true;
2432 }
2433 // wtai://wp/sd;dtmf
2434 // dtmf=string(dialstring)
2435 if (url.startsWith(SCHEME_WTAI_SD)) {
2436 // TODO
2437 // only send when there is active voice connection
2438 return false;
2439 }
2440 // wtai://wp/ap;number;name
2441 // number=string(phone-number)
2442 // name=string
2443 if (url.startsWith(SCHEME_WTAI_AP)) {
2444 // TODO
2445 return false;
2446 }
2447 }
2448
Dianne Hackborn99189432009-06-17 18:06:18 -07002449 // The "about:" schemes are internal to the browser; don't
2450 // want these to be dispatched to other apps.
2451 if (url.startsWith("about:")) {
2452 return false;
2453 }
Ben Murdochbff2d602009-07-01 20:19:05 +01002454
Dianne Hackborn99189432009-06-17 18:06:18 -07002455 Intent intent;
Ben Murdochbff2d602009-07-01 20:19:05 +01002456
Dianne Hackborn99189432009-06-17 18:06:18 -07002457 // perform generic parsing of the URI to turn it into an Intent.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002458 try {
Dianne Hackborn99189432009-06-17 18:06:18 -07002459 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2460 } catch (URISyntaxException ex) {
2461 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
The Android Open Source Project0c908882009-03-03 19:32:16 -08002462 return false;
2463 }
2464
Grace Kloba5b078b52009-06-24 20:23:41 -07002465 // check whether the intent can be resolved. If not, we will see
2466 // whether we can download it from the Market.
2467 if (getPackageManager().resolveActivity(intent, 0) == null) {
2468 String packagename = intent.getPackage();
2469 if (packagename != null) {
2470 intent = new Intent(Intent.ACTION_VIEW, Uri
2471 .parse("market://search?q=pname:" + packagename));
2472 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2473 startActivity(intent);
2474 return true;
2475 } else {
2476 return false;
2477 }
2478 }
2479
Dianne Hackborn99189432009-06-17 18:06:18 -07002480 // sanitize the Intent, ensuring web pages can not bypass browser
2481 // security (only access to BROWSABLE activities).
The Android Open Source Project0c908882009-03-03 19:32:16 -08002482 intent.addCategory(Intent.CATEGORY_BROWSABLE);
Dianne Hackborn99189432009-06-17 18:06:18 -07002483 intent.setComponent(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002484 try {
2485 if (startActivityIfNeeded(intent, -1)) {
2486 return true;
2487 }
2488 } catch (ActivityNotFoundException ex) {
2489 // ignore the error. If no application can handle the URL,
2490 // eg about:blank, assume the browser can handle it.
2491 }
2492
2493 if (mMenuIsDown) {
2494 openTab(url);
2495 closeOptionsMenu();
2496 return true;
2497 }
2498
2499 return false;
2500 }
2501
2502 /**
2503 * Updates the lock icon. This method is called when we discover another
2504 * resource to be loaded for this page (for example, javascript). While
2505 * we update the icon type, we do not update the lock icon itself until
2506 * we are done loading, it is slightly more secure this way.
2507 */
2508 @Override
2509 public void onLoadResource(WebView view, String url) {
2510 if (url != null && url.length() > 0) {
2511 // It is only if the page claims to be secure
2512 // that we may have to update the lock:
2513 if (mLockIconType == LOCK_ICON_SECURE) {
2514 // If NOT a 'safe' url, change the lock to mixed content!
2515 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2516 mLockIconType = LOCK_ICON_MIXED;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002517 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002518 Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2519 " updated lock icon to " + mLockIconType + " due to " + url);
2520 }
2521 }
2522 }
2523 }
2524 }
2525
2526 /**
2527 * Show the dialog, asking the user if they would like to continue after
2528 * an excessive number of HTTP redirects.
2529 */
2530 @Override
2531 public void onTooManyRedirects(WebView view, final Message cancelMsg,
2532 final Message continueMsg) {
2533 new AlertDialog.Builder(BrowserActivity.this)
2534 .setTitle(R.string.browserFrameRedirect)
2535 .setMessage(R.string.browserFrame307Post)
2536 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2537 public void onClick(DialogInterface dialog, int which) {
2538 continueMsg.sendToTarget();
2539 }})
2540 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2541 public void onClick(DialogInterface dialog, int which) {
2542 cancelMsg.sendToTarget();
2543 }})
2544 .setOnCancelListener(new OnCancelListener() {
2545 public void onCancel(DialogInterface dialog) {
2546 cancelMsg.sendToTarget();
2547 }})
2548 .show();
2549 }
2550
Patrick Scott37911c72009-03-24 18:02:58 -07002551 // Container class for the next error dialog that needs to be
2552 // displayed.
2553 class ErrorDialog {
2554 public final int mTitle;
2555 public final String mDescription;
2556 public final int mError;
2557 ErrorDialog(int title, String desc, int error) {
2558 mTitle = title;
2559 mDescription = desc;
2560 mError = error;
2561 }
2562 };
2563
2564 private void processNextError() {
2565 if (mQueuedErrors == null) {
2566 return;
2567 }
2568 // The first one is currently displayed so just remove it.
2569 mQueuedErrors.removeFirst();
2570 if (mQueuedErrors.size() == 0) {
2571 mQueuedErrors = null;
2572 return;
2573 }
2574 showError(mQueuedErrors.getFirst());
2575 }
2576
2577 private DialogInterface.OnDismissListener mDialogListener =
2578 new DialogInterface.OnDismissListener() {
2579 public void onDismiss(DialogInterface d) {
2580 processNextError();
2581 }
2582 };
2583 private LinkedList<ErrorDialog> mQueuedErrors;
2584
2585 private void queueError(int err, String desc) {
2586 if (mQueuedErrors == null) {
2587 mQueuedErrors = new LinkedList<ErrorDialog>();
2588 }
2589 for (ErrorDialog d : mQueuedErrors) {
2590 if (d.mError == err) {
2591 // Already saw a similar error, ignore the new one.
2592 return;
2593 }
2594 }
2595 ErrorDialog errDialog = new ErrorDialog(
2596 err == EventHandler.FILE_NOT_FOUND_ERROR ?
2597 R.string.browserFrameFileErrorLabel :
2598 R.string.browserFrameNetworkErrorLabel,
2599 desc, err);
2600 mQueuedErrors.addLast(errDialog);
2601
2602 // Show the dialog now if the queue was empty.
2603 if (mQueuedErrors.size() == 1) {
2604 showError(errDialog);
2605 }
2606 }
2607
2608 private void showError(ErrorDialog errDialog) {
2609 AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
2610 .setTitle(errDialog.mTitle)
2611 .setMessage(errDialog.mDescription)
2612 .setPositiveButton(R.string.ok, null)
2613 .create();
2614 d.setOnDismissListener(mDialogListener);
2615 d.show();
2616 }
2617
The Android Open Source Project0c908882009-03-03 19:32:16 -08002618 /**
2619 * Show a dialog informing the user of the network error reported by
2620 * WebCore.
2621 */
2622 @Override
2623 public void onReceivedError(WebView view, int errorCode,
2624 String description, String failingUrl) {
2625 if (errorCode != EventHandler.ERROR_LOOKUP &&
2626 errorCode != EventHandler.ERROR_CONNECT &&
2627 errorCode != EventHandler.ERROR_BAD_URL &&
2628 errorCode != EventHandler.ERROR_UNSUPPORTED_SCHEME &&
2629 errorCode != EventHandler.FILE_ERROR) {
Patrick Scott37911c72009-03-24 18:02:58 -07002630 queueError(errorCode, description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002631 }
Patrick Scott37911c72009-03-24 18:02:58 -07002632 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
2633 + " " + description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002634
2635 // We need to reset the title after an error.
2636 resetTitleAndRevertLockIcon();
2637 }
2638
2639 /**
2640 * Check with the user if it is ok to resend POST data as the page they
2641 * are trying to navigate to is the result of a POST.
2642 */
2643 @Override
2644 public void onFormResubmission(WebView view, final Message dontResend,
2645 final Message resend) {
2646 new AlertDialog.Builder(BrowserActivity.this)
2647 .setTitle(R.string.browserFrameFormResubmitLabel)
2648 .setMessage(R.string.browserFrameFormResubmitMessage)
2649 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2650 public void onClick(DialogInterface dialog, int which) {
2651 resend.sendToTarget();
2652 }})
2653 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2654 public void onClick(DialogInterface dialog, int which) {
2655 dontResend.sendToTarget();
2656 }})
2657 .setOnCancelListener(new OnCancelListener() {
2658 public void onCancel(DialogInterface dialog) {
2659 dontResend.sendToTarget();
2660 }})
2661 .show();
2662 }
2663
2664 /**
2665 * Insert the url into the visited history database.
2666 * @param url The url to be inserted.
2667 * @param isReload True if this url is being reloaded.
2668 * FIXME: Not sure what to do when reloading the page.
2669 */
2670 @Override
2671 public void doUpdateVisitedHistory(WebView view, String url,
2672 boolean isReload) {
2673 if (url.regionMatches(true, 0, "about:", 0, 6)) {
2674 return;
2675 }
2676 Browser.updateVisitedHistory(mResolver, url, true);
2677 WebIconDatabase.getInstance().retainIconForPageUrl(url);
2678 }
2679
2680 /**
2681 * Displays SSL error(s) dialog to the user.
2682 */
2683 @Override
2684 public void onReceivedSslError(
2685 final WebView view, final SslErrorHandler handler, final SslError error) {
2686
2687 if (mSettings.showSecurityWarnings()) {
2688 final LayoutInflater factory =
2689 LayoutInflater.from(BrowserActivity.this);
2690 final View warningsView =
2691 factory.inflate(R.layout.ssl_warnings, null);
2692 final LinearLayout placeholder =
2693 (LinearLayout)warningsView.findViewById(R.id.placeholder);
2694
2695 if (error.hasError(SslError.SSL_UNTRUSTED)) {
2696 LinearLayout ll = (LinearLayout)factory
2697 .inflate(R.layout.ssl_warning, null);
2698 ((TextView)ll.findViewById(R.id.warning))
2699 .setText(R.string.ssl_untrusted);
2700 placeholder.addView(ll);
2701 }
2702
2703 if (error.hasError(SslError.SSL_IDMISMATCH)) {
2704 LinearLayout ll = (LinearLayout)factory
2705 .inflate(R.layout.ssl_warning, null);
2706 ((TextView)ll.findViewById(R.id.warning))
2707 .setText(R.string.ssl_mismatch);
2708 placeholder.addView(ll);
2709 }
2710
2711 if (error.hasError(SslError.SSL_EXPIRED)) {
2712 LinearLayout ll = (LinearLayout)factory
2713 .inflate(R.layout.ssl_warning, null);
2714 ((TextView)ll.findViewById(R.id.warning))
2715 .setText(R.string.ssl_expired);
2716 placeholder.addView(ll);
2717 }
2718
2719 if (error.hasError(SslError.SSL_NOTYETVALID)) {
2720 LinearLayout ll = (LinearLayout)factory
2721 .inflate(R.layout.ssl_warning, null);
2722 ((TextView)ll.findViewById(R.id.warning))
2723 .setText(R.string.ssl_not_yet_valid);
2724 placeholder.addView(ll);
2725 }
2726
2727 new AlertDialog.Builder(BrowserActivity.this)
2728 .setTitle(R.string.security_warning)
2729 .setIcon(android.R.drawable.ic_dialog_alert)
2730 .setView(warningsView)
2731 .setPositiveButton(R.string.ssl_continue,
2732 new DialogInterface.OnClickListener() {
2733 public void onClick(DialogInterface dialog, int whichButton) {
2734 handler.proceed();
2735 }
2736 })
2737 .setNeutralButton(R.string.view_certificate,
2738 new DialogInterface.OnClickListener() {
2739 public void onClick(DialogInterface dialog, int whichButton) {
2740 showSSLCertificateOnError(view, handler, error);
2741 }
2742 })
2743 .setNegativeButton(R.string.cancel,
2744 new DialogInterface.OnClickListener() {
2745 public void onClick(DialogInterface dialog, int whichButton) {
2746 handler.cancel();
2747 BrowserActivity.this.resetTitleAndRevertLockIcon();
2748 }
2749 })
2750 .setOnCancelListener(
2751 new DialogInterface.OnCancelListener() {
2752 public void onCancel(DialogInterface dialog) {
2753 handler.cancel();
2754 BrowserActivity.this.resetTitleAndRevertLockIcon();
2755 }
2756 })
2757 .show();
2758 } else {
2759 handler.proceed();
2760 }
2761 }
2762
2763 /**
2764 * Handles an HTTP authentication request.
2765 *
2766 * @param handler The authentication handler
2767 * @param host The host
2768 * @param realm The realm
2769 */
2770 @Override
2771 public void onReceivedHttpAuthRequest(WebView view,
2772 final HttpAuthHandler handler, final String host, final String realm) {
2773 String username = null;
2774 String password = null;
2775
2776 boolean reuseHttpAuthUsernamePassword =
2777 handler.useHttpAuthUsernamePassword();
2778
2779 if (reuseHttpAuthUsernamePassword &&
2780 (mTabControl.getCurrentWebView() != null)) {
2781 String[] credentials =
2782 mTabControl.getCurrentWebView()
2783 .getHttpAuthUsernamePassword(host, realm);
2784 if (credentials != null && credentials.length == 2) {
2785 username = credentials[0];
2786 password = credentials[1];
2787 }
2788 }
2789
2790 if (username != null && password != null) {
2791 handler.proceed(username, password);
2792 } else {
2793 showHttpAuthentication(handler, host, realm, null, null, null, 0);
2794 }
2795 }
2796
2797 @Override
2798 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
2799 if (mMenuIsDown) {
2800 // only check shortcut key when MENU is held
2801 return getWindow().isShortcutKey(event.getKeyCode(), event);
2802 } else {
2803 return false;
2804 }
2805 }
2806
2807 @Override
2808 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
2809 if (view != mTabControl.getCurrentTopWebView()) {
2810 return;
2811 }
2812 if (event.isDown()) {
2813 BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
2814 } else {
2815 BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
2816 }
2817 }
2818 };
2819
2820 //--------------------------------------------------------------------------
2821 // WebChromeClient implementation
2822 //--------------------------------------------------------------------------
2823
2824 /* package */ WebChromeClient getWebChromeClient() {
2825 return mWebChromeClient;
2826 }
2827
2828 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
2829 // Helper method to create a new tab or sub window.
2830 private void createWindow(final boolean dialog, final Message msg) {
2831 if (dialog) {
2832 mTabControl.createSubWindow();
2833 final TabControl.Tab t = mTabControl.getCurrentTab();
2834 attachSubWindow(t);
2835 WebView.WebViewTransport transport =
2836 (WebView.WebViewTransport) msg.obj;
2837 transport.setWebView(t.getSubWebView());
2838 msg.sendToTarget();
2839 } else {
2840 final TabControl.Tab parent = mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04002841 final TabControl.Tab newTab
2842 = openTabAndShow(EMPTY_URL_DATA, false, null);
Grace Klobac9181842009-04-14 08:53:22 -07002843 if (newTab != parent) {
2844 parent.addChildTab(newTab);
2845 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002846 WebView.WebViewTransport transport =
2847 (WebView.WebViewTransport) msg.obj;
2848 transport.setWebView(mTabControl.getCurrentWebView());
Leon Scroggins1f005d32009-08-10 17:36:42 -04002849 msg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002850 }
2851 }
2852
2853 @Override
Leon Scroggins4943a312009-08-07 16:12:57 -04002854 public void onChangeViewingMode(boolean toZoomedOut) {
2855 if (!CUSTOM_BROWSER_BAR) {
2856 return;
2857 }
2858 if (toZoomedOut) {
2859 // FIXME: animate the title bar into view
2860 mTitleBar.setVisibility(View.VISIBLE);
2861 } else {
2862 // FXIME: animate the title bar out of view
2863 mTitleBar.setVisibility(View.GONE);
2864 }
2865 }
2866
2867 @Override
The Android Open Source Project0c908882009-03-03 19:32:16 -08002868 public boolean onCreateWindow(WebView view, final boolean dialog,
2869 final boolean userGesture, final Message resultMsg) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002870 // Short-circuit if we can't create any more tabs or sub windows.
2871 if (dialog && mTabControl.getCurrentSubWindow() != null) {
2872 new AlertDialog.Builder(BrowserActivity.this)
2873 .setTitle(R.string.too_many_subwindows_dialog_title)
2874 .setIcon(android.R.drawable.ic_dialog_alert)
2875 .setMessage(R.string.too_many_subwindows_dialog_message)
2876 .setPositiveButton(R.string.ok, null)
2877 .show();
2878 return false;
2879 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
2880 new AlertDialog.Builder(BrowserActivity.this)
2881 .setTitle(R.string.too_many_windows_dialog_title)
2882 .setIcon(android.R.drawable.ic_dialog_alert)
2883 .setMessage(R.string.too_many_windows_dialog_message)
2884 .setPositiveButton(R.string.ok, null)
2885 .show();
2886 return false;
2887 }
2888
2889 // Short-circuit if this was a user gesture.
2890 if (userGesture) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002891 createWindow(dialog, resultMsg);
2892 return true;
2893 }
2894
2895 // Allow the popup and create the appropriate window.
2896 final AlertDialog.OnClickListener allowListener =
2897 new AlertDialog.OnClickListener() {
2898 public void onClick(DialogInterface d,
2899 int which) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002900 createWindow(dialog, resultMsg);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002901 }
2902 };
2903
2904 // Block the popup by returning a null WebView.
2905 final AlertDialog.OnClickListener blockListener =
2906 new AlertDialog.OnClickListener() {
2907 public void onClick(DialogInterface d, int which) {
2908 resultMsg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002909 }
2910 };
2911
2912 // Build a confirmation dialog to display to the user.
2913 final AlertDialog d =
2914 new AlertDialog.Builder(BrowserActivity.this)
2915 .setTitle(R.string.attention)
2916 .setIcon(android.R.drawable.ic_dialog_alert)
2917 .setMessage(R.string.popup_window_attempt)
2918 .setPositiveButton(R.string.allow, allowListener)
2919 .setNegativeButton(R.string.block, blockListener)
2920 .setCancelable(false)
2921 .create();
2922
2923 // Show the confirmation dialog.
2924 d.show();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002925 return true;
2926 }
2927
2928 @Override
2929 public void onCloseWindow(WebView window) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002930 final TabControl.Tab current = mTabControl.getCurrentTab();
2931 final TabControl.Tab parent = current.getParentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002932 if (parent != null) {
2933 // JavaScript can only close popup window.
Leon Scroggins1f005d32009-08-10 17:36:42 -04002934 switchToTab(mTabControl.getTabIndex(parent));
2935 // Now we need to close the window
2936 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002937 }
2938 }
2939
2940 @Override
2941 public void onProgressChanged(WebView view, int newProgress) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002942 if (CUSTOM_BROWSER_BAR) {
2943 mTitleBar.setProgress(newProgress, view);
2944 } else {
2945 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
2946 newProgress * 100);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002947 }
2948
2949 if (newProgress == 100) {
2950 // onProgressChanged() is called for sub-frame too while
2951 // onPageFinished() is only called for the main frame. sync
2952 // cookie and cache promptly here.
2953 CookieSyncManager.getInstance().sync();
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002954 if (mInLoad) {
2955 mInLoad = false;
2956 updateInLoadMenuItems();
2957 }
2958 } else {
2959 // onPageFinished may have already been called but a subframe
2960 // is still loading and updating the progress. Reset mInLoad
2961 // and update the menu items.
2962 if (!mInLoad) {
2963 mInLoad = true;
2964 updateInLoadMenuItems();
2965 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002966 }
2967 }
2968
2969 @Override
2970 public void onReceivedTitle(WebView view, String title) {
Patrick Scott598c9cc2009-06-04 11:10:38 -04002971 String url = view.getUrl();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002972
2973 // here, if url is null, we want to reset the title
Leon Scroggins1f005d32009-08-10 17:36:42 -04002974 setUrlTitle(url, title, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002975
2976 if (url == null ||
2977 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
2978 return;
2979 }
Leon Scrogginsfce182b2009-05-08 13:54:52 -04002980 // See if we can find the current url in our history database and
2981 // add the new title to it.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002982 if (url.startsWith("http://www.")) {
2983 url = url.substring(11);
2984 } else if (url.startsWith("http://")) {
2985 url = url.substring(4);
2986 }
2987 try {
2988 url = "%" + url;
2989 String [] selArgs = new String[] { url };
2990
2991 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
2992 + Browser.BookmarkColumns.BOOKMARK + " = 0";
2993 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
2994 Browser.HISTORY_PROJECTION, where, selArgs, null);
2995 if (c.moveToFirst()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002996 // Current implementation of database only has one entry per
2997 // url.
Leon Scrogginsfce182b2009-05-08 13:54:52 -04002998 ContentValues map = new ContentValues();
2999 map.put(Browser.BookmarkColumns.TITLE, title);
3000 mResolver.update(Browser.BOOKMARKS_URI, map,
3001 "_id = " + c.getInt(0), null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003002 }
3003 c.close();
3004 } catch (IllegalStateException e) {
3005 Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3006 } catch (SQLiteException ex) {
3007 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3008 }
3009 }
3010
3011 @Override
3012 public void onReceivedIcon(WebView view, Bitmap icon) {
Patrick Scott3918d442009-08-04 13:22:29 -04003013 updateIcon(view, icon);
3014 }
3015
3016 @Override
3017 public void onReceivedTouchIconUrl(WebView view, String url) {
3018 final ContentResolver cr = getContentResolver();
3019 final Cursor c =
3020 BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04003021 view.getOriginalUrl(), view.getUrl(), true);
Patrick Scott3918d442009-08-04 13:22:29 -04003022 if (c != null) {
3023 if (c.getCount() > 0) {
3024 new DownloadTouchIcon(cr, c, view).execute(url);
3025 } else {
3026 c.close();
3027 }
3028 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003029 }
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003030
Andrei Popescuadc008d2009-06-26 14:11:30 +01003031 @Override
Andrei Popescuc9b55562009-07-07 10:51:15 +01003032 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01003033 if (mCustomView != null)
3034 return;
3035
3036 // Add the custom view to its container.
3037 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
3038 mCustomView = view;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003039 mCustomViewCallback = callback;
Andrei Popescuadc008d2009-06-26 14:11:30 +01003040 // Save the menu state and set it to empty while the custom
3041 // view is showing.
3042 mOldMenuState = mMenuState;
3043 mMenuState = EMPTY_MENU;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003044 // Hide the content view.
3045 mContentView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003046 // Finally show the custom view container.
Andrei Popescuc9b55562009-07-07 10:51:15 +01003047 mCustomViewContainer.setVisibility(View.VISIBLE);
3048 mCustomViewContainer.bringToFront();
Andrei Popescuadc008d2009-06-26 14:11:30 +01003049 }
3050
3051 @Override
3052 public void onHideCustomView() {
3053 if (mCustomView == null)
3054 return;
3055
Andrei Popescuc9b55562009-07-07 10:51:15 +01003056 // Hide the custom view.
3057 mCustomView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003058 // Remove the custom view from its container.
3059 mCustomViewContainer.removeView(mCustomView);
3060 mCustomView = null;
3061 // Reset the old menu state.
3062 mMenuState = mOldMenuState;
3063 mOldMenuState = EMPTY_MENU;
3064 mCustomViewContainer.setVisibility(View.GONE);
Andrei Popescuc9b55562009-07-07 10:51:15 +01003065 mCustomViewCallback.onCustomViewHidden();
3066 // Show the content view.
3067 mContentView.setVisibility(View.VISIBLE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003068 }
3069
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003070 /**
Andrei Popescu79e82b72009-07-27 12:01:59 +01003071 * The origin has exceeded its database quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003072 * @param url the URL that exceeded the quota
3073 * @param databaseIdentifier the identifier of the database on
3074 * which the transaction that caused the quota overflow was run
3075 * @param currentQuota the current quota for the origin.
Andrei Popescu79e82b72009-07-27 12:01:59 +01003076 * @param totalUsedQuota is the sum of all origins' quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003077 * @param quotaUpdater The callback to run when a decision to allow or
3078 * deny quota has been made. Don't forget to call this!
3079 */
3080 @Override
3081 public void onExceededDatabaseQuota(String url,
Andrei Popescu79e82b72009-07-27 12:01:59 +01003082 String databaseIdentifier, long currentQuota, long totalUsedQuota,
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003083 WebStorage.QuotaUpdater quotaUpdater) {
Andrei Popescu79e82b72009-07-27 12:01:59 +01003084 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
3085 url, databaseIdentifier, currentQuota, totalUsedQuota,
3086 quotaUpdater);
3087 }
3088
3089 /**
3090 * The Application Cache has exceeded its max size.
3091 * @param spaceNeeded is the amount of disk space that would be needed
3092 * in order for the last appcache operation to succeed.
3093 * @param totalUsedQuota is the sum of all origins' quota.
3094 * @param quotaUpdater A callback to inform the WebCore thread that a new
3095 * app cache size is available. This callback must always be executed at
3096 * some point to ensure that the sleeping WebCore thread is woken up.
3097 */
3098 @Override
3099 public void onReachedMaxAppCacheSize(long spaceNeeded,
3100 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3101 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3102 spaceNeeded, totalUsedQuota, quotaUpdater);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003103 }
Ben Murdoch7db26342009-06-03 18:21:19 +01003104
Steve Block2bc69912009-07-30 14:45:13 +01003105 /**
3106 * Instructs the browser to show a prompt to ask the user to set the
3107 * Geolocation permission state for the specified origin.
3108 * @param origin The origin for which Geolocation permissions are
3109 * requested.
3110 * @param callback The callback to call once the user has set the
3111 * Geolocation permission state.
3112 */
3113 @Override
3114 public void onGeolocationPermissionsShowPrompt(String origin,
3115 GeolocationPermissions.Callback callback) {
3116 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3117 origin, callback);
3118 }
3119
3120 /**
3121 * Instructs the browser to hide the Geolocation permissions prompt.
3122 */
3123 @Override
3124 public void onGeolocationPermissionsHidePrompt() {
3125 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3126 }
3127
Ben Murdoch7db26342009-06-03 18:21:19 +01003128 /* Adds a JavaScript error message to the system log.
3129 * @param message The error message to report.
3130 * @param lineNumber The line number of the error.
3131 * @param sourceID The name of the source file that caused the error.
3132 */
3133 @Override
3134 public void addMessageToConsole(String message, int lineNumber, String sourceID) {
Ben Murdochbff2d602009-07-01 20:19:05 +01003135 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3136 errorConsole.addErrorMessage(message, sourceID, lineNumber);
3137 if (mShouldShowErrorConsole &&
3138 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3139 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3140 }
3141 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
Ben Murdoch7db26342009-06-03 18:21:19 +01003142 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003143 };
3144
3145 /**
3146 * Notify the host application a download should be done, or that
3147 * the data should be streamed if a streaming viewer is available.
3148 * @param url The full url to the content that should be downloaded
3149 * @param contentDisposition Content-disposition http header, if
3150 * present.
3151 * @param mimetype The mimetype of the content reported by the server
3152 * @param contentLength The file size reported by the server
3153 */
3154 public void onDownloadStart(String url, String userAgent,
3155 String contentDisposition, String mimetype, long contentLength) {
3156 // if we're dealing wih A/V content that's not explicitly marked
3157 // for download, check if it's streamable.
3158 if (contentDisposition == null
3159 || !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) {
3160 // query the package manager to see if there's a registered handler
3161 // that matches.
3162 Intent intent = new Intent(Intent.ACTION_VIEW);
3163 intent.setDataAndType(Uri.parse(url), mimetype);
3164 if (getPackageManager().resolveActivity(intent,
3165 PackageManager.MATCH_DEFAULT_ONLY) != null) {
3166 // someone knows how to handle this mime type with this scheme, don't download.
3167 try {
3168 startActivity(intent);
3169 return;
3170 } catch (ActivityNotFoundException ex) {
Dave Bort31a6d1c2009-04-13 15:56:49 -07003171 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003172 Log.d(LOGTAG, "activity not found for " + mimetype
3173 + " over " + Uri.parse(url).getScheme(), ex);
3174 }
3175 // Best behavior is to fall back to a download in this case
3176 }
3177 }
3178 }
3179 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3180 }
3181
3182 /**
3183 * Notify the host application a download should be done, even if there
3184 * is a streaming viewer available for thise type.
3185 * @param url The full url to the content that should be downloaded
3186 * @param contentDisposition Content-disposition http header, if
3187 * present.
3188 * @param mimetype The mimetype of the content reported by the server
3189 * @param contentLength The file size reported by the server
3190 */
3191 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3192 String contentDisposition, String mimetype, long contentLength) {
3193
3194 String filename = URLUtil.guessFileName(url,
3195 contentDisposition, mimetype);
3196
3197 // Check to see if we have an SDCard
3198 String status = Environment.getExternalStorageState();
3199 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3200 int title;
3201 String msg;
3202
3203 // Check to see if the SDCard is busy, same as the music app
3204 if (status.equals(Environment.MEDIA_SHARED)) {
3205 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3206 title = R.string.download_sdcard_busy_dlg_title;
3207 } else {
3208 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3209 title = R.string.download_no_sdcard_dlg_title;
3210 }
3211
3212 new AlertDialog.Builder(this)
3213 .setTitle(title)
3214 .setIcon(android.R.drawable.ic_dialog_alert)
3215 .setMessage(msg)
3216 .setPositiveButton(R.string.ok, null)
3217 .show();
3218 return;
3219 }
3220
3221 // java.net.URI is a lot stricter than KURL so we have to undo
3222 // KURL's percent-encoding and redo the encoding using java.net.URI.
3223 URI uri = null;
3224 try {
3225 // Undo the percent-encoding that KURL may have done.
3226 String newUrl = new String(URLUtil.decode(url.getBytes()));
3227 // Parse the url into pieces
3228 WebAddress w = new WebAddress(newUrl);
3229 String frag = null;
3230 String query = null;
3231 String path = w.mPath;
3232 // Break the path into path, query, and fragment
3233 if (path.length() > 0) {
3234 // Strip the fragment
3235 int idx = path.lastIndexOf('#');
3236 if (idx != -1) {
3237 frag = path.substring(idx + 1);
3238 path = path.substring(0, idx);
3239 }
3240 idx = path.lastIndexOf('?');
3241 if (idx != -1) {
3242 query = path.substring(idx + 1);
3243 path = path.substring(0, idx);
3244 }
3245 }
3246 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3247 query, frag);
3248 } catch (Exception e) {
3249 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3250 return;
3251 }
3252
3253 // XXX: Have to use the old url since the cookies were stored using the
3254 // old percent-encoded url.
3255 String cookies = CookieManager.getInstance().getCookie(url);
3256
3257 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003258 values.put(Downloads.COLUMN_URI, uri.toString());
3259 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3260 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3261 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003262 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003263 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003264 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003265 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3266 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3267 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3268 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003269 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003270 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003271 }
3272 if (mimetype == null) {
3273 // We must have long pressed on a link or image to download it. We
3274 // are not sure of the mimetype in this case, so do a head request
3275 new FetchUrlMimeType(this).execute(values);
3276 } else {
3277 final Uri contentUri =
3278 getContentResolver().insert(Downloads.CONTENT_URI, values);
3279 viewDownloads(contentUri);
3280 }
3281
3282 }
3283
3284 /**
3285 * Resets the lock icon. This method is called when we start a new load and
3286 * know the url to be loaded.
3287 */
3288 private void resetLockIcon(String url) {
3289 // Save the lock-icon state (we revert to it if the load gets cancelled)
3290 saveLockIcon();
3291
3292 mLockIconType = LOCK_ICON_UNSECURE;
3293 if (URLUtil.isHttpsUrl(url)) {
3294 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003295 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003296 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3297 " reset lock icon to " + mLockIconType);
3298 }
3299 }
3300
3301 updateLockIconImage(LOCK_ICON_UNSECURE);
3302 }
3303
3304 /**
3305 * Resets the lock icon. This method is called when the icon needs to be
3306 * reset but we do not know whether we are loading a secure or not secure
3307 * page.
3308 */
3309 private void resetLockIcon() {
3310 // Save the lock-icon state (we revert to it if the load gets cancelled)
3311 saveLockIcon();
3312
3313 mLockIconType = LOCK_ICON_UNSECURE;
3314
Dave Bort31a6d1c2009-04-13 15:56:49 -07003315 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003316 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3317 " reset lock icon to " + mLockIconType);
3318 }
3319
3320 updateLockIconImage(LOCK_ICON_UNSECURE);
3321 }
3322
3323 /**
3324 * Updates the lock-icon image in the title-bar.
3325 */
3326 private void updateLockIconImage(int lockIconType) {
3327 Drawable d = null;
3328 if (lockIconType == LOCK_ICON_SECURE) {
3329 d = mSecLockIcon;
3330 } else if (lockIconType == LOCK_ICON_MIXED) {
3331 d = mMixLockIcon;
3332 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04003333 if (CUSTOM_BROWSER_BAR) {
3334 mTitleBar.setLock(d, getTopWindow());
3335 } else {
3336 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003337 }
3338 }
3339
3340 /**
3341 * Displays a page-info dialog.
3342 * @param tab The tab to show info about
3343 * @param fromShowSSLCertificateOnError The flag that indicates whether
3344 * this dialog was opened from the SSL-certificate-on-error dialog or
3345 * not. This is important, since we need to know whether to return to
3346 * the parent dialog or simply dismiss.
3347 */
3348 private void showPageInfo(final TabControl.Tab tab,
3349 final boolean fromShowSSLCertificateOnError) {
3350 final LayoutInflater factory = LayoutInflater
3351 .from(this);
3352
3353 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3354
3355 final WebView view = tab.getWebView();
3356
3357 String url = null;
3358 String title = null;
3359
3360 if (view == null) {
3361 url = tab.getUrl();
3362 title = tab.getTitle();
3363 } else if (view == mTabControl.getCurrentWebView()) {
3364 // Use the cached title and url if this is the current WebView
3365 url = mUrl;
3366 title = mTitle;
3367 } else {
3368 url = view.getUrl();
3369 title = view.getTitle();
3370 }
3371
3372 if (url == null) {
3373 url = "";
3374 }
3375 if (title == null) {
3376 title = "";
3377 }
3378
3379 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3380 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3381
3382 mPageInfoView = tab;
3383 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3384
3385 AlertDialog.Builder alertDialogBuilder =
3386 new AlertDialog.Builder(this)
3387 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3388 .setView(pageInfoView)
3389 .setPositiveButton(
3390 R.string.ok,
3391 new DialogInterface.OnClickListener() {
3392 public void onClick(DialogInterface dialog,
3393 int whichButton) {
3394 mPageInfoDialog = null;
3395 mPageInfoView = null;
3396 mPageInfoFromShowSSLCertificateOnError = null;
3397
3398 // if we came here from the SSL error dialog
3399 if (fromShowSSLCertificateOnError) {
3400 // go back to the SSL error dialog
3401 showSSLCertificateOnError(
3402 mSSLCertificateOnErrorView,
3403 mSSLCertificateOnErrorHandler,
3404 mSSLCertificateOnErrorError);
3405 }
3406 }
3407 })
3408 .setOnCancelListener(
3409 new DialogInterface.OnCancelListener() {
3410 public void onCancel(DialogInterface dialog) {
3411 mPageInfoDialog = null;
3412 mPageInfoView = null;
3413 mPageInfoFromShowSSLCertificateOnError = null;
3414
3415 // if we came here from the SSL error dialog
3416 if (fromShowSSLCertificateOnError) {
3417 // go back to the SSL error dialog
3418 showSSLCertificateOnError(
3419 mSSLCertificateOnErrorView,
3420 mSSLCertificateOnErrorHandler,
3421 mSSLCertificateOnErrorError);
3422 }
3423 }
3424 });
3425
3426 // if we have a main top-level page SSL certificate set or a certificate
3427 // error
3428 if (fromShowSSLCertificateOnError ||
3429 (view != null && view.getCertificate() != null)) {
3430 // add a 'View Certificate' button
3431 alertDialogBuilder.setNeutralButton(
3432 R.string.view_certificate,
3433 new DialogInterface.OnClickListener() {
3434 public void onClick(DialogInterface dialog,
3435 int whichButton) {
3436 mPageInfoDialog = null;
3437 mPageInfoView = null;
3438 mPageInfoFromShowSSLCertificateOnError = null;
3439
3440 // if we came here from the SSL error dialog
3441 if (fromShowSSLCertificateOnError) {
3442 // go back to the SSL error dialog
3443 showSSLCertificateOnError(
3444 mSSLCertificateOnErrorView,
3445 mSSLCertificateOnErrorHandler,
3446 mSSLCertificateOnErrorError);
3447 } else {
3448 // otherwise, display the top-most certificate from
3449 // the chain
3450 if (view.getCertificate() != null) {
3451 showSSLCertificate(tab);
3452 }
3453 }
3454 }
3455 });
3456 }
3457
3458 mPageInfoDialog = alertDialogBuilder.show();
3459 }
3460
3461 /**
3462 * Displays the main top-level page SSL certificate dialog
3463 * (accessible from the Page-Info dialog).
3464 * @param tab The tab to show certificate for.
3465 */
3466 private void showSSLCertificate(final TabControl.Tab tab) {
3467 final View certificateView =
3468 inflateCertificateView(tab.getWebView().getCertificate());
3469 if (certificateView == null) {
3470 return;
3471 }
3472
3473 LayoutInflater factory = LayoutInflater.from(this);
3474
3475 final LinearLayout placeholder =
3476 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3477
3478 LinearLayout ll = (LinearLayout) factory.inflate(
3479 R.layout.ssl_success, placeholder);
3480 ((TextView)ll.findViewById(R.id.success))
3481 .setText(R.string.ssl_certificate_is_valid);
3482
3483 mSSLCertificateView = tab;
3484 mSSLCertificateDialog =
3485 new AlertDialog.Builder(this)
3486 .setTitle(R.string.ssl_certificate).setIcon(
3487 R.drawable.ic_dialog_browser_certificate_secure)
3488 .setView(certificateView)
3489 .setPositiveButton(R.string.ok,
3490 new DialogInterface.OnClickListener() {
3491 public void onClick(DialogInterface dialog,
3492 int whichButton) {
3493 mSSLCertificateDialog = null;
3494 mSSLCertificateView = null;
3495
3496 showPageInfo(tab, false);
3497 }
3498 })
3499 .setOnCancelListener(
3500 new DialogInterface.OnCancelListener() {
3501 public void onCancel(DialogInterface dialog) {
3502 mSSLCertificateDialog = null;
3503 mSSLCertificateView = null;
3504
3505 showPageInfo(tab, false);
3506 }
3507 })
3508 .show();
3509 }
3510
3511 /**
3512 * Displays the SSL error certificate dialog.
3513 * @param view The target web-view.
3514 * @param handler The SSL error handler responsible for cancelling the
3515 * connection that resulted in an SSL error or proceeding per user request.
3516 * @param error The SSL error object.
3517 */
3518 private void showSSLCertificateOnError(
3519 final WebView view, final SslErrorHandler handler, final SslError error) {
3520
3521 final View certificateView =
3522 inflateCertificateView(error.getCertificate());
3523 if (certificateView == null) {
3524 return;
3525 }
3526
3527 LayoutInflater factory = LayoutInflater.from(this);
3528
3529 final LinearLayout placeholder =
3530 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3531
3532 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3533 LinearLayout ll = (LinearLayout)factory
3534 .inflate(R.layout.ssl_warning, placeholder);
3535 ((TextView)ll.findViewById(R.id.warning))
3536 .setText(R.string.ssl_untrusted);
3537 }
3538
3539 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3540 LinearLayout ll = (LinearLayout)factory
3541 .inflate(R.layout.ssl_warning, placeholder);
3542 ((TextView)ll.findViewById(R.id.warning))
3543 .setText(R.string.ssl_mismatch);
3544 }
3545
3546 if (error.hasError(SslError.SSL_EXPIRED)) {
3547 LinearLayout ll = (LinearLayout)factory
3548 .inflate(R.layout.ssl_warning, placeholder);
3549 ((TextView)ll.findViewById(R.id.warning))
3550 .setText(R.string.ssl_expired);
3551 }
3552
3553 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3554 LinearLayout ll = (LinearLayout)factory
3555 .inflate(R.layout.ssl_warning, placeholder);
3556 ((TextView)ll.findViewById(R.id.warning))
3557 .setText(R.string.ssl_not_yet_valid);
3558 }
3559
3560 mSSLCertificateOnErrorHandler = handler;
3561 mSSLCertificateOnErrorView = view;
3562 mSSLCertificateOnErrorError = error;
3563 mSSLCertificateOnErrorDialog =
3564 new AlertDialog.Builder(this)
3565 .setTitle(R.string.ssl_certificate).setIcon(
3566 R.drawable.ic_dialog_browser_certificate_partially_secure)
3567 .setView(certificateView)
3568 .setPositiveButton(R.string.ok,
3569 new DialogInterface.OnClickListener() {
3570 public void onClick(DialogInterface dialog,
3571 int whichButton) {
3572 mSSLCertificateOnErrorDialog = null;
3573 mSSLCertificateOnErrorView = null;
3574 mSSLCertificateOnErrorHandler = null;
3575 mSSLCertificateOnErrorError = null;
3576
3577 mWebViewClient.onReceivedSslError(
3578 view, handler, error);
3579 }
3580 })
3581 .setNeutralButton(R.string.page_info_view,
3582 new DialogInterface.OnClickListener() {
3583 public void onClick(DialogInterface dialog,
3584 int whichButton) {
3585 mSSLCertificateOnErrorDialog = null;
3586
3587 // do not clear the dialog state: we will
3588 // need to show the dialog again once the
3589 // user is done exploring the page-info details
3590
3591 showPageInfo(mTabControl.getTabFromView(view),
3592 true);
3593 }
3594 })
3595 .setOnCancelListener(
3596 new DialogInterface.OnCancelListener() {
3597 public void onCancel(DialogInterface dialog) {
3598 mSSLCertificateOnErrorDialog = null;
3599 mSSLCertificateOnErrorView = null;
3600 mSSLCertificateOnErrorHandler = null;
3601 mSSLCertificateOnErrorError = null;
3602
3603 mWebViewClient.onReceivedSslError(
3604 view, handler, error);
3605 }
3606 })
3607 .show();
3608 }
3609
3610 /**
3611 * Inflates the SSL certificate view (helper method).
3612 * @param certificate The SSL certificate.
3613 * @return The resultant certificate view with issued-to, issued-by,
3614 * issued-on, expires-on, and possibly other fields set.
3615 * If the input certificate is null, returns null.
3616 */
3617 private View inflateCertificateView(SslCertificate certificate) {
3618 if (certificate == null) {
3619 return null;
3620 }
3621
3622 LayoutInflater factory = LayoutInflater.from(this);
3623
3624 View certificateView = factory.inflate(
3625 R.layout.ssl_certificate, null);
3626
3627 // issued to:
3628 SslCertificate.DName issuedTo = certificate.getIssuedTo();
3629 if (issuedTo != null) {
3630 ((TextView) certificateView.findViewById(R.id.to_common))
3631 .setText(issuedTo.getCName());
3632 ((TextView) certificateView.findViewById(R.id.to_org))
3633 .setText(issuedTo.getOName());
3634 ((TextView) certificateView.findViewById(R.id.to_org_unit))
3635 .setText(issuedTo.getUName());
3636 }
3637
3638 // issued by:
3639 SslCertificate.DName issuedBy = certificate.getIssuedBy();
3640 if (issuedBy != null) {
3641 ((TextView) certificateView.findViewById(R.id.by_common))
3642 .setText(issuedBy.getCName());
3643 ((TextView) certificateView.findViewById(R.id.by_org))
3644 .setText(issuedBy.getOName());
3645 ((TextView) certificateView.findViewById(R.id.by_org_unit))
3646 .setText(issuedBy.getUName());
3647 }
3648
3649 // issued on:
3650 String issuedOn = reformatCertificateDate(
3651 certificate.getValidNotBefore());
3652 ((TextView) certificateView.findViewById(R.id.issued_on))
3653 .setText(issuedOn);
3654
3655 // expires on:
3656 String expiresOn = reformatCertificateDate(
3657 certificate.getValidNotAfter());
3658 ((TextView) certificateView.findViewById(R.id.expires_on))
3659 .setText(expiresOn);
3660
3661 return certificateView;
3662 }
3663
3664 /**
3665 * Re-formats the certificate date (Date.toString()) string to
3666 * a properly localized date string.
3667 * @return Properly localized version of the certificate date string and
3668 * the original certificate date string if fails to localize.
3669 * If the original string is null, returns an empty string "".
3670 */
3671 private String reformatCertificateDate(String certificateDate) {
3672 String reformattedDate = null;
3673
3674 if (certificateDate != null) {
3675 Date date = null;
3676 try {
3677 date = java.text.DateFormat.getInstance().parse(certificateDate);
3678 } catch (ParseException e) {
3679 date = null;
3680 }
3681
3682 if (date != null) {
3683 reformattedDate =
3684 DateFormat.getDateFormat(this).format(date);
3685 }
3686 }
3687
3688 return reformattedDate != null ? reformattedDate :
3689 (certificateDate != null ? certificateDate : "");
3690 }
3691
3692 /**
3693 * Displays an http-authentication dialog.
3694 */
3695 private void showHttpAuthentication(final HttpAuthHandler handler,
3696 final String host, final String realm, final String title,
3697 final String name, final String password, int focusId) {
3698 LayoutInflater factory = LayoutInflater.from(this);
3699 final View v = factory
3700 .inflate(R.layout.http_authentication, null);
3701 if (name != null) {
3702 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3703 }
3704 if (password != null) {
3705 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3706 }
3707
3708 String titleText = title;
3709 if (titleText == null) {
3710 titleText = getText(R.string.sign_in_to).toString().replace(
3711 "%s1", host).replace("%s2", realm);
3712 }
3713
3714 mHttpAuthHandler = handler;
3715 AlertDialog dialog = new AlertDialog.Builder(this)
3716 .setTitle(titleText)
3717 .setIcon(android.R.drawable.ic_dialog_alert)
3718 .setView(v)
3719 .setPositiveButton(R.string.action,
3720 new DialogInterface.OnClickListener() {
3721 public void onClick(DialogInterface dialog,
3722 int whichButton) {
3723 String nm = ((EditText) v
3724 .findViewById(R.id.username_edit))
3725 .getText().toString();
3726 String pw = ((EditText) v
3727 .findViewById(R.id.password_edit))
3728 .getText().toString();
3729 BrowserActivity.this.setHttpAuthUsernamePassword
3730 (host, realm, nm, pw);
3731 handler.proceed(nm, pw);
3732 mHttpAuthenticationDialog = null;
3733 mHttpAuthHandler = null;
3734 }})
3735 .setNegativeButton(R.string.cancel,
3736 new DialogInterface.OnClickListener() {
3737 public void onClick(DialogInterface dialog,
3738 int whichButton) {
3739 handler.cancel();
3740 BrowserActivity.this.resetTitleAndRevertLockIcon();
3741 mHttpAuthenticationDialog = null;
3742 mHttpAuthHandler = null;
3743 }})
3744 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3745 public void onCancel(DialogInterface dialog) {
3746 handler.cancel();
3747 BrowserActivity.this.resetTitleAndRevertLockIcon();
3748 mHttpAuthenticationDialog = null;
3749 mHttpAuthHandler = null;
3750 }})
3751 .create();
3752 // Make the IME appear when the dialog is displayed if applicable.
3753 dialog.getWindow().setSoftInputMode(
3754 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3755 dialog.show();
3756 if (focusId != 0) {
3757 dialog.findViewById(focusId).requestFocus();
3758 } else {
3759 v.findViewById(R.id.username_edit).requestFocus();
3760 }
3761 mHttpAuthenticationDialog = dialog;
3762 }
3763
3764 public int getProgress() {
3765 WebView w = mTabControl.getCurrentWebView();
3766 if (w != null) {
3767 return w.getProgress();
3768 } else {
3769 return 100;
3770 }
3771 }
3772
3773 /**
3774 * Set HTTP authentication password.
3775 *
3776 * @param host The host for the password
3777 * @param realm The realm for the password
3778 * @param username The username for the password. If it is null, it means
3779 * password can't be saved.
3780 * @param password The password
3781 */
3782 public void setHttpAuthUsernamePassword(String host, String realm,
3783 String username,
3784 String password) {
3785 WebView w = mTabControl.getCurrentWebView();
3786 if (w != null) {
3787 w.setHttpAuthUsernamePassword(host, realm, username, password);
3788 }
3789 }
3790
3791 /**
3792 * connectivity manager says net has come or gone... inform the user
3793 * @param up true if net has come up, false if net has gone down
3794 */
3795 public void onNetworkToggle(boolean up) {
3796 if (up == mIsNetworkUp) {
3797 return;
3798 } else if (up) {
3799 mIsNetworkUp = true;
3800 if (mAlertDialog != null) {
3801 mAlertDialog.cancel();
3802 mAlertDialog = null;
3803 }
3804 } else {
3805 mIsNetworkUp = false;
3806 if (mInLoad && mAlertDialog == null) {
3807 mAlertDialog = new AlertDialog.Builder(this)
3808 .setTitle(R.string.loadSuspendedTitle)
3809 .setMessage(R.string.loadSuspended)
3810 .setPositiveButton(R.string.ok, null)
3811 .show();
3812 }
3813 }
3814 WebView w = mTabControl.getCurrentWebView();
3815 if (w != null) {
3816 w.setNetworkAvailable(up);
3817 }
3818 }
3819
3820 @Override
3821 protected void onActivityResult(int requestCode, int resultCode,
3822 Intent intent) {
3823 switch (requestCode) {
3824 case COMBO_PAGE:
3825 if (resultCode == RESULT_OK && intent != null) {
3826 String data = intent.getAction();
3827 Bundle extras = intent.getExtras();
3828 if (extras != null && extras.getBoolean("new_window", false)) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003829 final TabControl.Tab newTab = openTab(data);
3830 if (mSettings.openInBackground() &&
Leon Scroggins1f005d32009-08-10 17:36:42 -04003831 newTab != null) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003832 mTabControl.populatePickerData(newTab);
3833 mTabControl.setCurrentTab(newTab);
Leon Scroggins1f005d32009-08-10 17:36:42 -04003834 int newIndex = mTabControl.getCurrentIndex();
3835 if (CUSTOM_BROWSER_BAR) {
3836 mTitleBar.setCurrentTab(newIndex);
3837 }
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003838 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003839 } else {
3840 final TabControl.Tab currentTab =
3841 mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04003842 dismissSubWindow(currentTab);
3843 if (data != null && data.length() != 0) {
3844 getTopWindow().loadUrl(data);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003845 }
3846 }
Leon Scroggins39362092009-08-18 19:47:09 -04003847/*
3848 FIXME: Removing this breaks the behavior of pressing BACK from
3849 the Go page resulting in the window being closed. However, it
3850 needs to be removed so that the user can use the Search bar to
3851 enter a URL. Further, the Go behavior is going to change
3852 drastically, so this behavior may not last anyway.
Leon Scroggins160a7e72009-08-14 18:28:01 -04003853 } else if (resultCode == RESULT_CANCELED
3854 && mCancelGoPageMeansClose) {
3855 if (mTabControl.getTabCount() == 1) {
3856 // finish the Browser. When the Browser opens up again,
3857 // we will go through onCreate and once again open up
3858 // the Go page.
3859 finish();
3860 return;
3861 }
3862 closeCurrentWindow();
Leon Scroggins39362092009-08-18 19:47:09 -04003863*/
The Android Open Source Project0c908882009-03-03 19:32:16 -08003864 }
3865 break;
3866 default:
3867 break;
3868 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003869 mCancelGoPageMeansClose = false;
3870 if (getTopWindow() != null) {
3871 getTopWindow().requestFocus();
3872 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003873 }
3874
3875 /*
3876 * This method is called as a result of the user selecting the options
3877 * menu to see the download window, or when a download changes state. It
3878 * shows the download window ontop of the current window.
3879 */
3880 /* package */ void viewDownloads(Uri downloadRecord) {
3881 Intent intent = new Intent(this,
3882 BrowserDownloadPage.class);
3883 intent.setData(downloadRecord);
3884 startActivityForResult(intent, this.DOWNLOAD_PAGE);
3885
3886 }
3887
Leon Scroggins160a7e72009-08-14 18:28:01 -04003888 // True if canceling the "Go" screen should result in closing the current
3889 // window/browser.
3890 private boolean mCancelGoPageMeansClose;
3891
3892 /**
3893 * Open the Go page.
3894 * @param startWithHistory If true, open starting on the history tab.
3895 * Otherwise, start with the bookmarks tab.
3896 * @param cancelGoPageMeansClose Set to true if this came from a new tab, or
3897 * from the only tab, and canceling means to
3898 * close the tab (and possibly the browser)
3899 */
3900 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory,
3901 boolean cancelGoPageMeansClose) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003902 WebView current = mTabControl.getCurrentWebView();
3903 if (current == null) {
3904 return;
3905 }
3906 Intent intent = new Intent(this,
3907 CombinedBookmarkHistoryActivity.class);
3908 String title = current.getTitle();
3909 String url = current.getUrl();
3910 // Just in case the user opens bookmarks before a page finishes loading
3911 // so the current history item, and therefore the page, is null.
3912 if (null == url) {
3913 url = mLastEnteredUrl;
3914 // This can happen.
3915 if (null == url) {
3916 url = mSettings.getHomePage();
3917 }
3918 }
3919 // In case the web page has not yet received its associated title.
3920 if (title == null) {
3921 title = url;
3922 }
3923 intent.putExtra("title", title);
3924 intent.putExtra("url", url);
Leon Scroggins190095d2009-08-17 17:01:38 -04003925 // If this is opening in a new window, then disable opening in a
3926 // (different) new window. Also disable it if we have maxed out the
3927 // windows.
3928 intent.putExtra("disable_new_window", cancelGoPageMeansClose
3929 || mTabControl.getTabCount() >= TabControl.MAX_TABS);
Patrick Scott3918d442009-08-04 13:22:29 -04003930 intent.putExtra("touch_icon_url", current.getTouchIconUrl());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003931 if (startWithHistory) {
3932 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
3933 CombinedBookmarkHistoryActivity.HISTORY_TAB);
3934 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003935 mCancelGoPageMeansClose = cancelGoPageMeansClose;
The Android Open Source Project0c908882009-03-03 19:32:16 -08003936 startActivityForResult(intent, COMBO_PAGE);
3937 }
3938
3939 // Called when loading from context menu or LOAD_URL message
3940 private void loadURL(WebView view, String url) {
3941 // In case the user enters nothing.
3942 if (url != null && url.length() != 0 && view != null) {
3943 url = smartUrlFilter(url);
3944 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
3945 view.loadUrl(url);
3946 }
3947 }
3948 }
3949
The Android Open Source Project0c908882009-03-03 19:32:16 -08003950 private String smartUrlFilter(Uri inUri) {
3951 if (inUri != null) {
3952 return smartUrlFilter(inUri.toString());
3953 }
3954 return null;
3955 }
3956
3957
3958 // get window count
3959
3960 int getWindowCount(){
3961 if(mTabControl != null){
3962 return mTabControl.getTabCount();
3963 }
3964 return 0;
3965 }
3966
Feng Qianb34f87a2009-03-24 21:27:26 -07003967 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08003968 "(?i)" + // switch on case insensitive matching
3969 "(" + // begin group for schema
3970 "(?:http|https|file):\\/\\/" +
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07003971 "|(?:inline|data|about|content|javascript):" +
The Android Open Source Project0c908882009-03-03 19:32:16 -08003972 ")" +
3973 "(.*)" );
3974
3975 /**
3976 * Attempts to determine whether user input is a URL or search
3977 * terms. Anything with a space is passed to search.
3978 *
3979 * Converts to lowercase any mistakenly uppercased schema (i.e.,
3980 * "Http://" converts to "http://"
3981 *
3982 * @return Original or modified URL
3983 *
3984 */
3985 String smartUrlFilter(String url) {
3986
3987 String inUrl = url.trim();
3988 boolean hasSpace = inUrl.indexOf(' ') != -1;
3989
3990 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
3991 if (matcher.matches()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003992 // force scheme to lowercase
3993 String scheme = matcher.group(1);
3994 String lcScheme = scheme.toLowerCase();
3995 if (!lcScheme.equals(scheme)) {
Mitsuru Oshima123ecfb2009-05-18 19:11:14 -07003996 inUrl = lcScheme + matcher.group(2);
3997 }
3998 if (hasSpace) {
3999 inUrl = inUrl.replace(" ", "%20");
The Android Open Source Project0c908882009-03-03 19:32:16 -08004000 }
4001 return inUrl;
4002 }
4003 if (hasSpace) {
Satish Sampath565505b2009-05-29 15:37:27 +01004004 // FIXME: Is this the correct place to add to searches?
4005 // what if someone else calls this function?
4006 int shortcut = parseUrlShortcut(inUrl);
4007 if (shortcut != SHORTCUT_INVALID) {
4008 Browser.addSearchUrl(mResolver, inUrl);
4009 String query = inUrl.substring(2);
4010 switch (shortcut) {
4011 case SHORTCUT_GOOGLE_SEARCH:
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004012 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
Satish Sampath565505b2009-05-29 15:37:27 +01004013 case SHORTCUT_WIKIPEDIA_SEARCH:
4014 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4015 case SHORTCUT_DICTIONARY_SEARCH:
4016 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4017 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
The Android Open Source Project0c908882009-03-03 19:32:16 -08004018 // FIXME: we need location in this case
Satish Sampath565505b2009-05-29 15:37:27 +01004019 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004020 }
4021 }
4022 } else {
4023 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4024 return URLUtil.guessUrl(inUrl);
4025 }
4026 }
4027
4028 Browser.addSearchUrl(mResolver, inUrl);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004029 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004030 }
4031
Ben Murdochbff2d602009-07-01 20:19:05 +01004032 /* package */ void setShouldShowErrorConsole(boolean flag) {
4033 if (flag == mShouldShowErrorConsole) {
4034 // Nothing to do.
4035 return;
4036 }
4037
4038 mShouldShowErrorConsole = flag;
4039
4040 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4041
4042 if (flag) {
4043 // Setting the show state of the console will cause it's the layout to be inflated.
4044 if (errorConsole.numberOfErrors() > 0) {
4045 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4046 } else {
4047 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4048 }
4049
4050 // Now we can add it to the main view.
4051 mErrorConsoleContainer.addView(errorConsole,
4052 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4053 ViewGroup.LayoutParams.WRAP_CONTENT));
4054 } else {
4055 mErrorConsoleContainer.removeView(errorConsole);
4056 }
4057
4058 }
4059
The Android Open Source Project0c908882009-03-03 19:32:16 -08004060 private final static int LOCK_ICON_UNSECURE = 0;
4061 private final static int LOCK_ICON_SECURE = 1;
4062 private final static int LOCK_ICON_MIXED = 2;
4063
4064 private int mLockIconType = LOCK_ICON_UNSECURE;
4065 private int mPrevLockType = LOCK_ICON_UNSECURE;
4066
4067 private BrowserSettings mSettings;
4068 private TabControl mTabControl;
4069 private ContentResolver mResolver;
4070 private FrameLayout mContentView;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004071 private View mCustomView;
4072 private FrameLayout mCustomViewContainer;
Andrei Popescuc9b55562009-07-07 10:51:15 +01004073 private WebChromeClient.CustomViewCallback mCustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004074
4075 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4076 // view, we should rewrite this.
4077 private int mCurrentMenuState = 0;
4078 private int mMenuState = R.id.MAIN_MENU;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004079 private int mOldMenuState = EMPTY_MENU;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004080 private static final int EMPTY_MENU = -1;
4081 private Menu mMenu;
4082
4083 private FindDialog mFindDialog;
4084 // Used to prevent chording to result in firing two shortcuts immediately
4085 // one after another. Fixes bug 1211714.
4086 boolean mCanChord;
4087
4088 private boolean mInLoad;
4089 private boolean mIsNetworkUp;
4090
4091 private boolean mPageStarted;
4092 private boolean mActivityInPause = true;
4093
4094 private boolean mMenuIsDown;
4095
4096 private final KeyTracker mKeyTracker = new KeyTracker(this);
4097
4098 // As trackball doesn't send repeat down, we have to track it ourselves
4099 private boolean mTrackTrackball;
4100
4101 private static boolean mInTrace;
4102
4103 // Performance probe
4104 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4105 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4106 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4107 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4108 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4109 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4110 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4111 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4112 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4113 };
4114
4115 private long mStart;
4116 private long mProcessStart;
4117 private long mUserStart;
4118 private long mSystemStart;
4119 private long mIdleStart;
4120 private long mIrqStart;
4121
4122 private long mUiStart;
4123
4124 private Drawable mMixLockIcon;
4125 private Drawable mSecLockIcon;
4126 private Drawable mGenericFavicon;
4127
4128 /* hold a ref so we can auto-cancel if necessary */
4129 private AlertDialog mAlertDialog;
4130
4131 // Wait for credentials before loading google.com
4132 private ProgressDialog mCredsDlg;
4133
4134 // The up-to-date URL and title (these can be different from those stored
4135 // in WebView, since it takes some time for the information in WebView to
4136 // get updated)
4137 private String mUrl;
4138 private String mTitle;
4139
4140 // As PageInfo has different style for landscape / portrait, we have
4141 // to re-open it when configuration changed
4142 private AlertDialog mPageInfoDialog;
4143 private TabControl.Tab mPageInfoView;
4144 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4145 // dialog, we should not just dismiss it, but should get back to the
4146 // SSL-certificate-on-error dialog. This flag is used to store this state
4147 private Boolean mPageInfoFromShowSSLCertificateOnError;
4148
4149 // as SSLCertificateOnError has different style for landscape / portrait,
4150 // we have to re-open it when configuration changed
4151 private AlertDialog mSSLCertificateOnErrorDialog;
4152 private WebView mSSLCertificateOnErrorView;
4153 private SslErrorHandler mSSLCertificateOnErrorHandler;
4154 private SslError mSSLCertificateOnErrorError;
4155
4156 // as SSLCertificate has different style for landscape / portrait, we
4157 // have to re-open it when configuration changed
4158 private AlertDialog mSSLCertificateDialog;
4159 private TabControl.Tab mSSLCertificateView;
4160
4161 // as HttpAuthentication has different style for landscape / portrait, we
4162 // have to re-open it when configuration changed
4163 private AlertDialog mHttpAuthenticationDialog;
4164 private HttpAuthHandler mHttpAuthHandler;
4165
4166 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4167 new FrameLayout.LayoutParams(
4168 ViewGroup.LayoutParams.FILL_PARENT,
4169 ViewGroup.LayoutParams.FILL_PARENT);
Andrei Popescuadc008d2009-06-26 14:11:30 +01004170 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4171 new FrameLayout.LayoutParams(
4172 ViewGroup.LayoutParams.FILL_PARENT,
4173 ViewGroup.LayoutParams.FILL_PARENT,
4174 Gravity.CENTER);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004175 // Google search
4176 final static String QuickSearch_G = "http://www.google.com/m?q=%s";
The Android Open Source Project0c908882009-03-03 19:32:16 -08004177 // Wikipedia search
4178 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4179 // Dictionary search
4180 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4181 // Google Mobile Local search
4182 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4183
4184 final static String QUERY_PLACE_HOLDER = "%s";
4185
4186 // "source" parameter for Google search through search key
4187 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4188 // "source" parameter for Google search through goto menu
4189 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4190 // "source" parameter for Google search through simplily type
4191 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4192 // "source" parameter for Google search suggested by the browser
4193 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4194 // "source" parameter for Google search from unknown source
4195 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4196
4197 private final static String LOGTAG = "browser";
4198
The Android Open Source Project0c908882009-03-03 19:32:16 -08004199 private String mLastEnteredUrl;
4200
4201 private PowerManager.WakeLock mWakeLock;
4202 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4203
4204 private Toast mStopToast;
4205
Leon Scroggins1f005d32009-08-10 17:36:42 -04004206 private TitleBarSet mTitleBar;
Leon Scroggins81db3662009-06-04 17:45:11 -04004207
Ben Murdochbff2d602009-07-01 20:19:05 +01004208 private LinearLayout mErrorConsoleContainer = null;
4209 private boolean mShouldShowErrorConsole = false;
4210
The Android Open Source Project0c908882009-03-03 19:32:16 -08004211 // As the ids are dynamically created, we can't guarantee that they will
4212 // be in sequence, so this static array maps ids to a window number.
4213 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4214 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4215 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4216 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4217
4218 // monitor platform changes
4219 private IntentFilter mNetworkStateChangedFilter;
4220 private BroadcastReceiver mNetworkStateIntentReceiver;
4221
Grace Klobab4da0ad2009-05-14 14:45:40 -07004222 private BroadcastReceiver mPackageInstallationReceiver;
4223
The Android Open Source Project0c908882009-03-03 19:32:16 -08004224 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004225 final static int COMBO_PAGE = 1;
4226 final static int DOWNLOAD_PAGE = 2;
4227 final static int PREFERENCES_PAGE = 3;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004228
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004229 /**
4230 * A UrlData class to abstract how the content will be set to WebView.
4231 * This base class uses loadUrl to show the content.
4232 */
4233 private static class UrlData {
4234 String mUrl;
Grace Kloba60e095c2009-06-16 11:50:55 -07004235 byte[] mPostData;
4236
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004237 UrlData(String url) {
4238 this.mUrl = url;
4239 }
Grace Kloba60e095c2009-06-16 11:50:55 -07004240
4241 void setPostData(byte[] postData) {
4242 mPostData = postData;
4243 }
4244
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004245 boolean isEmpty() {
4246 return mUrl == null || mUrl.length() == 0;
4247 }
4248
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004249 public void loadIn(WebView webView) {
Grace Kloba60e095c2009-06-16 11:50:55 -07004250 if (mPostData != null) {
4251 webView.postUrl(mUrl, mPostData);
4252 } else {
4253 webView.loadUrl(mUrl);
4254 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004255 }
4256 };
4257
4258 /**
4259 * A subclass of UrlData class that can display inlined content using
4260 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4261 */
4262 private static class InlinedUrlData extends UrlData {
4263 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4264 super(failUrl);
4265 mInlined = inlined;
4266 mMimeType = mimeType;
4267 mEncoding = encoding;
4268 }
4269 String mMimeType;
4270 String mInlined;
4271 String mEncoding;
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004272 @Override
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004273 boolean isEmpty() {
Ben Murdochbff2d602009-07-01 20:19:05 +01004274 return mInlined == null || mInlined.length() == 0 || super.isEmpty();
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004275 }
4276
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004277 @Override
4278 public void loadIn(WebView webView) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004279 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4280 }
4281 }
4282
Leon Scroggins1f005d32009-08-10 17:36:42 -04004283 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004284}