blob: 244a9039da36aa782b7e48581467949e1f3700d9 [file] [log] [blame]
The Android Open Source Project0c908882009-03-03 19:32:16 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import com.google.android.googleapps.IGoogleLoginService;
20import com.google.android.googlelogin.GoogleLoginServiceConstants;
21
22import android.app.Activity;
The Android Open Source Project0c908882009-03-03 19:32:16 -080023import android.app.AlertDialog;
24import android.app.ProgressDialog;
25import android.app.SearchManager;
26import android.content.ActivityNotFoundException;
27import android.content.BroadcastReceiver;
28import android.content.ComponentName;
29import android.content.ContentResolver;
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -040030import android.content.ContentUris;
The Android Open Source Project0c908882009-03-03 19:32:16 -080031import android.content.ContentValues;
32import android.content.Context;
33import android.content.DialogInterface;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.ServiceConnection;
37import android.content.DialogInterface.OnCancelListener;
Grace Klobab4da0ad2009-05-14 14:45:40 -070038import android.content.pm.PackageInfo;
The Android Open Source Project0c908882009-03-03 19:32:16 -080039import android.content.pm.PackageManager;
40import android.content.pm.ResolveInfo;
41import android.content.res.AssetManager;
42import android.content.res.Configuration;
43import android.content.res.Resources;
44import android.database.Cursor;
45import android.database.sqlite.SQLiteDatabase;
46import android.database.sqlite.SQLiteException;
47import android.graphics.Bitmap;
48import android.graphics.Canvas;
49import android.graphics.Color;
50import android.graphics.DrawFilter;
51import android.graphics.Paint;
52import android.graphics.PaintFlagsDrawFilter;
53import android.graphics.Picture;
54import android.graphics.drawable.BitmapDrawable;
55import android.graphics.drawable.Drawable;
56import android.graphics.drawable.LayerDrawable;
57import android.graphics.drawable.PaintDrawable;
58import android.hardware.SensorListener;
59import android.hardware.SensorManager;
60import android.net.ConnectivityManager;
61import android.net.Uri;
62import android.net.WebAddress;
63import android.net.http.EventHandler;
64import android.net.http.SslCertificate;
65import android.net.http.SslError;
66import android.os.AsyncTask;
67import android.os.Bundle;
68import android.os.Debug;
69import android.os.Environment;
70import android.os.Handler;
71import android.os.IBinder;
72import android.os.Message;
73import android.os.PowerManager;
74import android.os.Process;
75import android.os.RemoteException;
76import android.os.ServiceManager;
77import android.os.SystemClock;
The Android Open Source Project0c908882009-03-03 19:32:16 -080078import android.provider.Browser;
79import android.provider.Contacts;
80import android.provider.Downloads;
81import android.provider.MediaStore;
82import android.provider.Contacts.Intents.Insert;
83import android.text.IClipboard;
84import android.text.TextUtils;
85import android.text.format.DateFormat;
86import android.text.util.Regex;
The Android Open Source Project0c908882009-03-03 19:32:16 -080087import android.util.Log;
88import android.view.ContextMenu;
89import android.view.Gravity;
90import android.view.KeyEvent;
91import android.view.LayoutInflater;
92import android.view.Menu;
93import android.view.MenuInflater;
94import android.view.MenuItem;
95import android.view.View;
96import android.view.ViewGroup;
97import android.view.Window;
98import android.view.WindowManager;
99import android.view.ContextMenu.ContextMenuInfo;
100import android.view.MenuItem.OnMenuItemClickListener;
101import android.view.animation.AlphaAnimation;
102import android.view.animation.Animation;
103import android.view.animation.AnimationSet;
104import android.view.animation.DecelerateInterpolator;
105import android.view.animation.ScaleAnimation;
106import android.view.animation.TranslateAnimation;
107import android.webkit.CookieManager;
108import android.webkit.CookieSyncManager;
109import android.webkit.DownloadListener;
Steve Block2bc69912009-07-30 14:45:13 +0100110import android.webkit.GeolocationPermissions;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800111import android.webkit.HttpAuthHandler;
Grace Klobab4da0ad2009-05-14 14:45:40 -0700112import android.webkit.PluginManager;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800113import android.webkit.SslErrorHandler;
114import android.webkit.URLUtil;
115import android.webkit.WebChromeClient;
Andrei Popescuc9b55562009-07-07 10:51:15 +0100116import android.webkit.WebChromeClient.CustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800117import android.webkit.WebHistoryItem;
118import android.webkit.WebIconDatabase;
Ben Murdoch092dd5d2009-04-22 12:34:12 +0100119import android.webkit.WebStorage;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800120import android.webkit.WebView;
121import android.webkit.WebViewClient;
122import android.widget.EditText;
123import android.widget.FrameLayout;
124import android.widget.LinearLayout;
125import android.widget.TextView;
126import android.widget.Toast;
127
128import java.io.BufferedOutputStream;
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -0400129import java.io.ByteArrayOutputStream;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800130import java.io.File;
131import java.io.FileInputStream;
132import java.io.FileOutputStream;
133import java.io.IOException;
134import java.io.InputStream;
135import java.net.MalformedURLException;
136import java.net.URI;
Dianne Hackborn99189432009-06-17 18:06:18 -0700137import java.net.URISyntaxException;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800138import java.net.URL;
139import java.net.URLEncoder;
140import java.text.ParseException;
141import java.util.Date;
142import java.util.Enumeration;
143import java.util.HashMap;
Patrick Scott37911c72009-03-24 18:02:58 -0700144import java.util.LinkedList;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800145import java.util.Vector;
146import java.util.regex.Matcher;
147import java.util.regex.Pattern;
148import java.util.zip.ZipEntry;
149import java.util.zip.ZipFile;
150
151public class BrowserActivity extends Activity
152 implements KeyTracker.OnKeyTracker,
153 View.OnCreateContextMenuListener,
154 DownloadListener {
155
Dave Bort31a6d1c2009-04-13 15:56:49 -0700156 /* Define some aliases to make these debugging flags easier to refer to.
157 * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
158 */
159 private final static boolean DEBUG = com.android.browser.Browser.DEBUG;
160 private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
161 private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
162
The Android Open Source Project0c908882009-03-03 19:32:16 -0800163 private IGoogleLoginService mGls = null;
164 private ServiceConnection mGlsConnection = null;
165
166 private SensorManager mSensorManager = null;
167
Satish Sampath565505b2009-05-29 15:37:27 +0100168 // These are single-character shortcuts for searching popular sources.
169 private static final int SHORTCUT_INVALID = 0;
170 private static final int SHORTCUT_GOOGLE_SEARCH = 1;
171 private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2;
172 private static final int SHORTCUT_DICTIONARY_SEARCH = 3;
173 private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4;
174
The Android Open Source Project0c908882009-03-03 19:32:16 -0800175 /* Whitelisted webpages
176 private static HashSet<String> sWhiteList;
177
178 static {
179 sWhiteList = new HashSet<String>();
180 sWhiteList.add("cnn.com/");
181 sWhiteList.add("espn.go.com/");
182 sWhiteList.add("nytimes.com/");
183 sWhiteList.add("engadget.com/");
184 sWhiteList.add("yahoo.com/");
185 sWhiteList.add("msn.com/");
186 sWhiteList.add("amazon.com/");
187 sWhiteList.add("consumerist.com/");
188 sWhiteList.add("google.com/m/news");
189 }
190 */
191
192 private void setupHomePage() {
193 final Runnable getAccount = new Runnable() {
194 public void run() {
195 // Lower priority
196 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
197 // get the default home page
198 String homepage = mSettings.getHomePage();
199
200 try {
201 if (mGls == null) return;
202
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700203 if (!homepage.startsWith("http://www.google.")) return;
204 if (homepage.indexOf('?') == -1) return;
205
The Android Open Source Project0c908882009-03-03 19:32:16 -0800206 String hostedUser = mGls.getAccount(GoogleLoginServiceConstants.PREFER_HOSTED);
207 String googleUser = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE);
208
209 // three cases:
210 //
211 // hostedUser == googleUser
212 // The device has only a google account
213 //
214 // hostedUser != googleUser
215 // The device has a hosted account and a google account
216 //
217 // hostedUser != null, googleUser == null
218 // The device has only a hosted account (so far)
219
220 // developers might have no accounts at all
221 if (hostedUser == null) return;
222
223 if (googleUser == null || !hostedUser.equals(googleUser)) {
224 String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1);
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700225 homepage = homepage.replace("?", "/a/" + domain + "?");
The Android Open Source Project0c908882009-03-03 19:32:16 -0800226 }
227 } catch (RemoteException ignore) {
228 // Login service died; carry on
229 } catch (RuntimeException ignore) {
230 // Login service died; carry on
231 } finally {
232 finish(homepage);
233 }
234 }
235
236 private void finish(final String homepage) {
237 mHandler.post(new Runnable() {
238 public void run() {
239 mSettings.setHomePage(BrowserActivity.this, homepage);
240 resumeAfterCredentials();
241
242 // as this is running in a separate thread,
243 // BrowserActivity's onDestroy() may have been called,
244 // which also calls unbindService().
245 if (mGlsConnection != null) {
246 // we no longer need to keep GLS open
247 unbindService(mGlsConnection);
248 mGlsConnection = null;
249 }
250 } });
251 } };
252
253 final boolean[] done = { false };
254
255 // Open a connection to the Google Login Service. The first
256 // time the connection is established, set up the homepage depending on
257 // the account in a background thread.
258 mGlsConnection = new ServiceConnection() {
259 public void onServiceConnected(ComponentName className, IBinder service) {
260 mGls = IGoogleLoginService.Stub.asInterface(service);
261 if (done[0] == false) {
262 done[0] = true;
263 Thread account = new Thread(getAccount);
264 account.setName("GLSAccount");
265 account.start();
266 }
267 }
268 public void onServiceDisconnected(ComponentName className) {
269 mGls = null;
270 }
271 };
272
273 bindService(GoogleLoginServiceConstants.SERVICE_INTENT,
274 mGlsConnection, Context.BIND_AUTO_CREATE);
275 }
276
Cary Clarka9771242009-08-11 16:42:26 -0400277 private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800278 @Override
279 public Void doInBackground(File... files) {
280 if (files != null) {
281 for (File f : files) {
282 f.delete();
283 }
284 }
285 return null;
286 }
287 }
288
Leon Scroggins81db3662009-06-04 17:45:11 -0400289 // Flag to enable the touchable browser bar with buttons
290 private final boolean CUSTOM_BROWSER_BAR = true;
291
The Android Open Source Project0c908882009-03-03 19:32:16 -0800292 @Override public void onCreate(Bundle icicle) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700293 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800294 Log.v(LOGTAG, this + " onStart");
295 }
296 super.onCreate(icicle);
Leon Scroggins81db3662009-06-04 17:45:11 -0400297 if (CUSTOM_BROWSER_BAR) {
298 this.requestWindowFeature(Window.FEATURE_NO_TITLE);
Leon Scroggins81db3662009-06-04 17:45:11 -0400299 } else {
300 this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
301 this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
302 this.requestWindowFeature(Window.FEATURE_PROGRESS);
303 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
304 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800305 // test the browser in OpenGL
306 // requestWindowFeature(Window.FEATURE_OPENGL);
307
308 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
309
310 mResolver = getContentResolver();
311
The Android Open Source Project0c908882009-03-03 19:32:16 -0800312 //
313 // start MASF proxy service
314 //
315 //Intent proxyServiceIntent = new Intent();
316 //proxyServiceIntent.setComponent
317 // (new ComponentName(
318 // "com.android.masfproxyservice",
319 // "com.android.masfproxyservice.MasfProxyService"));
320 //startService(proxyServiceIntent, null);
321
322 mSecLockIcon = Resources.getSystem().getDrawable(
323 android.R.drawable.ic_secure);
324 mMixLockIcon = Resources.getSystem().getDrawable(
325 android.R.drawable.ic_partial_secure);
326 mGenericFavicon = getResources().getDrawable(
327 R.drawable.app_web_browser_sm);
328
Leon Scroggins81db3662009-06-04 17:45:11 -0400329 FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
330 .findViewById(com.android.internal.R.id.content);
331 if (CUSTOM_BROWSER_BAR) {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100332 // This FrameLayout will hold the custom FrameLayout and a LinearLayout
333 // that contains the title bar and a FrameLayout, which
Leon Scroggins81db3662009-06-04 17:45:11 -0400334 // holds everything else.
Andrei Popescuadc008d2009-06-26 14:11:30 +0100335 FrameLayout browserFrameLayout = (FrameLayout) LayoutInflater.from(this)
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400336 .inflate(R.layout.custom_screen, null);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100337 mTitleBar = (TitleBar) browserFrameLayout.findViewById(R.id.title_bar);
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400338 mTitleBar.setBrowserActivity(this);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100339 mContentView = (FrameLayout) browserFrameLayout.findViewById(
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400340 R.id.main_content);
Ben Murdochbff2d602009-07-01 20:19:05 +0100341 mErrorConsoleContainer = (LinearLayout) browserFrameLayout.findViewById(
342 R.id.error_console);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100343 mCustomViewContainer = (FrameLayout) browserFrameLayout
344 .findViewById(R.id.fullscreen_custom_content);
345 frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400346 } else {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100347 mCustomViewContainer = new FrameLayout(this);
Andrei Popescu78f75702009-06-26 16:50:04 +0100348 mCustomViewContainer.setBackgroundColor(Color.BLACK);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100349 mContentView = new FrameLayout(this);
Ben Murdochbff2d602009-07-01 20:19:05 +0100350
351 LinearLayout linearLayout = new LinearLayout(this);
352 linearLayout.setOrientation(LinearLayout.VERTICAL);
353 mErrorConsoleContainer = new LinearLayout(this);
354 linearLayout.addView(mErrorConsoleContainer, new LinearLayout.LayoutParams(
355 ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
356 linearLayout.addView(mContentView, COVER_SCREEN_PARAMS);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100357 frameLayout.addView(mCustomViewContainer, COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +0100358 frameLayout.addView(linearLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400359 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800360
361 // Create the tab control and our initial tab
362 mTabControl = new TabControl(this);
363
364 // Open the icon database and retain all the bookmark urls for favicons
365 retainIconsOnStartup();
366
367 // Keep a settings instance handy.
368 mSettings = BrowserSettings.getInstance();
369 mSettings.setTabControl(mTabControl);
370 mSettings.loadFromDb(this);
371
372 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
373 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
374
Grace Klobaa34f6862009-07-31 16:28:17 -0700375 /* enables registration for changes in network status from
376 http stack */
377 mNetworkStateChangedFilter = new IntentFilter();
378 mNetworkStateChangedFilter.addAction(
379 ConnectivityManager.CONNECTIVITY_ACTION);
380 mNetworkStateIntentReceiver = new BroadcastReceiver() {
381 @Override
382 public void onReceive(Context context, Intent intent) {
383 if (intent.getAction().equals(
384 ConnectivityManager.CONNECTIVITY_ACTION)) {
385 boolean down = intent.getBooleanExtra(
386 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
387 onNetworkToggle(!down);
388 }
389 }
390 };
391
Grace Kloba615c6c92009-08-03 10:22:44 -0700392 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
393 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
394 filter.addDataScheme("package");
395 mPackageInstallationReceiver = new BroadcastReceiver() {
396 @Override
397 public void onReceive(Context context, Intent intent) {
398 final String action = intent.getAction();
399 final String packageName = intent.getData()
400 .getSchemeSpecificPart();
401 final boolean replacing = intent.getBooleanExtra(
402 Intent.EXTRA_REPLACING, false);
403 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
404 // if it is replacing, refreshPlugins() when adding
405 return;
406 }
407 PackageManager pm = BrowserActivity.this.getPackageManager();
408 PackageInfo pkgInfo = null;
409 try {
410 pkgInfo = pm.getPackageInfo(packageName,
411 PackageManager.GET_PERMISSIONS);
412 } catch (PackageManager.NameNotFoundException e) {
413 return;
414 }
415 if (pkgInfo != null) {
416 String permissions[] = pkgInfo.requestedPermissions;
417 if (permissions == null) {
418 return;
419 }
420 boolean permissionOk = false;
421 for (String permit : permissions) {
422 if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
423 permissionOk = true;
424 break;
425 }
426 }
427 if (permissionOk) {
428 PluginManager.getInstance(BrowserActivity.this)
429 .refreshPlugins(
430 Intent.ACTION_PACKAGE_ADDED
431 .equals(action));
432 }
433 }
434 }
435 };
436 registerReceiver(mPackageInstallationReceiver, filter);
437
Satish Sampath565505b2009-05-29 15:37:27 +0100438 // If this was a web search request, pass it on to the default web search provider.
439 if (handleWebSearchIntent(getIntent())) {
440 moveTaskToBack(true);
441 return;
442 }
443
The Android Open Source Project0c908882009-03-03 19:32:16 -0800444 if (!mTabControl.restoreState(icicle)) {
445 // clear up the thumbnail directory if we can't restore the state as
446 // none of the files in the directory are referenced any more.
447 new ClearThumbnails().execute(
448 mTabControl.getThumbnailDir().listFiles());
Grace Klobaaab3f092009-07-30 12:29:51 -0700449 // there is no quit on Android. But if we can't restore the state,
450 // we can treat it as a new Browser, remove the old session cookies.
451 CookieManager.getInstance().removeSessionCookie();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800452 final Intent intent = getIntent();
453 final Bundle extra = intent.getExtras();
454 // Create an initial tab.
455 // If the intent is ACTION_VIEW and data is not null, the Browser is
456 // invoked to view the content by another application. In this case,
457 // the tab will be close when exit.
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700458 UrlData urlData = getUrlDataFromIntent(intent);
459
The Android Open Source Project0c908882009-03-03 19:32:16 -0800460 final TabControl.Tab t = mTabControl.createNewTab(
461 Intent.ACTION_VIEW.equals(intent.getAction()) &&
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700462 intent.getData() != null,
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700463 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800464 mTabControl.setCurrentTab(t);
465 // This is one of the only places we call attachTabToContentView
466 // without animating from the tab picker.
467 attachTabToContentView(t);
468 WebView webView = t.getWebView();
469 if (extra != null) {
470 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
471 if (scale > 0 && scale <= 1000) {
472 webView.setInitialScale(scale);
473 }
474 }
475 // If we are not restoring from an icicle, then there is a high
476 // likely hood this is the first run. So, check to see if the
477 // homepage needs to be configured and copy any plugins from our
478 // asset directory to the data partition.
479 if ((extra == null || !extra.getBoolean("testing"))
480 && !mSettings.isLoginInitialized()) {
481 setupHomePage();
482 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800483
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700484 if (urlData.isEmpty()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800485 if (mSettings.isLoginInitialized()) {
Leon Scroggins64b80f32009-08-07 12:03:34 -0400486 bookmarksOrHistoryPicker(false);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800487 } else {
488 waitForCredentials();
489 }
490 } else {
Grace Kloba81678d92009-06-30 07:09:56 -0700491 if (extra != null) {
492 urlData.setPostData(extra
493 .getByteArray(Browser.EXTRA_POST_DATA));
494 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700495 urlData.loadIn(webView);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800496 }
497 } else {
498 // TabControl.restoreState() will create a new tab even if
499 // restoring the state fails. Attach it to the view here since we
500 // are not animating from the tab picker.
501 attachTabToContentView(mTabControl.getCurrentTab());
502 }
Grace Kloba615c6c92009-08-03 10:22:44 -0700503
Feng Qianb3c02da2009-06-29 15:58:08 -0700504 // Read JavaScript flags if it exists.
505 String jsFlags = mSettings.getJsFlags();
506 if (jsFlags.trim().length() != 0) {
507 mTabControl.getCurrentWebView().setJsFlags(jsFlags);
508 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800509 }
510
511 @Override
512 protected void onNewIntent(Intent intent) {
513 TabControl.Tab current = mTabControl.getCurrentTab();
514 // When a tab is closed on exit, the current tab index is set to -1.
515 // Reset before proceed as Browser requires the current tab to be set.
516 if (current == null) {
517 // Try to reset the tab in case the index was incorrect.
518 current = mTabControl.getTab(0);
519 if (current == null) {
520 // No tabs at all so just ignore this intent.
521 return;
522 }
523 mTabControl.setCurrentTab(current);
524 attachTabToContentView(current);
525 resetTitleAndIcon(current.getWebView());
526 }
527 final String action = intent.getAction();
528 final int flags = intent.getFlags();
529 if (Intent.ACTION_MAIN.equals(action) ||
530 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
531 // just resume the browser
532 return;
533 }
534 if (Intent.ACTION_VIEW.equals(action)
535 || Intent.ACTION_SEARCH.equals(action)
536 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
537 || Intent.ACTION_WEB_SEARCH.equals(action)) {
Satish Sampath565505b2009-05-29 15:37:27 +0100538 // If this was a search request (e.g. search query directly typed into the address bar),
539 // pass it on to the default web search provider.
540 if (handleWebSearchIntent(intent)) {
541 return;
542 }
543
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700544 UrlData urlData = getUrlDataFromIntent(intent);
545 if (urlData.isEmpty()) {
546 urlData = new UrlData(mSettings.getHomePage());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800547 }
Grace Kloba81678d92009-06-30 07:09:56 -0700548 urlData.setPostData(intent
549 .getByteArrayExtra(Browser.EXTRA_POST_DATA));
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700550
Grace Klobacc634032009-07-28 15:58:19 -0700551 final String appId = intent
552 .getStringExtra(Browser.EXTRA_APPLICATION_ID);
553 if (Intent.ACTION_VIEW.equals(action)
554 && !getPackageName().equals(appId)
555 && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
Patrick Scottcd115892009-07-16 09:42:58 -0400556 TabControl.Tab appTab = mTabControl.getTabFromId(appId);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700557 if (appTab != null) {
558 Log.i(LOGTAG, "Reusing tab for " + appId);
559 // Dismiss the subwindow if applicable.
560 dismissSubWindow(appTab);
561 // Since we might kill the WebView, remove it from the
562 // content view first.
563 removeTabFromContentView(appTab);
564 // Recreate the main WebView after destroying the old one.
565 // If the WebView has the same original url and is on that
566 // page, it can be reused.
567 boolean needsLoad =
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700568 mTabControl.recreateWebView(appTab, urlData.mUrl);
Ben Murdochbff2d602009-07-01 20:19:05 +0100569
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700570 if (current != appTab) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700571 showTab(appTab, needsLoad ? urlData : EMPTY_URL_DATA);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700572 } else {
573 if (mTabOverview != null && mAnimationCount == 0) {
574 sendAnimateFromOverview(appTab, false,
Grace Klobaec7eb372009-06-16 13:45:56 -0700575 needsLoad ? urlData : EMPTY_URL_DATA,
Grace Kloba8ca2c792009-05-26 15:41:51 -0700576 TAB_OVERVIEW_DELAY, null);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700577 } else {
578 // If the tab was the current tab, we have to attach
579 // it to the view system again.
580 attachTabToContentView(appTab);
581 if (needsLoad) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700582 urlData.loadIn(appTab.getWebView());
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700583 }
584 }
585 }
586 return;
Patrick Scottcd115892009-07-16 09:42:58 -0400587 } else {
588 // No matching application tab, try to find a regular tab
589 // with a matching url.
590 appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
591 if (appTab != null) {
592 if (current != appTab) {
593 // Use EMPTY_URL_DATA so we do not reload the page
594 showTab(appTab, EMPTY_URL_DATA);
595 } else {
596 if (mTabOverview != null && mAnimationCount == 0) {
597 sendAnimateFromOverview(appTab, false,
598 EMPTY_URL_DATA, TAB_OVERVIEW_DELAY,
599 null);
600 }
601 // Don't do anything here since we are on the
602 // correct page.
603 }
604 } else {
605 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
606 // will be opened in a new tab unless we have reached
607 // MAX_TABS. Then the url will be opened in the current
608 // tab. If a new tab is created, it will have "true" for
609 // exit on close.
610 openTabAndShow(urlData, null, true, appId);
611 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700612 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800613 } else {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700614 if ("about:debug".equals(urlData.mUrl)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800615 mSettings.toggleDebugSettings();
616 return;
617 }
618 // If the Window overview is up and we are not in the midst of
619 // an animation, animate away from the Window overview.
620 if (mTabOverview != null && mAnimationCount == 0) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700621 sendAnimateFromOverview(current, false, urlData,
The Android Open Source Project0c908882009-03-03 19:32:16 -0800622 TAB_OVERVIEW_DELAY, null);
623 } else {
624 // Get rid of the subwindow if it exists
625 dismissSubWindow(current);
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700626 urlData.loadIn(current.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800627 }
628 }
629 }
630 }
631
Satish Sampath565505b2009-05-29 15:37:27 +0100632 private int parseUrlShortcut(String url) {
633 if (url == null) return SHORTCUT_INVALID;
634
635 // FIXME: quick search, need to be customized by setting
636 if (url.length() > 2 && url.charAt(1) == ' ') {
637 switch (url.charAt(0)) {
638 case 'g': return SHORTCUT_GOOGLE_SEARCH;
639 case 'w': return SHORTCUT_WIKIPEDIA_SEARCH;
640 case 'd': return SHORTCUT_DICTIONARY_SEARCH;
641 case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH;
642 }
643 }
644 return SHORTCUT_INVALID;
645 }
646
647 /**
648 * Launches the default web search activity with the query parameters if the given intent's data
649 * are identified as plain search terms and not URLs/shortcuts.
650 * @return true if the intent was handled and web search activity was launched, false if not.
651 */
652 private boolean handleWebSearchIntent(Intent intent) {
653 if (intent == null) return false;
654
655 String url = null;
656 final String action = intent.getAction();
657 if (Intent.ACTION_VIEW.equals(action)) {
658 url = intent.getData().toString();
659 } else if (Intent.ACTION_SEARCH.equals(action)
660 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
661 || Intent.ACTION_WEB_SEARCH.equals(action)) {
662 url = intent.getStringExtra(SearchManager.QUERY);
663 }
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100664 return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA));
Satish Sampath565505b2009-05-29 15:37:27 +0100665 }
666
667 /**
668 * Launches the default web search activity with the query parameters if the given url string
669 * was identified as plain search terms and not URL/shortcut.
670 * @return true if the request was handled and web search activity was launched, false if not.
671 */
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100672 private boolean handleWebSearchRequest(String inUrl, Bundle appData) {
Satish Sampath565505b2009-05-29 15:37:27 +0100673 if (inUrl == null) return false;
674
675 // In general, we shouldn't modify URL from Intent.
676 // But currently, we get the user-typed URL from search box as well.
677 String url = fixUrl(inUrl).trim();
678
679 // URLs and site specific search shortcuts are handled by the regular flow of control, so
680 // return early.
681 if (Regex.WEB_URL_PATTERN.matcher(url).matches()
Satish Sampathbc5b9f32009-06-04 18:21:40 +0100682 || ACCEPTED_URI_SCHEMA.matcher(url).matches()
Satish Sampath565505b2009-05-29 15:37:27 +0100683 || parseUrlShortcut(url) != SHORTCUT_INVALID) {
684 return false;
685 }
686
687 Browser.updateVisitedHistory(mResolver, url, false);
688 Browser.addSearchUrl(mResolver, url);
689
690 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
691 intent.addCategory(Intent.CATEGORY_DEFAULT);
692 intent.putExtra(SearchManager.QUERY, url);
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100693 if (appData != null) {
694 intent.putExtra(SearchManager.APP_DATA, appData);
695 }
Grace Klobacc634032009-07-28 15:58:19 -0700696 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
Satish Sampath565505b2009-05-29 15:37:27 +0100697 startActivity(intent);
698
699 return true;
700 }
701
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700702 private UrlData getUrlDataFromIntent(Intent intent) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800703 String url = null;
704 if (intent != null) {
705 final String action = intent.getAction();
706 if (Intent.ACTION_VIEW.equals(action)) {
707 url = smartUrlFilter(intent.getData());
708 if (url != null && url.startsWith("content:")) {
709 /* Append mimetype so webview knows how to display */
710 String mimeType = intent.resolveType(getContentResolver());
711 if (mimeType != null) {
712 url += "?" + mimeType;
713 }
714 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700715 if ("inline:".equals(url)) {
716 return new InlinedUrlData(
717 intent.getStringExtra(Browser.EXTRA_INLINE_CONTENT),
718 intent.getType(),
719 intent.getStringExtra(Browser.EXTRA_INLINE_ENCODING),
720 intent.getStringExtra(Browser.EXTRA_INLINE_FAILURL));
721 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800722 } else if (Intent.ACTION_SEARCH.equals(action)
723 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
724 || Intent.ACTION_WEB_SEARCH.equals(action)) {
725 url = intent.getStringExtra(SearchManager.QUERY);
726 if (url != null) {
727 mLastEnteredUrl = url;
728 // Don't add Urls, just search terms.
729 // Urls will get added when the page is loaded.
730 if (!Regex.WEB_URL_PATTERN.matcher(url).matches()) {
731 Browser.updateVisitedHistory(mResolver, url, false);
732 }
733 // In general, we shouldn't modify URL from Intent.
734 // But currently, we get the user-typed URL from search box as well.
735 url = fixUrl(url);
736 url = smartUrlFilter(url);
737 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
738 if (url.contains(searchSource)) {
739 String source = null;
740 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
741 if (appData != null) {
742 source = appData.getString(SearchManager.SOURCE);
743 }
744 if (TextUtils.isEmpty(source)) {
745 source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
746 }
747 url = url.replace(searchSource, "&source=android-"+source+"&");
748 }
749 }
750 }
751 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700752 return new UrlData(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800753 }
754
755 /* package */ static String fixUrl(String inUrl) {
756 if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
757 return inUrl;
758 if (inUrl.startsWith("http:") ||
759 inUrl.startsWith("https:")) {
760 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
761 inUrl = inUrl.replaceFirst("/", "//");
762 } else inUrl = inUrl.replaceFirst(":", "://");
763 }
764 return inUrl;
765 }
766
767 /**
768 * Looking for the pattern like this
769 *
770 * *
771 * * *
772 * *** * *******
773 * * *
774 * * *
775 * *
776 */
777 private final SensorListener mSensorListener = new SensorListener() {
778 private long mLastGestureTime;
779 private float[] mPrev = new float[3];
780 private float[] mPrevDiff = new float[3];
781 private float[] mDiff = new float[3];
782 private float[] mRevertDiff = new float[3];
783
784 public void onSensorChanged(int sensor, float[] values) {
785 boolean show = false;
786 float[] diff = new float[3];
787
788 for (int i = 0; i < 3; i++) {
789 diff[i] = values[i] - mPrev[i];
790 if (Math.abs(diff[i]) > 1) {
791 show = true;
792 }
793 if ((diff[i] > 1.0 && mDiff[i] < 0.2)
794 || (diff[i] < -1.0 && mDiff[i] > -0.2)) {
795 // start track when there is a big move, or revert
796 mRevertDiff[i] = mDiff[i];
797 mDiff[i] = 0;
798 } else if (diff[i] > -0.2 && diff[i] < 0.2) {
799 // reset when it is flat
800 mDiff[i] = mRevertDiff[i] = 0;
801 }
802 mDiff[i] += diff[i];
803 mPrevDiff[i] = diff[i];
804 mPrev[i] = values[i];
805 }
806
807 if (false) {
808 // only shows if we think the delta is big enough, in an attempt
809 // to detect "serious" moves left/right or up/down
810 Log.d("BrowserSensorHack", "sensorChanged " + sensor + " ("
811 + values[0] + ", " + values[1] + ", " + values[2] + ")"
812 + " diff(" + diff[0] + " " + diff[1] + " " + diff[2]
813 + ")");
814 Log.d("BrowserSensorHack", " mDiff(" + mDiff[0] + " "
815 + mDiff[1] + " " + mDiff[2] + ")" + " mRevertDiff("
816 + mRevertDiff[0] + " " + mRevertDiff[1] + " "
817 + mRevertDiff[2] + ")");
818 }
819
820 long now = android.os.SystemClock.uptimeMillis();
821 if (now - mLastGestureTime > 1000) {
822 mLastGestureTime = 0;
823
824 float y = mDiff[1];
825 float z = mDiff[2];
826 float ay = Math.abs(y);
827 float az = Math.abs(z);
828 float ry = mRevertDiff[1];
829 float rz = mRevertDiff[2];
830 float ary = Math.abs(ry);
831 float arz = Math.abs(rz);
832 boolean gestY = ay > 2.5f && ary > 1.0f && ay > ary;
833 boolean gestZ = az > 3.5f && arz > 1.0f && az > arz;
834
835 if ((gestY || gestZ) && !(gestY && gestZ)) {
836 WebView view = mTabControl.getCurrentWebView();
837
838 if (view != null) {
839 if (gestZ) {
840 if (z < 0) {
841 view.zoomOut();
842 } else {
843 view.zoomIn();
844 }
845 } else {
846 view.flingScroll(0, Math.round(y * 100));
847 }
848 }
849 mLastGestureTime = now;
850 }
851 }
852 }
853
854 public void onAccuracyChanged(int sensor, int accuracy) {
855 // TODO Auto-generated method stub
856
857 }
858 };
859
860 @Override protected void onResume() {
861 super.onResume();
Dave Bort31a6d1c2009-04-13 15:56:49 -0700862 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800863 Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
864 }
865
866 if (!mActivityInPause) {
867 Log.e(LOGTAG, "BrowserActivity is already resumed.");
868 return;
869 }
870
Mike Reed7bfa63b2009-05-28 11:08:32 -0400871 mTabControl.resumeCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800872 mActivityInPause = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400873 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800874
875 if (mWakeLock.isHeld()) {
876 mHandler.removeMessages(RELEASE_WAKELOCK);
877 mWakeLock.release();
878 }
879
880 if (mCredsDlg != null) {
881 if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
882 // In case credential request never comes back
883 mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
884 }
885 }
886
887 registerReceiver(mNetworkStateIntentReceiver,
888 mNetworkStateChangedFilter);
889 WebView.enablePlatformNotifications();
890
891 if (mSettings.doFlick()) {
892 if (mSensorManager == null) {
893 mSensorManager = (SensorManager) getSystemService(
894 Context.SENSOR_SERVICE);
895 }
896 mSensorManager.registerListener(mSensorListener,
897 SensorManager.SENSOR_ACCELEROMETER,
898 SensorManager.SENSOR_DELAY_FASTEST);
899 } else {
900 mSensorManager = null;
901 }
902 }
903
904 /**
905 * onSaveInstanceState(Bundle map)
906 * onSaveInstanceState is called right before onStop(). The map contains
907 * the saved state.
908 */
909 @Override protected void onSaveInstanceState(Bundle outState) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700910 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800911 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
912 }
913 // the default implementation requires each view to have an id. As the
914 // browser handles the state itself and it doesn't use id for the views,
915 // don't call the default implementation. Otherwise it will trigger the
916 // warning like this, "couldn't save which view has focus because the
917 // focused view XXX has no id".
918
919 // Save all the tabs
920 mTabControl.saveState(outState);
921 }
922
923 @Override protected void onPause() {
924 super.onPause();
925
926 if (mActivityInPause) {
927 Log.e(LOGTAG, "BrowserActivity is already paused.");
928 return;
929 }
930
Mike Reed7bfa63b2009-05-28 11:08:32 -0400931 mTabControl.pauseCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800932 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400933 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800934 mWakeLock.acquire();
935 mHandler.sendMessageDelayed(mHandler
936 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
937 }
938
939 // Clear the credentials toast if it is up
940 if (mCredsDlg != null && mCredsDlg.isShowing()) {
941 mCredsDlg.dismiss();
942 }
943 mCredsDlg = null;
944
945 cancelStopToast();
946
947 // unregister network state listener
948 unregisterReceiver(mNetworkStateIntentReceiver);
949 WebView.disablePlatformNotifications();
950
951 if (mSensorManager != null) {
952 mSensorManager.unregisterListener(mSensorListener);
953 }
954 }
955
956 @Override protected void onDestroy() {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700957 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800958 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
959 }
960 super.onDestroy();
961 // Remove the current tab and sub window
962 TabControl.Tab t = mTabControl.getCurrentTab();
Patrick Scottfb5e77f2009-04-08 19:17:37 -0700963 if (t != null) {
964 dismissSubWindow(t);
965 removeTabFromContentView(t);
966 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800967 // Destroy all the tabs
968 mTabControl.destroy();
969 WebIconDatabase.getInstance().close();
970 if (mGlsConnection != null) {
971 unbindService(mGlsConnection);
972 mGlsConnection = null;
973 }
974
975 //
976 // stop MASF proxy service
977 //
978 //Intent proxyServiceIntent = new Intent();
979 //proxyServiceIntent.setComponent
980 // (new ComponentName(
981 // "com.android.masfproxyservice",
982 // "com.android.masfproxyservice.MasfProxyService"));
983 //stopService(proxyServiceIntent);
Grace Klobab4da0ad2009-05-14 14:45:40 -0700984
985 unregisterReceiver(mPackageInstallationReceiver);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800986 }
987
988 @Override
989 public void onConfigurationChanged(Configuration newConfig) {
990 super.onConfigurationChanged(newConfig);
991
992 if (mPageInfoDialog != null) {
993 mPageInfoDialog.dismiss();
994 showPageInfo(
995 mPageInfoView,
996 mPageInfoFromShowSSLCertificateOnError.booleanValue());
997 }
998 if (mSSLCertificateDialog != null) {
999 mSSLCertificateDialog.dismiss();
1000 showSSLCertificate(
1001 mSSLCertificateView);
1002 }
1003 if (mSSLCertificateOnErrorDialog != null) {
1004 mSSLCertificateOnErrorDialog.dismiss();
1005 showSSLCertificateOnError(
1006 mSSLCertificateOnErrorView,
1007 mSSLCertificateOnErrorHandler,
1008 mSSLCertificateOnErrorError);
1009 }
1010 if (mHttpAuthenticationDialog != null) {
1011 String title = ((TextView) mHttpAuthenticationDialog
1012 .findViewById(com.android.internal.R.id.alertTitle)).getText()
1013 .toString();
1014 String name = ((TextView) mHttpAuthenticationDialog
1015 .findViewById(R.id.username_edit)).getText().toString();
1016 String password = ((TextView) mHttpAuthenticationDialog
1017 .findViewById(R.id.password_edit)).getText().toString();
1018 int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1019 .getId();
1020 mHttpAuthenticationDialog.dismiss();
1021 showHttpAuthentication(mHttpAuthHandler, null, null, title,
1022 name, password, focusId);
1023 }
1024 if (mFindDialog != null && mFindDialog.isShowing()) {
1025 mFindDialog.onConfigurationChanged(newConfig);
1026 }
1027 }
1028
1029 @Override public void onLowMemory() {
1030 super.onLowMemory();
1031 mTabControl.freeMemory();
1032 }
1033
Mike Reed7bfa63b2009-05-28 11:08:32 -04001034 private boolean resumeWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001035 if ((!mActivityInPause && !mPageStarted) ||
1036 (mActivityInPause && mPageStarted)) {
1037 CookieSyncManager.getInstance().startSync();
1038 WebView w = mTabControl.getCurrentWebView();
1039 if (w != null) {
1040 w.resumeTimers();
1041 }
1042 return true;
1043 } else {
1044 return false;
1045 }
1046 }
1047
Mike Reed7bfa63b2009-05-28 11:08:32 -04001048 private boolean pauseWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001049 if (mActivityInPause && !mPageStarted) {
1050 CookieSyncManager.getInstance().stopSync();
1051 WebView w = mTabControl.getCurrentWebView();
1052 if (w != null) {
1053 w.pauseTimers();
1054 }
1055 return true;
1056 } else {
1057 return false;
1058 }
1059 }
1060
1061 /*
1062 * This function is called when we are launching for the first time. We
1063 * are waiting for the login credentials before loading Google home
1064 * pages. This way the user will be logged in straight away.
1065 */
1066 private void waitForCredentials() {
1067 // Show a toast
1068 mCredsDlg = new ProgressDialog(this);
1069 mCredsDlg.setIndeterminate(true);
1070 mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1071 // If the user cancels the operation, then cancel the Google
1072 // Credentials request.
1073 mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1074 mCredsDlg.show();
1075
1076 // We set a timeout for the retrieval of credentials in onResume()
1077 // as that is when we have freed up some CPU time to get
1078 // the login credentials.
1079 }
1080
1081 /*
1082 * If we have received the credentials or we have timed out and we are
1083 * showing the credentials dialog, then it is time to move on.
1084 */
1085 private void resumeAfterCredentials() {
1086 if (mCredsDlg == null) {
1087 return;
1088 }
1089
1090 // Clear the toast
1091 if (mCredsDlg.isShowing()) {
1092 mCredsDlg.dismiss();
1093 }
1094 mCredsDlg = null;
1095
1096 // Clear any pending timeout
1097 mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1098
1099 // Load the page
1100 WebView w = mTabControl.getCurrentWebView();
1101 if (w != null) {
1102 w.loadUrl(mSettings.getHomePage());
1103 }
1104
1105 // Update the settings, need to do this last as it can take a moment
1106 // to persist the settings. In the mean time we could be loading
1107 // content.
1108 mSettings.setLoginInitialized(this);
1109 }
1110
1111 // Open the icon database and retain all the icons for visited sites.
1112 private void retainIconsOnStartup() {
1113 final WebIconDatabase db = WebIconDatabase.getInstance();
1114 db.open(getDir("icons", 0).getPath());
1115 try {
1116 Cursor c = Browser.getAllBookmarks(mResolver);
1117 if (!c.moveToFirst()) {
1118 c.deactivate();
1119 return;
1120 }
1121 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1122 do {
1123 String url = c.getString(urlIndex);
1124 db.retainIconForPageUrl(url);
1125 } while (c.moveToNext());
1126 c.deactivate();
1127 } catch (IllegalStateException e) {
1128 Log.e(LOGTAG, "retainIconsOnStartup", e);
1129 }
1130 }
1131
1132 // Helper method for getting the top window.
1133 WebView getTopWindow() {
1134 return mTabControl.getCurrentTopWebView();
1135 }
1136
1137 @Override
1138 public boolean onCreateOptionsMenu(Menu menu) {
1139 super.onCreateOptionsMenu(menu);
1140
1141 MenuInflater inflater = getMenuInflater();
1142 inflater.inflate(R.menu.browser, menu);
1143 mMenu = menu;
1144 updateInLoadMenuItems();
1145 return true;
1146 }
1147
1148 /**
1149 * As the menu can be open when loading state changes
1150 * we must manually update the state of the stop/reload menu
1151 * item
1152 */
1153 private void updateInLoadMenuItems() {
1154 if (mMenu == null) {
1155 return;
1156 }
1157 MenuItem src = mInLoad ?
1158 mMenu.findItem(R.id.stop_menu_id):
1159 mMenu.findItem(R.id.reload_menu_id);
1160 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1161 dest.setIcon(src.getIcon());
1162 dest.setTitle(src.getTitle());
1163 }
1164
1165 @Override
1166 public boolean onContextItemSelected(MenuItem item) {
1167 // chording is not an issue with context menus, but we use the same
1168 // options selector, so set mCanChord to true so we can access them.
1169 mCanChord = true;
1170 int id = item.getItemId();
1171 final WebView webView = getTopWindow();
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001172 if (null == webView) {
1173 return false;
1174 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001175 final HashMap hrefMap = new HashMap();
1176 hrefMap.put("webview", webView);
1177 final Message msg = mHandler.obtainMessage(
1178 FOCUS_NODE_HREF, id, 0, hrefMap);
1179 switch (id) {
1180 // -- Browser context menu
1181 case R.id.open_context_menu_id:
1182 case R.id.open_newtab_context_menu_id:
1183 case R.id.bookmark_context_menu_id:
1184 case R.id.save_link_context_menu_id:
1185 case R.id.share_link_context_menu_id:
1186 case R.id.copy_link_context_menu_id:
1187 webView.requestFocusNodeHref(msg);
1188 break;
1189
1190 default:
1191 // For other context menus
1192 return onOptionsItemSelected(item);
1193 }
1194 mCanChord = false;
1195 return true;
1196 }
1197
1198 private Bundle createGoogleSearchSourceBundle(String source) {
1199 Bundle bundle = new Bundle();
1200 bundle.putString(SearchManager.SOURCE, source);
1201 return bundle;
1202 }
1203
1204 /**
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001205 * Overriding this to insert a local information bundle
The Android Open Source Project0c908882009-03-03 19:32:16 -08001206 */
1207 @Override
1208 public boolean onSearchRequested() {
Leon Scroggins5bbe9802009-07-31 13:10:55 -04001209 String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
Grace Kloba83f47342009-07-20 10:44:31 -07001210 startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001211 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001212 return true;
1213 }
1214
1215 @Override
1216 public void startSearch(String initialQuery, boolean selectInitialQuery,
1217 Bundle appSearchData, boolean globalSearch) {
1218 if (appSearchData == null) {
1219 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1220 }
1221 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1222 }
1223
1224 @Override
1225 public boolean onOptionsItemSelected(MenuItem item) {
1226 if (!mCanChord) {
1227 // The user has already fired a shortcut with this hold down of the
1228 // menu key.
1229 return false;
1230 }
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001231 if (null == mTabOverview && null == getTopWindow()) {
1232 return false;
1233 }
Grace Kloba6ee9c492009-07-13 10:04:34 -07001234 if (mMenuIsDown) {
1235 // The shortcut action consumes the MENU. Even if it is still down,
1236 // it won't trigger the next shortcut action. In the case of the
1237 // shortcut action triggering a new activity, like Bookmarks, we
1238 // won't get onKeyUp for MENU. So it is important to reset it here.
1239 mMenuIsDown = false;
1240 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001241 switch (item.getItemId()) {
1242 // -- Main menu
Leon Scroggins64b80f32009-08-07 12:03:34 -04001243 case R.id.goto_menu_id:
The Android Open Source Project0c908882009-03-03 19:32:16 -08001244 bookmarksOrHistoryPicker(false);
1245 break;
1246
1247 case R.id.windows_menu_id:
1248 if (mTabControl.getTabCount() == 1) {
Leon Scroggins64b80f32009-08-07 12:03:34 -04001249 openTabAndShow(EMPTY_URL_DATA, null, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001250 } else {
1251 tabPicker(true, mTabControl.getCurrentIndex(), false);
1252 }
1253 break;
1254
1255 case R.id.stop_reload_menu_id:
1256 if (mInLoad) {
1257 stopLoading();
1258 } else {
1259 getTopWindow().reload();
1260 }
1261 break;
1262
1263 case R.id.back_menu_id:
1264 getTopWindow().goBack();
1265 break;
1266
1267 case R.id.forward_menu_id:
1268 getTopWindow().goForward();
1269 break;
1270
1271 case R.id.close_menu_id:
1272 // Close the subwindow if it exists.
1273 if (mTabControl.getCurrentSubWindow() != null) {
1274 dismissSubWindow(mTabControl.getCurrentTab());
1275 break;
1276 }
1277 final int currentIndex = mTabControl.getCurrentIndex();
1278 final TabControl.Tab parent =
1279 mTabControl.getCurrentTab().getParentTab();
1280 int indexToShow = -1;
1281 if (parent != null) {
1282 indexToShow = mTabControl.getTabIndex(parent);
1283 } else {
1284 // Get the last tab in the list. If it is the current tab,
1285 // subtract 1 more.
1286 indexToShow = mTabControl.getTabCount() - 1;
1287 if (currentIndex == indexToShow) {
1288 indexToShow--;
1289 }
1290 }
1291 switchTabs(currentIndex, indexToShow, true);
1292 break;
1293
1294 case R.id.homepage_menu_id:
1295 TabControl.Tab current = mTabControl.getCurrentTab();
1296 if (current != null) {
1297 dismissSubWindow(current);
1298 current.getWebView().loadUrl(mSettings.getHomePage());
1299 }
1300 break;
1301
1302 case R.id.preferences_menu_id:
1303 Intent intent = new Intent(this,
1304 BrowserPreferencesPage.class);
1305 startActivityForResult(intent, PREFERENCES_PAGE);
1306 break;
1307
1308 case R.id.find_menu_id:
1309 if (null == mFindDialog) {
1310 mFindDialog = new FindDialog(this);
1311 }
1312 mFindDialog.setWebView(getTopWindow());
1313 mFindDialog.show();
1314 mMenuState = EMPTY_MENU;
1315 break;
1316
1317 case R.id.select_text_id:
1318 getTopWindow().emulateShiftHeld();
1319 break;
1320 case R.id.page_info_menu_id:
1321 showPageInfo(mTabControl.getCurrentTab(), false);
1322 break;
1323
1324 case R.id.classic_history_menu_id:
1325 bookmarksOrHistoryPicker(true);
1326 break;
1327
1328 case R.id.share_page_menu_id:
1329 Browser.sendString(this, getTopWindow().getUrl());
1330 break;
1331
1332 case R.id.dump_nav_menu_id:
1333 getTopWindow().debugDump();
1334 break;
1335
1336 case R.id.zoom_in_menu_id:
1337 getTopWindow().zoomIn();
1338 break;
1339
1340 case R.id.zoom_out_menu_id:
1341 getTopWindow().zoomOut();
1342 break;
1343
1344 case R.id.view_downloads_menu_id:
1345 viewDownloads(null);
1346 break;
1347
1348 // -- Tab menu
1349 case R.id.view_tab_menu_id:
1350 if (mTabListener != null && mTabOverview != null) {
1351 int pos = mTabOverview.getContextMenuPosition(item);
1352 mTabOverview.setCurrentIndex(pos);
1353 mTabListener.onClick(pos);
1354 }
1355 break;
1356
1357 case R.id.remove_tab_menu_id:
1358 if (mTabListener != null && mTabOverview != null) {
1359 int pos = mTabOverview.getContextMenuPosition(item);
1360 mTabListener.remove(pos);
1361 }
1362 break;
1363
1364 case R.id.new_tab_menu_id:
1365 // No need to check for mTabOverview here since we are not
1366 // dependent on it for a position.
1367 if (mTabListener != null) {
1368 // If the overview happens to be non-null, make the "New
1369 // Tab" cell visible.
1370 if (mTabOverview != null) {
1371 mTabOverview.setCurrentIndex(ImageGrid.NEW_TAB);
1372 }
1373 mTabListener.onClick(ImageGrid.NEW_TAB);
1374 }
1375 break;
1376
1377 case R.id.bookmark_tab_menu_id:
1378 if (mTabListener != null && mTabOverview != null) {
1379 int pos = mTabOverview.getContextMenuPosition(item);
1380 TabControl.Tab t = mTabControl.getTab(pos);
1381 // Since we called populatePickerData for all of the
1382 // tabs, getTitle and getUrl will return appropriate
1383 // values.
1384 Browser.saveBookmark(BrowserActivity.this, t.getTitle(),
1385 t.getUrl());
1386 }
1387 break;
1388
1389 case R.id.history_tab_menu_id:
1390 bookmarksOrHistoryPicker(true);
1391 break;
1392
1393 case R.id.bookmarks_tab_menu_id:
1394 bookmarksOrHistoryPicker(false);
1395 break;
1396
1397 case R.id.properties_tab_menu_id:
1398 if (mTabListener != null && mTabOverview != null) {
1399 int pos = mTabOverview.getContextMenuPosition(item);
1400 showPageInfo(mTabControl.getTab(pos), false);
1401 }
1402 break;
1403
1404 case R.id.window_one_menu_id:
1405 case R.id.window_two_menu_id:
1406 case R.id.window_three_menu_id:
1407 case R.id.window_four_menu_id:
1408 case R.id.window_five_menu_id:
1409 case R.id.window_six_menu_id:
1410 case R.id.window_seven_menu_id:
1411 case R.id.window_eight_menu_id:
1412 {
1413 int menuid = item.getItemId();
1414 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1415 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1416 TabControl.Tab desiredTab = mTabControl.getTab(id);
1417 if (desiredTab != null &&
1418 desiredTab != mTabControl.getCurrentTab()) {
1419 switchTabs(mTabControl.getCurrentIndex(), id, false);
1420 }
1421 break;
1422 }
1423 }
1424 }
1425 break;
1426
1427 default:
1428 if (!super.onOptionsItemSelected(item)) {
1429 return false;
1430 }
1431 // Otherwise fall through.
1432 }
1433 mCanChord = false;
1434 return true;
1435 }
1436
1437 public void closeFind() {
1438 mMenuState = R.id.MAIN_MENU;
1439 }
1440
1441 @Override public boolean onPrepareOptionsMenu(Menu menu)
1442 {
1443 // This happens when the user begins to hold down the menu key, so
1444 // allow them to chord to get a shortcut.
1445 mCanChord = true;
1446 // Note: setVisible will decide whether an item is visible; while
1447 // setEnabled() will decide whether an item is enabled, which also means
1448 // whether the matching shortcut key will function.
1449 super.onPrepareOptionsMenu(menu);
1450 switch (mMenuState) {
1451 case R.id.TAB_MENU:
1452 if (mCurrentMenuState != mMenuState) {
1453 menu.setGroupVisible(R.id.MAIN_MENU, false);
1454 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1455 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1456 menu.setGroupVisible(R.id.TAB_MENU, true);
1457 menu.setGroupEnabled(R.id.TAB_MENU, true);
1458 }
1459 boolean newT = mTabControl.getTabCount() < TabControl.MAX_TABS;
1460 final MenuItem tab = menu.findItem(R.id.new_tab_menu_id);
1461 tab.setVisible(newT);
1462 tab.setEnabled(newT);
1463 break;
1464 case EMPTY_MENU:
1465 if (mCurrentMenuState != mMenuState) {
1466 menu.setGroupVisible(R.id.MAIN_MENU, false);
1467 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1468 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1469 menu.setGroupVisible(R.id.TAB_MENU, false);
1470 menu.setGroupEnabled(R.id.TAB_MENU, false);
1471 }
1472 break;
1473 default:
1474 if (mCurrentMenuState != mMenuState) {
1475 menu.setGroupVisible(R.id.MAIN_MENU, true);
1476 menu.setGroupEnabled(R.id.MAIN_MENU, true);
1477 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
1478 menu.setGroupVisible(R.id.TAB_MENU, false);
1479 menu.setGroupEnabled(R.id.TAB_MENU, false);
1480 }
1481 final WebView w = getTopWindow();
1482 boolean canGoBack = false;
1483 boolean canGoForward = false;
1484 boolean isHome = false;
1485 if (w != null) {
1486 canGoBack = w.canGoBack();
1487 canGoForward = w.canGoForward();
1488 isHome = mSettings.getHomePage().equals(w.getUrl());
1489 }
1490 final MenuItem back = menu.findItem(R.id.back_menu_id);
1491 back.setEnabled(canGoBack);
1492
1493 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1494 home.setEnabled(!isHome);
1495
1496 menu.findItem(R.id.forward_menu_id)
1497 .setEnabled(canGoForward);
1498
1499 // decide whether to show the share link option
1500 PackageManager pm = getPackageManager();
1501 Intent send = new Intent(Intent.ACTION_SEND);
1502 send.setType("text/plain");
1503 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1504 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1505
1506 // If there is only 1 window, the text will be "New window"
1507 final MenuItem windows = menu.findItem(R.id.windows_menu_id);
1508 windows.setTitleCondensed(mTabControl.getTabCount() > 1 ?
1509 getString(R.string.view_tabs_condensed) :
1510 getString(R.string.tab_picker_new_tab));
1511
1512 boolean isNavDump = mSettings.isNavDump();
1513 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1514 nav.setVisible(isNavDump);
1515 nav.setEnabled(isNavDump);
1516 break;
1517 }
1518 mCurrentMenuState = mMenuState;
1519 return true;
1520 }
1521
1522 @Override
1523 public void onCreateContextMenu(ContextMenu menu, View v,
1524 ContextMenuInfo menuInfo) {
1525 WebView webview = (WebView) v;
1526 WebView.HitTestResult result = webview.getHitTestResult();
1527 if (result == null) {
1528 return;
1529 }
1530
1531 int type = result.getType();
1532 if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1533 Log.w(LOGTAG,
1534 "We should not show context menu when nothing is touched");
1535 return;
1536 }
1537 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1538 // let TextView handles context menu
1539 return;
1540 }
1541
1542 // Note, http://b/issue?id=1106666 is requesting that
1543 // an inflated menu can be used again. This is not available
1544 // yet, so inflate each time (yuk!)
1545 MenuInflater inflater = getMenuInflater();
1546 inflater.inflate(R.menu.browsercontext, menu);
1547
1548 // Show the correct menu group
1549 String extra = result.getExtra();
1550 menu.setGroupVisible(R.id.PHONE_MENU,
1551 type == WebView.HitTestResult.PHONE_TYPE);
1552 menu.setGroupVisible(R.id.EMAIL_MENU,
1553 type == WebView.HitTestResult.EMAIL_TYPE);
1554 menu.setGroupVisible(R.id.GEO_MENU,
1555 type == WebView.HitTestResult.GEO_TYPE);
1556 menu.setGroupVisible(R.id.IMAGE_MENU,
1557 type == WebView.HitTestResult.IMAGE_TYPE
1558 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1559 menu.setGroupVisible(R.id.ANCHOR_MENU,
1560 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1561 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1562
1563 // Setup custom handling depending on the type
1564 switch (type) {
1565 case WebView.HitTestResult.PHONE_TYPE:
1566 menu.setHeaderTitle(Uri.decode(extra));
1567 menu.findItem(R.id.dial_context_menu_id).setIntent(
1568 new Intent(Intent.ACTION_VIEW, Uri
1569 .parse(WebView.SCHEME_TEL + extra)));
1570 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1571 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1572 addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1573 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1574 addIntent);
1575 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1576 new Copy(extra));
1577 break;
1578
1579 case WebView.HitTestResult.EMAIL_TYPE:
1580 menu.setHeaderTitle(extra);
1581 menu.findItem(R.id.email_context_menu_id).setIntent(
1582 new Intent(Intent.ACTION_VIEW, Uri
1583 .parse(WebView.SCHEME_MAILTO + extra)));
1584 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1585 new Copy(extra));
1586 break;
1587
1588 case WebView.HitTestResult.GEO_TYPE:
1589 menu.setHeaderTitle(extra);
1590 menu.findItem(R.id.map_context_menu_id).setIntent(
1591 new Intent(Intent.ACTION_VIEW, Uri
1592 .parse(WebView.SCHEME_GEO
1593 + URLEncoder.encode(extra))));
1594 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1595 new Copy(extra));
1596 break;
1597
1598 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1599 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1600 TextView titleView = (TextView) LayoutInflater.from(this)
1601 .inflate(android.R.layout.browser_link_context_header,
1602 null);
1603 titleView.setText(extra);
1604 menu.setHeaderView(titleView);
1605 // decide whether to show the open link in new tab option
1606 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1607 mTabControl.getTabCount() < TabControl.MAX_TABS);
1608 PackageManager pm = getPackageManager();
1609 Intent send = new Intent(Intent.ACTION_SEND);
1610 send.setType("text/plain");
1611 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1612 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1613 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1614 break;
1615 }
1616 // otherwise fall through to handle image part
1617 case WebView.HitTestResult.IMAGE_TYPE:
1618 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1619 menu.setHeaderTitle(extra);
1620 }
1621 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1622 new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1623 menu.findItem(R.id.download_context_menu_id).
1624 setOnMenuItemClickListener(new Download(extra));
1625 break;
1626
1627 default:
1628 Log.w(LOGTAG, "We should not get here.");
1629 break;
1630 }
1631 }
1632
The Android Open Source Project0c908882009-03-03 19:32:16 -08001633 // Attach the given tab to the content view.
1634 private void attachTabToContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001635 // Attach the container that contains the main WebView and any other UI
1636 // associated with the tab.
1637 mContentView.addView(t.getContainer(), COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +01001638
1639 if (mShouldShowErrorConsole) {
1640 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
1641 if (errorConsole.numberOfErrors() == 0) {
1642 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1643 } else {
1644 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1645 }
1646
1647 mErrorConsoleContainer.addView(errorConsole,
1648 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
1649 ViewGroup.LayoutParams.WRAP_CONTENT));
1650 }
1651
The Android Open Source Project0c908882009-03-03 19:32:16 -08001652 // Attach the sub window if necessary
1653 attachSubWindow(t);
1654 // Request focus on the top window.
1655 t.getTopWindow().requestFocus();
1656 }
1657
1658 // Attach a sub window to the main WebView of the given tab.
1659 private void attachSubWindow(TabControl.Tab t) {
1660 // If a sub window exists, attach it to the content view.
1661 final WebView subView = t.getSubWebView();
1662 if (subView != null) {
1663 final View container = t.getSubWebViewContainer();
1664 mContentView.addView(container, COVER_SCREEN_PARAMS);
1665 subView.requestFocus();
1666 }
1667 }
1668
1669 // Remove the given tab from the content view.
1670 private void removeTabFromContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001671 // Remove the container that contains the main WebView.
1672 mContentView.removeView(t.getContainer());
Ben Murdochbff2d602009-07-01 20:19:05 +01001673
1674 if (mTabControl.getCurrentErrorConsole(false) != null) {
1675 mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
1676 }
1677
The Android Open Source Project0c908882009-03-03 19:32:16 -08001678 // Remove the sub window if it exists.
1679 if (t.getSubWebView() != null) {
1680 mContentView.removeView(t.getSubWebViewContainer());
1681 }
1682 }
1683
1684 // Remove the sub window if it exists. Also called by TabControl when the
1685 // user clicks the 'X' to dismiss a sub window.
1686 /* package */ void dismissSubWindow(TabControl.Tab t) {
1687 final WebView mainView = t.getWebView();
1688 if (t.getSubWebView() != null) {
1689 // Remove the container view and request focus on the main WebView.
1690 mContentView.removeView(t.getSubWebViewContainer());
1691 mainView.requestFocus();
1692 // Tell the TabControl to dismiss the subwindow. This will destroy
1693 // the WebView.
1694 mTabControl.dismissSubWindow(t);
1695 }
1696 }
1697
1698 // Send the ANIMTE_FROM_OVERVIEW message after changing the current tab.
1699 private void sendAnimateFromOverview(final TabControl.Tab tab,
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001700 final boolean newTab, final UrlData urlData, final int delay,
The Android Open Source Project0c908882009-03-03 19:32:16 -08001701 final Message msg) {
1702 // Set the current tab.
1703 mTabControl.setCurrentTab(tab);
1704 // Attach the WebView so it will layout.
1705 attachTabToContentView(tab);
1706 // Set the view to invisibile for now.
1707 tab.getWebView().setVisibility(View.INVISIBLE);
1708 // If there is a sub window, make it invisible too.
1709 if (tab.getSubWebView() != null) {
1710 tab.getSubWebViewContainer().setVisibility(View.INVISIBLE);
1711 }
1712 // Create our fake animating view.
1713 final AnimatingView view = new AnimatingView(this, tab);
1714 // Attach it to the view system and make in invisible so it will
1715 // layout but not flash white on the screen.
1716 mContentView.addView(view, COVER_SCREEN_PARAMS);
1717 view.setVisibility(View.INVISIBLE);
1718 // Send the animate message.
1719 final HashMap map = new HashMap();
1720 map.put("view", view);
1721 // Load the url after the AnimatingView has captured the picture. This
1722 // prevents any bad layout or bad scale from being used during
1723 // animation.
Leon Scroggins64b80f32009-08-07 12:03:34 -04001724 dismissSubWindow(tab);
1725 if (urlData.isEmpty()) {
1726 bookmarksOrHistoryPicker(false);
1727 } else {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001728 urlData.loadIn(tab.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001729 }
1730 map.put("msg", msg);
1731 mHandler.sendMessageDelayed(mHandler.obtainMessage(
1732 ANIMATE_FROM_OVERVIEW, newTab ? 1 : 0, 0, map), delay);
1733 // Increment the count to indicate that we are in an animation.
1734 mAnimationCount++;
1735 // Remove the listener so we don't get any more tab changes.
1736 mTabOverview.setListener(null);
1737 mTabListener = null;
1738 // Make the menu empty until the animation completes.
1739 mMenuState = EMPTY_MENU;
1740
1741 }
1742
1743 // 500ms animation with 800ms delay
Patrick Scott95d601f2009-06-11 10:06:46 -04001744 private static final int TAB_ANIMATION_DURATION = 200;
1745 private static final int TAB_OVERVIEW_DELAY = 500;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001746
1747 // Called by TabControl when a tab is requesting focus
1748 /* package */ void showTab(TabControl.Tab t) {
Patrick Scott95d601f2009-06-11 10:06:46 -04001749 showTab(t, EMPTY_URL_DATA);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001750 }
1751
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001752 private void showTab(TabControl.Tab t, UrlData urlData) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001753 // Disallow focus change during a tab animation.
1754 if (mAnimationCount > 0) {
1755 return;
1756 }
1757 int delay = 0;
1758 if (mTabOverview == null) {
1759 // Add a delay so the tab overview can be shown before the second
1760 // animation begins.
1761 delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
1762 tabPicker(false, mTabControl.getTabIndex(t), false);
1763 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001764 sendAnimateFromOverview(t, false, urlData, delay, null);
1765 }
1766
1767 // A wrapper function of {@link #openTabAndShow(UrlData, Message, boolean, String)}
1768 // that accepts url as string.
Mitsuru Oshimaf26aeab2009-06-11 02:53:57 -07001769 private TabControl.Tab openTabAndShow(String url, final Message msg,
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001770 boolean closeOnExit, String appId) {
Mitsuru Oshimaf26aeab2009-06-11 02:53:57 -07001771 return openTabAndShow(new UrlData(url), msg, closeOnExit, appId);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001772 }
1773
1774 // This method does a ton of stuff. It will attempt to create a new tab
1775 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
1776 // url isn't null, it will load the given url. If the tab overview is not
1777 // showing, it will animate to the tab overview, create a new tab and
1778 // animate away from it. After the animation completes, it will dispatch
1779 // the given Message. If the tab overview is already showing (i.e. this
1780 // method is called from TabListener.onClick(), the method will animate
1781 // away from the tab overview.
Mitsuru Oshimaf26aeab2009-06-11 02:53:57 -07001782 private TabControl.Tab openTabAndShow(UrlData urlData, final Message msg,
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001783 boolean closeOnExit, String appId) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001784 final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1785 final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1786 if (newTab) {
1787 int delay = 0;
1788 // If the tab overview is up and there are animations, just load
1789 // the url.
1790 if (mTabOverview != null && mAnimationCount > 0) {
Leon Scroggins64b80f32009-08-07 12:03:34 -04001791 if (urlData.isEmpty()) {
1792 bookmarksOrHistoryPicker(false);
1793 } else {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001794 // We should not have a msg here since onCreateWindow
1795 // checks the animation count and every other caller passes
1796 // null.
1797 assert msg == null;
1798 // just dismiss the subwindow and load the given url.
1799 dismissSubWindow(currentTab);
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001800 urlData.loadIn(currentTab.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001801 }
1802 } else {
1803 // show mTabOverview if it is not there.
1804 if (mTabOverview == null) {
1805 // We have to delay the animation from the tab picker by the
1806 // length of the tab animation. Add a delay so the tab
1807 // overview can be shown before the second animation begins.
1808 delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
1809 tabPicker(false, ImageGrid.NEW_TAB, false);
1810 }
1811 // Animate from the Tab overview after any animations have
1812 // finished.
Grace Klobac9181842009-04-14 08:53:22 -07001813 final TabControl.Tab tab = mTabControl.createNewTab(
Mitsuru Oshimaf26aeab2009-06-11 02:53:57 -07001814 closeOnExit, appId, urlData.mUrl);
Grace Klobaec7eb372009-06-16 13:45:56 -07001815 sendAnimateFromOverview(tab, true, urlData, delay, msg);
Grace Klobac9181842009-04-14 08:53:22 -07001816 return tab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001817 }
Leon Scroggins64b80f32009-08-07 12:03:34 -04001818 } else {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001819 // We should not have a msg here.
1820 assert msg == null;
1821 if (mTabOverview != null && mAnimationCount == 0) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001822 sendAnimateFromOverview(currentTab, false, urlData,
The Android Open Source Project0c908882009-03-03 19:32:16 -08001823 TAB_OVERVIEW_DELAY, null);
1824 } else {
1825 // Get rid of the subwindow if it exists
1826 dismissSubWindow(currentTab);
Leon Scroggins64b80f32009-08-07 12:03:34 -04001827 if (!urlData.isEmpty()) {
1828 // Load the given url.
1829 urlData.loadIn(currentTab.getWebView());
1830 } else {
1831 bookmarksOrHistoryPicker(false);
1832 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001833 }
1834 }
Grace Klobac9181842009-04-14 08:53:22 -07001835 return currentTab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001836 }
1837
1838 private Animation createTabAnimation(final AnimatingView view,
1839 final View cell, boolean scaleDown) {
1840 final AnimationSet set = new AnimationSet(true);
1841 final float scaleX = (float) cell.getWidth() / view.getWidth();
1842 final float scaleY = (float) cell.getHeight() / view.getHeight();
1843 if (scaleDown) {
1844 set.addAnimation(new ScaleAnimation(1.0f, scaleX, 1.0f, scaleY));
1845 set.addAnimation(new TranslateAnimation(0, cell.getLeft(), 0,
1846 cell.getTop()));
1847 } else {
1848 set.addAnimation(new ScaleAnimation(scaleX, 1.0f, scaleY, 1.0f));
1849 set.addAnimation(new TranslateAnimation(cell.getLeft(), 0,
1850 cell.getTop(), 0));
1851 }
1852 set.setDuration(TAB_ANIMATION_DURATION);
1853 set.setInterpolator(new DecelerateInterpolator());
1854 return set;
1855 }
1856
1857 // Animate to the tab overview. currentIndex tells us which position to
1858 // animate to and newIndex is the position that should be selected after
1859 // the animation completes.
1860 // If remove is true, after the animation stops, a confirmation dialog will
1861 // be displayed to the user.
1862 private void animateToTabOverview(final int newIndex, final boolean remove,
1863 final AnimatingView view) {
1864 // Find the view in the ImageGrid allowing for the "New Tab" cell.
1865 int position = mTabControl.getTabIndex(view.mTab);
1866 if (!((ImageAdapter) mTabOverview.getAdapter()).maxedOut()) {
1867 position++;
1868 }
1869
1870 // Offset the tab position with the first visible position to get a
1871 // number between 0 and 3.
1872 position -= mTabOverview.getFirstVisiblePosition();
1873
1874 // Grab the view that we are going to animate to.
1875 final View v = mTabOverview.getChildAt(position);
1876
1877 final Animation.AnimationListener l =
1878 new Animation.AnimationListener() {
1879 public void onAnimationStart(Animation a) {
Patrick Scottd068f802009-06-22 11:46:06 -04001880 if (mTabOverview != null) {
1881 mTabOverview.requestFocus();
1882 // Clear the listener so we don't trigger a tab
1883 // selection.
1884 mTabOverview.setListener(null);
1885 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001886 }
1887 public void onAnimationRepeat(Animation a) {}
1888 public void onAnimationEnd(Animation a) {
1889 // We are no longer animating so decrement the count.
1890 mAnimationCount--;
1891 // Make the view GONE so that it will not draw between
1892 // now and when the Runnable is handled.
1893 view.setVisibility(View.GONE);
1894 // Post a runnable since we can't modify the view
1895 // hierarchy during this callback.
1896 mHandler.post(new Runnable() {
1897 public void run() {
1898 // Remove the AnimatingView.
1899 mContentView.removeView(view);
1900 if (mTabOverview != null) {
1901 // Make newIndex visible.
1902 mTabOverview.setCurrentIndex(newIndex);
1903 // Restore the listener.
1904 mTabOverview.setListener(mTabListener);
1905 // Change the menu to TAB_MENU if the
1906 // ImageGrid is interactive.
1907 if (mTabOverview.isLive()) {
1908 mMenuState = R.id.TAB_MENU;
1909 mTabOverview.requestFocus();
1910 }
1911 }
1912 // If a remove was requested, remove the tab.
1913 if (remove) {
1914 // During a remove, the current tab has
1915 // already changed. Remember the current one
1916 // here.
1917 final TabControl.Tab currentTab =
1918 mTabControl.getCurrentTab();
1919 // Remove the tab at newIndex from
1920 // TabControl and the tab overview.
1921 final TabControl.Tab tab =
1922 mTabControl.getTab(newIndex);
1923 mTabControl.removeTab(tab);
1924 // Restore the current tab.
1925 if (currentTab != tab) {
1926 mTabControl.setCurrentTab(currentTab);
1927 }
1928 if (mTabOverview != null) {
1929 mTabOverview.remove(newIndex);
1930 // Make the current tab visible.
1931 mTabOverview.setCurrentIndex(
1932 mTabControl.getCurrentIndex());
1933 }
1934 }
1935 }
1936 });
1937 }
1938 };
1939
1940 // Do an animation if there is a view to animate to.
1941 if (v != null) {
1942 // Create our animation
1943 final Animation anim = createTabAnimation(view, v, true);
1944 anim.setAnimationListener(l);
1945 // Start animating
1946 view.startAnimation(anim);
1947 } else {
1948 // If something goes wrong and we didn't find a view to animate to,
1949 // just do everything here.
1950 l.onAnimationStart(null);
1951 l.onAnimationEnd(null);
1952 }
1953 }
1954
1955 // Animate from the tab picker. The index supplied is the index to animate
1956 // from.
1957 private void animateFromTabOverview(final AnimatingView view,
1958 final boolean newTab, final Message msg) {
1959 // firstVisible is the first visible tab on the screen. This helps
1960 // to know which corner of the screen the selected tab is.
1961 int firstVisible = mTabOverview.getFirstVisiblePosition();
1962 // tabPosition is the 0-based index of of the tab being opened
1963 int tabPosition = mTabControl.getTabIndex(view.mTab);
1964 if (!((ImageAdapter) mTabOverview.getAdapter()).maxedOut()) {
1965 // Add one to make room for the "New Tab" cell.
1966 tabPosition++;
1967 }
1968 // If this is a new tab, animate from the "New Tab" cell.
1969 if (newTab) {
1970 tabPosition = 0;
1971 }
1972 // Location corresponds to the four corners of the screen.
1973 // A new tab or 0 is upper left, 0 for an old tab is upper
1974 // right, 1 is lower left, and 2 is lower right
1975 int location = tabPosition - firstVisible;
1976
1977 // Find the view at this location.
1978 final View v = mTabOverview.getChildAt(location);
1979
1980 // Wait until the animation completes to replace the AnimatingView.
1981 final Animation.AnimationListener l =
1982 new Animation.AnimationListener() {
1983 public void onAnimationStart(Animation a) {}
1984 public void onAnimationRepeat(Animation a) {}
1985 public void onAnimationEnd(Animation a) {
1986 mHandler.post(new Runnable() {
1987 public void run() {
1988 mContentView.removeView(view);
1989 // Dismiss the tab overview. If the cell at the
1990 // given location is null, set the fade
1991 // parameter to true.
1992 dismissTabOverview(v == null);
1993 TabControl.Tab t =
1994 mTabControl.getCurrentTab();
1995 mMenuState = R.id.MAIN_MENU;
1996 // Resume regular updates.
1997 t.getWebView().resumeTimers();
1998 // Dispatch the message after the animation
1999 // completes.
2000 if (msg != null) {
2001 msg.sendToTarget();
2002 }
2003 // The animation is done and the tab overview is
2004 // gone so allow key events and other animations
2005 // to begin.
2006 mAnimationCount--;
2007 // Reset all the title bar info.
2008 resetTitle();
2009 }
2010 });
2011 }
2012 };
2013
2014 if (v != null) {
2015 final Animation anim = createTabAnimation(view, v, false);
2016 // Set the listener and start animating
2017 anim.setAnimationListener(l);
2018 view.startAnimation(anim);
2019 // Make the view VISIBLE during the animation.
2020 view.setVisibility(View.VISIBLE);
2021 } else {
2022 // Go ahead and do all the cleanup.
2023 l.onAnimationEnd(null);
2024 }
2025 }
2026
2027 // Dismiss the tab overview applying a fade if needed.
2028 private void dismissTabOverview(final boolean fade) {
2029 if (fade) {
2030 AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f);
2031 anim.setDuration(500);
2032 anim.startNow();
2033 mTabOverview.startAnimation(anim);
2034 }
2035 // Just in case there was a problem with animating away from the tab
2036 // overview
2037 WebView current = mTabControl.getCurrentWebView();
2038 if (current != null) {
2039 current.setVisibility(View.VISIBLE);
2040 } else {
2041 Log.e(LOGTAG, "No current WebView in dismissTabOverview");
2042 }
2043 // Make the sub window container visible.
2044 if (mTabControl.getCurrentSubWindow() != null) {
2045 mTabControl.getCurrentTab().getSubWebViewContainer()
2046 .setVisibility(View.VISIBLE);
2047 }
2048 mContentView.removeView(mTabOverview);
Patrick Scott2ed6edb2009-04-22 10:07:45 -04002049 // Clear all the data for tab picker so next time it will be
2050 // recreated.
2051 mTabControl.wipeAllPickerData();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002052 mTabOverview.clear();
2053 mTabOverview = null;
2054 mTabListener = null;
2055 }
2056
Grace Klobac9181842009-04-14 08:53:22 -07002057 private TabControl.Tab openTab(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002058 if (mSettings.openInBackground()) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07002059 TabControl.Tab t = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002060 if (t != null) {
2061 t.getWebView().loadUrl(url);
2062 }
Grace Klobac9181842009-04-14 08:53:22 -07002063 return t;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002064 } else {
Grace Klobac9181842009-04-14 08:53:22 -07002065 return openTabAndShow(url, null, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002066 }
2067 }
2068
2069 private class Copy implements OnMenuItemClickListener {
2070 private CharSequence mText;
2071
2072 public boolean onMenuItemClick(MenuItem item) {
2073 copy(mText);
2074 return true;
2075 }
2076
2077 public Copy(CharSequence toCopy) {
2078 mText = toCopy;
2079 }
2080 }
2081
2082 private class Download implements OnMenuItemClickListener {
2083 private String mText;
2084
2085 public boolean onMenuItemClick(MenuItem item) {
2086 onDownloadStartNoStream(mText, null, null, null, -1);
2087 return true;
2088 }
2089
2090 public Download(String toDownload) {
2091 mText = toDownload;
2092 }
2093 }
2094
2095 private void copy(CharSequence text) {
2096 try {
2097 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
2098 if (clip != null) {
2099 clip.setClipboardText(text);
2100 }
2101 } catch (android.os.RemoteException e) {
2102 Log.e(LOGTAG, "Copy failed", e);
2103 }
2104 }
2105
2106 /**
2107 * Resets the browser title-view to whatever it must be (for example, if we
2108 * load a page from history).
2109 */
2110 private void resetTitle() {
2111 resetLockIcon();
2112 resetTitleIconAndProgress();
2113 }
2114
2115 /**
2116 * Resets the browser title-view to whatever it must be
2117 * (for example, if we had a loading error)
2118 * When we have a new page, we call resetTitle, when we
2119 * have to reset the titlebar to whatever it used to be
2120 * (for example, if the user chose to stop loading), we
2121 * call resetTitleAndRevertLockIcon.
2122 */
2123 /* package */ void resetTitleAndRevertLockIcon() {
2124 revertLockIcon();
2125 resetTitleIconAndProgress();
2126 }
2127
2128 /**
2129 * Reset the title, favicon, and progress.
2130 */
2131 private void resetTitleIconAndProgress() {
2132 WebView current = mTabControl.getCurrentWebView();
2133 if (current == null) {
2134 return;
2135 }
2136 resetTitleAndIcon(current);
2137 int progress = current.getProgress();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002138 mWebChromeClient.onProgressChanged(current, progress);
2139 }
2140
2141 // Reset the title and the icon based on the given item.
2142 private void resetTitleAndIcon(WebView view) {
2143 WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
2144 if (item != null) {
2145 setUrlTitle(item.getUrl(), item.getTitle());
2146 setFavicon(item.getFavicon());
2147 } else {
2148 setUrlTitle(null, null);
2149 setFavicon(null);
2150 }
2151 }
2152
2153 /**
2154 * Sets a title composed of the URL and the title string.
2155 * @param url The URL of the site being loaded.
2156 * @param title The title of the site being loaded.
2157 */
2158 private void setUrlTitle(String url, String title) {
2159 mUrl = url;
2160 mTitle = title;
2161
2162 // While the tab overview is animating or being shown, block changes
2163 // to the title.
2164 if (mAnimationCount == 0 && mTabOverview == null) {
Leon Scroggins81db3662009-06-04 17:45:11 -04002165 if (CUSTOM_BROWSER_BAR) {
2166 mTitleBar.setTitleAndUrl(title, url);
2167 } else {
2168 setTitle(buildUrlTitle(url, title));
2169 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002170 }
2171 }
2172
2173 /**
2174 * Builds and returns the page title, which is some
2175 * combination of the page URL and title.
2176 * @param url The URL of the site being loaded.
2177 * @param title The title of the site being loaded.
2178 * @return The page title.
2179 */
2180 private String buildUrlTitle(String url, String title) {
2181 String urlTitle = "";
2182
2183 if (url != null) {
2184 String titleUrl = buildTitleUrl(url);
2185
2186 if (title != null && 0 < title.length()) {
2187 if (titleUrl != null && 0 < titleUrl.length()) {
2188 urlTitle = titleUrl + ": " + title;
2189 } else {
2190 urlTitle = title;
2191 }
2192 } else {
2193 if (titleUrl != null) {
2194 urlTitle = titleUrl;
2195 }
2196 }
2197 }
2198
2199 return urlTitle;
2200 }
2201
2202 /**
2203 * @param url The URL to build a title version of the URL from.
2204 * @return The title version of the URL or null if fails.
2205 * The title version of the URL can be either the URL hostname,
2206 * or the hostname with an "https://" prefix (for secure URLs),
2207 * or an empty string if, for example, the URL in question is a
2208 * file:// URL with no hostname.
2209 */
Leon Scroggins32e14a62009-06-11 10:26:34 -04002210 /* package */ static String buildTitleUrl(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002211 String titleUrl = null;
2212
2213 if (url != null) {
2214 try {
2215 // parse the url string
2216 URL urlObj = new URL(url);
2217 if (urlObj != null) {
2218 titleUrl = "";
2219
2220 String protocol = urlObj.getProtocol();
2221 String host = urlObj.getHost();
2222
2223 if (host != null && 0 < host.length()) {
2224 titleUrl = host;
2225 if (protocol != null) {
2226 // if a secure site, add an "https://" prefix!
2227 if (protocol.equalsIgnoreCase("https")) {
2228 titleUrl = protocol + "://" + host;
2229 }
2230 }
2231 }
2232 }
2233 } catch (MalformedURLException e) {}
2234 }
2235
2236 return titleUrl;
2237 }
2238
2239 // Set the favicon in the title bar.
2240 private void setFavicon(Bitmap icon) {
2241 // While the tab overview is animating or being shown, block changes to
2242 // the favicon.
2243 if (mAnimationCount > 0 || mTabOverview != null) {
2244 return;
2245 }
Leon Scroggins81db3662009-06-04 17:45:11 -04002246 if (CUSTOM_BROWSER_BAR) {
2247 Drawable[] array = new Drawable[3];
2248 array[0] = new PaintDrawable(Color.BLACK);
2249 PaintDrawable p = new PaintDrawable(Color.WHITE);
2250 array[1] = p;
2251 if (icon == null) {
2252 array[2] = mGenericFavicon;
2253 } else {
2254 array[2] = new BitmapDrawable(icon);
2255 }
2256 LayerDrawable d = new LayerDrawable(array);
2257 d.setLayerInset(1, 1, 1, 1, 1);
2258 d.setLayerInset(2, 2, 2, 2, 2);
2259 mTitleBar.setFavicon(d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002260 } else {
Leon Scroggins81db3662009-06-04 17:45:11 -04002261 Drawable[] array = new Drawable[2];
2262 PaintDrawable p = new PaintDrawable(Color.WHITE);
2263 p.setCornerRadius(3f);
2264 array[0] = p;
2265 if (icon == null) {
2266 array[1] = mGenericFavicon;
2267 } else {
2268 array[1] = new BitmapDrawable(icon);
2269 }
2270 LayerDrawable d = new LayerDrawable(array);
2271 d.setLayerInset(1, 2, 2, 2, 2);
2272 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002273 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002274 }
2275
2276 /**
2277 * Saves the current lock-icon state before resetting
2278 * the lock icon. If we have an error, we may need to
2279 * roll back to the previous state.
2280 */
2281 private void saveLockIcon() {
2282 mPrevLockType = mLockIconType;
2283 }
2284
2285 /**
2286 * Reverts the lock-icon state to the last saved state,
2287 * for example, if we had an error, and need to cancel
2288 * the load.
2289 */
2290 private void revertLockIcon() {
2291 mLockIconType = mPrevLockType;
2292
Dave Bort31a6d1c2009-04-13 15:56:49 -07002293 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002294 Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
2295 " revert lock icon to " + mLockIconType);
2296 }
2297
2298 updateLockIconImage(mLockIconType);
2299 }
2300
2301 private void switchTabs(int indexFrom, int indexToShow, boolean remove) {
2302 int delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
2303 // Animate to the tab picker, remove the current tab, then
2304 // animate away from the tab picker to the parent WebView.
2305 tabPicker(false, indexFrom, remove);
2306 // Change to the parent tab
2307 final TabControl.Tab tab = mTabControl.getTab(indexToShow);
2308 if (tab != null) {
Patrick Scott95d601f2009-06-11 10:06:46 -04002309 sendAnimateFromOverview(tab, false, EMPTY_URL_DATA, delay, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002310 } else {
2311 // Increment this here so that no other animations can happen in
2312 // between the end of the tab picker transition and the beginning
2313 // of openTabAndShow. This has a matching decrement in the handler
2314 // of OPEN_TAB_AND_SHOW.
2315 mAnimationCount++;
2316 // Send a message to open a new tab.
2317 mHandler.sendMessageDelayed(
2318 mHandler.obtainMessage(OPEN_TAB_AND_SHOW,
Leon Scroggins64b80f32009-08-07 12:03:34 -04002319 null), delay);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002320 }
2321 }
2322
2323 private void goBackOnePageOrQuit() {
2324 TabControl.Tab current = mTabControl.getCurrentTab();
2325 if (current == null) {
2326 /*
2327 * Instead of finishing the activity, simply push this to the back
2328 * of the stack and let ActivityManager to choose the foreground
2329 * activity. As BrowserActivity is singleTask, it will be always the
2330 * root of the task. So we can use either true or false for
2331 * moveTaskToBack().
2332 */
2333 moveTaskToBack(true);
2334 }
2335 WebView w = current.getWebView();
2336 if (w.canGoBack()) {
2337 w.goBack();
2338 } else {
2339 // Check to see if we are closing a window that was created by
2340 // another window. If so, we switch back to that window.
2341 TabControl.Tab parent = current.getParentTab();
2342 if (parent != null) {
2343 switchTabs(mTabControl.getCurrentIndex(),
2344 mTabControl.getTabIndex(parent), true);
2345 } else {
2346 if (current.closeOnExit()) {
2347 if (mTabControl.getTabCount() == 1) {
2348 finish();
2349 return;
2350 }
Mike Reed7bfa63b2009-05-28 11:08:32 -04002351 // call pauseWebViewTimers() now, we won't be able to call
2352 // it in onPause() as the WebView won't be valid.
2353 pauseWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002354 removeTabFromContentView(current);
2355 mTabControl.removeTab(current);
2356 }
2357 /*
2358 * Instead of finishing the activity, simply push this to the back
2359 * of the stack and let ActivityManager to choose the foreground
2360 * activity. As BrowserActivity is singleTask, it will be always the
2361 * root of the task. So we can use either true or false for
2362 * moveTaskToBack().
2363 */
2364 moveTaskToBack(true);
2365 }
2366 }
2367 }
2368
2369 public KeyTracker.State onKeyTracker(int keyCode,
2370 KeyEvent event,
2371 KeyTracker.Stage stage,
2372 int duration) {
2373 // if onKeyTracker() is called after activity onStop()
2374 // because of accumulated key events,
2375 // we should ignore it as browser is not active any more.
2376 WebView topWindow = getTopWindow();
Andrei Popescuadc008d2009-06-26 14:11:30 +01002377 if (topWindow == null && mCustomView == null)
The Android Open Source Project0c908882009-03-03 19:32:16 -08002378 return KeyTracker.State.NOT_TRACKING;
2379
2380 if (keyCode == KeyEvent.KEYCODE_BACK) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01002381 // Check if a custom view is currently showing and, if it is, hide it.
2382 if (mCustomView != null) {
2383 mWebChromeClient.onHideCustomView();
2384 return KeyTracker.State.DONE_TRACKING;
2385 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002386 // During animations, block the back key so that other animations
2387 // are not triggered and so that we don't end up destroying all the
2388 // WebViews before finishing the animation.
2389 if (mAnimationCount > 0) {
2390 return KeyTracker.State.DONE_TRACKING;
2391 }
2392 if (stage == KeyTracker.Stage.LONG_REPEAT) {
2393 bookmarksOrHistoryPicker(true);
2394 return KeyTracker.State.DONE_TRACKING;
2395 } else if (stage == KeyTracker.Stage.UP) {
2396 // FIXME: Currently, we do not have a notion of the
2397 // history picker for the subwindow, but maybe we
2398 // should?
2399 WebView subwindow = mTabControl.getCurrentSubWindow();
2400 if (subwindow != null) {
2401 if (subwindow.canGoBack()) {
2402 subwindow.goBack();
2403 } else {
2404 dismissSubWindow(mTabControl.getCurrentTab());
2405 }
2406 } else {
2407 goBackOnePageOrQuit();
2408 }
2409 return KeyTracker.State.DONE_TRACKING;
2410 }
2411 return KeyTracker.State.KEEP_TRACKING;
2412 }
2413 return KeyTracker.State.NOT_TRACKING;
2414 }
2415
2416 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2417 if (keyCode == KeyEvent.KEYCODE_MENU) {
2418 mMenuIsDown = true;
Grace Kloba6ee9c492009-07-13 10:04:34 -07002419 } else if (mMenuIsDown) {
2420 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2421 // still down, we don't want to trigger the search. Pretend to
2422 // consume the key and do nothing.
2423 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002424 }
2425 boolean handled = mKeyTracker.doKeyDown(keyCode, event);
2426 if (!handled) {
2427 switch (keyCode) {
2428 case KeyEvent.KEYCODE_SPACE:
2429 if (event.isShiftPressed()) {
2430 getTopWindow().pageUp(false);
2431 } else {
2432 getTopWindow().pageDown(false);
2433 }
2434 handled = true;
2435 break;
2436
2437 default:
2438 break;
2439 }
2440 }
2441 return handled || super.onKeyDown(keyCode, event);
2442 }
2443
2444 @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2445 if (keyCode == KeyEvent.KEYCODE_MENU) {
2446 mMenuIsDown = false;
2447 }
2448 return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2449 }
2450
2451 private void stopLoading() {
2452 resetTitleAndRevertLockIcon();
2453 WebView w = getTopWindow();
2454 w.stopLoading();
2455 mWebViewClient.onPageFinished(w, w.getUrl());
2456
2457 cancelStopToast();
2458 mStopToast = Toast
2459 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2460 mStopToast.show();
2461 }
2462
2463 private void cancelStopToast() {
2464 if (mStopToast != null) {
2465 mStopToast.cancel();
2466 mStopToast = null;
2467 }
2468 }
2469
2470 // called by a non-UI thread to post the message
2471 public void postMessage(int what, int arg1, int arg2, Object obj) {
2472 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2473 }
2474
2475 // public message ids
2476 public final static int LOAD_URL = 1001;
2477 public final static int STOP_LOAD = 1002;
2478
2479 // Message Ids
2480 private static final int FOCUS_NODE_HREF = 102;
2481 private static final int CANCEL_CREDS_REQUEST = 103;
2482 private static final int ANIMATE_FROM_OVERVIEW = 104;
2483 private static final int ANIMATE_TO_OVERVIEW = 105;
2484 private static final int OPEN_TAB_AND_SHOW = 106;
Grace Kloba92c18a52009-07-31 23:48:32 -07002485 private static final int RELEASE_WAKELOCK = 107;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002486
2487 // Private handler for handling javascript and saving passwords
2488 private Handler mHandler = new Handler() {
2489
2490 public void handleMessage(Message msg) {
2491 switch (msg.what) {
2492 case ANIMATE_FROM_OVERVIEW:
2493 final HashMap map = (HashMap) msg.obj;
2494 animateFromTabOverview((AnimatingView) map.get("view"),
2495 msg.arg1 == 1, (Message) map.get("msg"));
2496 break;
2497
2498 case ANIMATE_TO_OVERVIEW:
2499 animateToTabOverview(msg.arg1, msg.arg2 == 1,
2500 (AnimatingView) msg.obj);
2501 break;
2502
2503 case OPEN_TAB_AND_SHOW:
2504 // Decrement mAnimationCount before openTabAndShow because
2505 // the method relies on the value being 0 to start the next
2506 // animation.
2507 mAnimationCount--;
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07002508 openTabAndShow((String) msg.obj, null, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002509 break;
2510
2511 case FOCUS_NODE_HREF:
2512 String url = (String) msg.getData().get("url");
2513 if (url == null || url.length() == 0) {
2514 break;
2515 }
2516 HashMap focusNodeMap = (HashMap) msg.obj;
2517 WebView view = (WebView) focusNodeMap.get("webview");
2518 // Only apply the action if the top window did not change.
2519 if (getTopWindow() != view) {
2520 break;
2521 }
2522 switch (msg.arg1) {
2523 case R.id.open_context_menu_id:
2524 case R.id.view_image_context_menu_id:
2525 loadURL(getTopWindow(), url);
2526 break;
2527 case R.id.open_newtab_context_menu_id:
Grace Klobac9181842009-04-14 08:53:22 -07002528 final TabControl.Tab parent = mTabControl
2529 .getCurrentTab();
2530 final TabControl.Tab newTab = openTab(url);
2531 if (newTab != parent) {
2532 parent.addChildTab(newTab);
2533 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002534 break;
2535 case R.id.bookmark_context_menu_id:
2536 Intent intent = new Intent(BrowserActivity.this,
2537 AddBookmarkPage.class);
2538 intent.putExtra("url", url);
2539 startActivity(intent);
2540 break;
2541 case R.id.share_link_context_menu_id:
2542 Browser.sendString(BrowserActivity.this, url);
2543 break;
2544 case R.id.copy_link_context_menu_id:
2545 copy(url);
2546 break;
2547 case R.id.save_link_context_menu_id:
2548 case R.id.download_context_menu_id:
2549 onDownloadStartNoStream(url, null, null, null, -1);
2550 break;
2551 }
2552 break;
2553
2554 case LOAD_URL:
2555 loadURL(getTopWindow(), (String) msg.obj);
2556 break;
2557
2558 case STOP_LOAD:
2559 stopLoading();
2560 break;
2561
2562 case CANCEL_CREDS_REQUEST:
2563 resumeAfterCredentials();
2564 break;
2565
The Android Open Source Project0c908882009-03-03 19:32:16 -08002566 case RELEASE_WAKELOCK:
2567 if (mWakeLock.isHeld()) {
2568 mWakeLock.release();
2569 }
2570 break;
2571 }
2572 }
2573 };
2574
Leon Scroggins89c6d362009-07-15 16:54:37 -04002575 private void updateScreenshot(WebView view) {
2576 // If this is a bookmarked site, add a screenshot to the database.
2577 // FIXME: When should we update? Every time?
2578 // FIXME: Would like to make sure there is actually something to
2579 // draw, but the API for that (WebViewCore.pictureReady()) is not
2580 // currently accessible here.
Patrick Scott3918d442009-08-04 13:22:29 -04002581 ContentResolver cr = getContentResolver();
2582 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04002583 cr, view.getOriginalUrl(), view.getUrl(), false);
Patrick Scott3918d442009-08-04 13:22:29 -04002584 if (c != null) {
Leon Scroggins89c6d362009-07-15 16:54:37 -04002585 boolean succeed = c.moveToFirst();
2586 ContentValues values = null;
2587 while (succeed) {
2588 if (values == null) {
2589 final ByteArrayOutputStream os
2590 = new ByteArrayOutputStream();
2591 Picture thumbnail = view.capturePicture();
2592 // Keep width and height in sync with BrowserBookmarksPage
2593 // and bookmark_thumb
2594 Bitmap bm = Bitmap.createBitmap(100, 80,
2595 Bitmap.Config.ARGB_4444);
2596 Canvas canvas = new Canvas(bm);
2597 // May need to tweak these values to determine what is the
2598 // best scale factor
2599 canvas.scale(.5f, .5f);
2600 thumbnail.draw(canvas);
2601 bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2602 values = new ContentValues();
2603 values.put(Browser.BookmarkColumns.THUMBNAIL,
2604 os.toByteArray());
2605 }
2606 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2607 c.getInt(0)), values, null, null);
2608 succeed = c.moveToNext();
2609 }
2610 c.close();
2611 }
2612 }
2613
The Android Open Source Project0c908882009-03-03 19:32:16 -08002614 // -------------------------------------------------------------------------
2615 // WebViewClient implementation.
2616 //-------------------------------------------------------------------------
2617
2618 // Use in overrideUrlLoading
2619 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2620 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2621 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2622 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2623
2624 /* package */ WebViewClient getWebViewClient() {
2625 return mWebViewClient;
2626 }
2627
Patrick Scott3918d442009-08-04 13:22:29 -04002628 private void updateIcon(WebView view, Bitmap icon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002629 if (icon != null) {
2630 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
Patrick Scott3918d442009-08-04 13:22:29 -04002631 view, icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002632 }
2633 setFavicon(icon);
2634 }
2635
2636 private final WebViewClient mWebViewClient = new WebViewClient() {
2637 @Override
2638 public void onPageStarted(WebView view, String url, Bitmap favicon) {
2639 resetLockIcon(url);
2640 setUrlTitle(url, null);
Ben Murdochbff2d602009-07-01 20:19:05 +01002641
2642 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2643 if (errorConsole != null) {
2644 errorConsole.clearErrorMessages();
2645 if (mShouldShowErrorConsole) {
2646 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2647 }
2648 }
2649
The Android Open Source Project0c908882009-03-03 19:32:16 -08002650 // Call updateIcon instead of setFavicon so the bookmark
2651 // database can be updated.
Patrick Scott3918d442009-08-04 13:22:29 -04002652 updateIcon(view, favicon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002653
Grace Kloba4d7880f2009-08-12 09:35:42 -07002654 if (mSettings.isTracing()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002655 String host;
2656 try {
2657 WebAddress uri = new WebAddress(url);
2658 host = uri.mHost;
2659 } catch (android.net.ParseException ex) {
Grace Kloba4d7880f2009-08-12 09:35:42 -07002660 host = "browser";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002661 }
2662 host = host.replace('.', '_');
Grace Kloba4d7880f2009-08-12 09:35:42 -07002663 host += ".trace";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002664 mInTrace = true;
Grace Kloba4d7880f2009-08-12 09:35:42 -07002665 Debug.startMethodTracing(host, 20 * 1024 * 1024);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002666 }
2667
2668 // Performance probe
2669 if (false) {
2670 mStart = SystemClock.uptimeMillis();
2671 mProcessStart = Process.getElapsedCpuTime();
2672 long[] sysCpu = new long[7];
2673 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2674 sysCpu, null)) {
2675 mUserStart = sysCpu[0] + sysCpu[1];
2676 mSystemStart = sysCpu[2];
2677 mIdleStart = sysCpu[3];
2678 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2679 }
2680 mUiStart = SystemClock.currentThreadTimeMillis();
2681 }
2682
2683 if (!mPageStarted) {
2684 mPageStarted = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002685 // if onResume() has been called, resumeWebViewTimers() does
2686 // nothing.
2687 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002688 }
2689
2690 // reset sync timer to avoid sync starts during loading a page
2691 CookieSyncManager.getInstance().resetSync();
2692
2693 mInLoad = true;
2694 updateInLoadMenuItems();
2695 if (!mIsNetworkUp) {
2696 if ( mAlertDialog == null) {
2697 mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
2698 .setTitle(R.string.loadSuspendedTitle)
2699 .setMessage(R.string.loadSuspended)
2700 .setPositiveButton(R.string.ok, null)
2701 .show();
2702 }
2703 if (view != null) {
2704 view.setNetworkAvailable(false);
2705 }
2706 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002707 }
2708
2709 @Override
2710 public void onPageFinished(WebView view, String url) {
2711 // Reset the title and icon in case we stopped a provisional
2712 // load.
2713 resetTitleAndIcon(view);
2714
2715 // Update the lock icon image only once we are done loading
2716 updateLockIconImage(mLockIconType);
Leon Scroggins89c6d362009-07-15 16:54:37 -04002717 updateScreenshot(view);
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -04002718
The Android Open Source Project0c908882009-03-03 19:32:16 -08002719 // Performance probe
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002720 if (false) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002721 long[] sysCpu = new long[7];
2722 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2723 sysCpu, null)) {
2724 String uiInfo = "UI thread used "
2725 + (SystemClock.currentThreadTimeMillis() - mUiStart)
2726 + " ms";
Dave Bort31a6d1c2009-04-13 15:56:49 -07002727 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002728 Log.d(LOGTAG, uiInfo);
2729 }
2730 //The string that gets written to the log
2731 String performanceString = "It took total "
2732 + (SystemClock.uptimeMillis() - mStart)
2733 + " ms clock time to load the page."
2734 + "\nbrowser process used "
2735 + (Process.getElapsedCpuTime() - mProcessStart)
2736 + " ms, user processes used "
2737 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2738 + " ms, kernel used "
2739 + (sysCpu[2] - mSystemStart) * 10
2740 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2741 + " ms and irq took "
2742 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2743 * 10 + " ms, " + uiInfo;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002744 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002745 Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2746 }
2747 if (url != null) {
2748 // strip the url to maintain consistency
2749 String newUrl = new String(url);
2750 if (newUrl.startsWith("http://www.")) {
2751 newUrl = newUrl.substring(11);
2752 } else if (newUrl.startsWith("http://")) {
2753 newUrl = newUrl.substring(7);
2754 } else if (newUrl.startsWith("https://www.")) {
2755 newUrl = newUrl.substring(12);
2756 } else if (newUrl.startsWith("https://")) {
2757 newUrl = newUrl.substring(8);
2758 }
Dave Bort31a6d1c2009-04-13 15:56:49 -07002759 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002760 Log.d(LOGTAG, newUrl + " loaded");
2761 }
2762 /*
2763 if (sWhiteList.contains(newUrl)) {
2764 // The string that gets pushed to the statistcs
2765 // service
2766 performanceString = performanceString
2767 + "\nWebpage: "
2768 + newUrl
2769 + "\nCarrier: "
2770 + android.os.SystemProperties
2771 .get("gsm.sim.operator.alpha");
2772 if (mWebView != null
2773 && mWebView.getContext() != null
2774 && mWebView.getContext().getSystemService(
2775 Context.CONNECTIVITY_SERVICE) != null) {
2776 ConnectivityManager cManager =
2777 (ConnectivityManager) mWebView
2778 .getContext().getSystemService(
2779 Context.CONNECTIVITY_SERVICE);
2780 NetworkInfo nInfo = cManager
2781 .getActiveNetworkInfo();
2782 if (nInfo != null) {
2783 performanceString = performanceString
2784 + "\nNetwork Type: "
2785 + nInfo.getType().toString();
2786 }
2787 }
2788 Checkin.logEvent(mResolver,
2789 Checkin.Events.Tag.WEBPAGE_LOAD,
2790 performanceString);
2791 Log.w(LOGTAG, "pushed to the statistics service");
2792 }
2793 */
2794 }
2795 }
2796 }
2797
2798 if (mInTrace) {
2799 mInTrace = false;
2800 Debug.stopMethodTracing();
2801 }
2802
2803 if (mPageStarted) {
2804 mPageStarted = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002805 // pauseWebViewTimers() will do nothing and return false if
2806 // onPause() is not called yet.
2807 if (pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002808 if (mWakeLock.isHeld()) {
2809 mHandler.removeMessages(RELEASE_WAKELOCK);
2810 mWakeLock.release();
2811 }
2812 }
2813 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002814 }
2815
2816 // return true if want to hijack the url to let another app to handle it
2817 @Override
2818 public boolean shouldOverrideUrlLoading(WebView view, String url) {
2819 if (url.startsWith(SCHEME_WTAI)) {
2820 // wtai://wp/mc;number
2821 // number=string(phone-number)
2822 if (url.startsWith(SCHEME_WTAI_MC)) {
2823 Intent intent = new Intent(Intent.ACTION_VIEW,
2824 Uri.parse(WebView.SCHEME_TEL +
2825 url.substring(SCHEME_WTAI_MC.length())));
2826 startActivity(intent);
2827 return true;
2828 }
2829 // wtai://wp/sd;dtmf
2830 // dtmf=string(dialstring)
2831 if (url.startsWith(SCHEME_WTAI_SD)) {
2832 // TODO
2833 // only send when there is active voice connection
2834 return false;
2835 }
2836 // wtai://wp/ap;number;name
2837 // number=string(phone-number)
2838 // name=string
2839 if (url.startsWith(SCHEME_WTAI_AP)) {
2840 // TODO
2841 return false;
2842 }
2843 }
2844
Dianne Hackborn99189432009-06-17 18:06:18 -07002845 // The "about:" schemes are internal to the browser; don't
2846 // want these to be dispatched to other apps.
2847 if (url.startsWith("about:")) {
2848 return false;
2849 }
Ben Murdochbff2d602009-07-01 20:19:05 +01002850
Dianne Hackborn99189432009-06-17 18:06:18 -07002851 Intent intent;
Ben Murdochbff2d602009-07-01 20:19:05 +01002852
Dianne Hackborn99189432009-06-17 18:06:18 -07002853 // perform generic parsing of the URI to turn it into an Intent.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002854 try {
Dianne Hackborn99189432009-06-17 18:06:18 -07002855 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2856 } catch (URISyntaxException ex) {
2857 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
The Android Open Source Project0c908882009-03-03 19:32:16 -08002858 return false;
2859 }
2860
Grace Kloba5b078b52009-06-24 20:23:41 -07002861 // check whether the intent can be resolved. If not, we will see
2862 // whether we can download it from the Market.
2863 if (getPackageManager().resolveActivity(intent, 0) == null) {
2864 String packagename = intent.getPackage();
2865 if (packagename != null) {
2866 intent = new Intent(Intent.ACTION_VIEW, Uri
2867 .parse("market://search?q=pname:" + packagename));
2868 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2869 startActivity(intent);
2870 return true;
2871 } else {
2872 return false;
2873 }
2874 }
2875
Dianne Hackborn99189432009-06-17 18:06:18 -07002876 // sanitize the Intent, ensuring web pages can not bypass browser
2877 // security (only access to BROWSABLE activities).
The Android Open Source Project0c908882009-03-03 19:32:16 -08002878 intent.addCategory(Intent.CATEGORY_BROWSABLE);
Dianne Hackborn99189432009-06-17 18:06:18 -07002879 intent.setComponent(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002880 try {
2881 if (startActivityIfNeeded(intent, -1)) {
2882 return true;
2883 }
2884 } catch (ActivityNotFoundException ex) {
2885 // ignore the error. If no application can handle the URL,
2886 // eg about:blank, assume the browser can handle it.
2887 }
2888
2889 if (mMenuIsDown) {
2890 openTab(url);
2891 closeOptionsMenu();
2892 return true;
2893 }
2894
2895 return false;
2896 }
2897
2898 /**
2899 * Updates the lock icon. This method is called when we discover another
2900 * resource to be loaded for this page (for example, javascript). While
2901 * we update the icon type, we do not update the lock icon itself until
2902 * we are done loading, it is slightly more secure this way.
2903 */
2904 @Override
2905 public void onLoadResource(WebView view, String url) {
2906 if (url != null && url.length() > 0) {
2907 // It is only if the page claims to be secure
2908 // that we may have to update the lock:
2909 if (mLockIconType == LOCK_ICON_SECURE) {
2910 // If NOT a 'safe' url, change the lock to mixed content!
2911 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2912 mLockIconType = LOCK_ICON_MIXED;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002913 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002914 Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2915 " updated lock icon to " + mLockIconType + " due to " + url);
2916 }
2917 }
2918 }
2919 }
2920 }
2921
2922 /**
2923 * Show the dialog, asking the user if they would like to continue after
2924 * an excessive number of HTTP redirects.
2925 */
2926 @Override
2927 public void onTooManyRedirects(WebView view, final Message cancelMsg,
2928 final Message continueMsg) {
2929 new AlertDialog.Builder(BrowserActivity.this)
2930 .setTitle(R.string.browserFrameRedirect)
2931 .setMessage(R.string.browserFrame307Post)
2932 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2933 public void onClick(DialogInterface dialog, int which) {
2934 continueMsg.sendToTarget();
2935 }})
2936 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2937 public void onClick(DialogInterface dialog, int which) {
2938 cancelMsg.sendToTarget();
2939 }})
2940 .setOnCancelListener(new OnCancelListener() {
2941 public void onCancel(DialogInterface dialog) {
2942 cancelMsg.sendToTarget();
2943 }})
2944 .show();
2945 }
2946
Patrick Scott37911c72009-03-24 18:02:58 -07002947 // Container class for the next error dialog that needs to be
2948 // displayed.
2949 class ErrorDialog {
2950 public final int mTitle;
2951 public final String mDescription;
2952 public final int mError;
2953 ErrorDialog(int title, String desc, int error) {
2954 mTitle = title;
2955 mDescription = desc;
2956 mError = error;
2957 }
2958 };
2959
2960 private void processNextError() {
2961 if (mQueuedErrors == null) {
2962 return;
2963 }
2964 // The first one is currently displayed so just remove it.
2965 mQueuedErrors.removeFirst();
2966 if (mQueuedErrors.size() == 0) {
2967 mQueuedErrors = null;
2968 return;
2969 }
2970 showError(mQueuedErrors.getFirst());
2971 }
2972
2973 private DialogInterface.OnDismissListener mDialogListener =
2974 new DialogInterface.OnDismissListener() {
2975 public void onDismiss(DialogInterface d) {
2976 processNextError();
2977 }
2978 };
2979 private LinkedList<ErrorDialog> mQueuedErrors;
2980
2981 private void queueError(int err, String desc) {
2982 if (mQueuedErrors == null) {
2983 mQueuedErrors = new LinkedList<ErrorDialog>();
2984 }
2985 for (ErrorDialog d : mQueuedErrors) {
2986 if (d.mError == err) {
2987 // Already saw a similar error, ignore the new one.
2988 return;
2989 }
2990 }
2991 ErrorDialog errDialog = new ErrorDialog(
2992 err == EventHandler.FILE_NOT_FOUND_ERROR ?
2993 R.string.browserFrameFileErrorLabel :
2994 R.string.browserFrameNetworkErrorLabel,
2995 desc, err);
2996 mQueuedErrors.addLast(errDialog);
2997
2998 // Show the dialog now if the queue was empty.
2999 if (mQueuedErrors.size() == 1) {
3000 showError(errDialog);
3001 }
3002 }
3003
3004 private void showError(ErrorDialog errDialog) {
3005 AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
3006 .setTitle(errDialog.mTitle)
3007 .setMessage(errDialog.mDescription)
3008 .setPositiveButton(R.string.ok, null)
3009 .create();
3010 d.setOnDismissListener(mDialogListener);
3011 d.show();
3012 }
3013
The Android Open Source Project0c908882009-03-03 19:32:16 -08003014 /**
3015 * Show a dialog informing the user of the network error reported by
3016 * WebCore.
3017 */
3018 @Override
3019 public void onReceivedError(WebView view, int errorCode,
3020 String description, String failingUrl) {
3021 if (errorCode != EventHandler.ERROR_LOOKUP &&
3022 errorCode != EventHandler.ERROR_CONNECT &&
3023 errorCode != EventHandler.ERROR_BAD_URL &&
3024 errorCode != EventHandler.ERROR_UNSUPPORTED_SCHEME &&
3025 errorCode != EventHandler.FILE_ERROR) {
Patrick Scott37911c72009-03-24 18:02:58 -07003026 queueError(errorCode, description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003027 }
Patrick Scott37911c72009-03-24 18:02:58 -07003028 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
3029 + " " + description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003030
3031 // We need to reset the title after an error.
3032 resetTitleAndRevertLockIcon();
3033 }
3034
3035 /**
3036 * Check with the user if it is ok to resend POST data as the page they
3037 * are trying to navigate to is the result of a POST.
3038 */
3039 @Override
3040 public void onFormResubmission(WebView view, final Message dontResend,
3041 final Message resend) {
3042 new AlertDialog.Builder(BrowserActivity.this)
3043 .setTitle(R.string.browserFrameFormResubmitLabel)
3044 .setMessage(R.string.browserFrameFormResubmitMessage)
3045 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
3046 public void onClick(DialogInterface dialog, int which) {
3047 resend.sendToTarget();
3048 }})
3049 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
3050 public void onClick(DialogInterface dialog, int which) {
3051 dontResend.sendToTarget();
3052 }})
3053 .setOnCancelListener(new OnCancelListener() {
3054 public void onCancel(DialogInterface dialog) {
3055 dontResend.sendToTarget();
3056 }})
3057 .show();
3058 }
3059
3060 /**
3061 * Insert the url into the visited history database.
3062 * @param url The url to be inserted.
3063 * @param isReload True if this url is being reloaded.
3064 * FIXME: Not sure what to do when reloading the page.
3065 */
3066 @Override
3067 public void doUpdateVisitedHistory(WebView view, String url,
3068 boolean isReload) {
3069 if (url.regionMatches(true, 0, "about:", 0, 6)) {
3070 return;
3071 }
3072 Browser.updateVisitedHistory(mResolver, url, true);
3073 WebIconDatabase.getInstance().retainIconForPageUrl(url);
3074 }
3075
3076 /**
3077 * Displays SSL error(s) dialog to the user.
3078 */
3079 @Override
3080 public void onReceivedSslError(
3081 final WebView view, final SslErrorHandler handler, final SslError error) {
3082
3083 if (mSettings.showSecurityWarnings()) {
3084 final LayoutInflater factory =
3085 LayoutInflater.from(BrowserActivity.this);
3086 final View warningsView =
3087 factory.inflate(R.layout.ssl_warnings, null);
3088 final LinearLayout placeholder =
3089 (LinearLayout)warningsView.findViewById(R.id.placeholder);
3090
3091 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3092 LinearLayout ll = (LinearLayout)factory
3093 .inflate(R.layout.ssl_warning, null);
3094 ((TextView)ll.findViewById(R.id.warning))
3095 .setText(R.string.ssl_untrusted);
3096 placeholder.addView(ll);
3097 }
3098
3099 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3100 LinearLayout ll = (LinearLayout)factory
3101 .inflate(R.layout.ssl_warning, null);
3102 ((TextView)ll.findViewById(R.id.warning))
3103 .setText(R.string.ssl_mismatch);
3104 placeholder.addView(ll);
3105 }
3106
3107 if (error.hasError(SslError.SSL_EXPIRED)) {
3108 LinearLayout ll = (LinearLayout)factory
3109 .inflate(R.layout.ssl_warning, null);
3110 ((TextView)ll.findViewById(R.id.warning))
3111 .setText(R.string.ssl_expired);
3112 placeholder.addView(ll);
3113 }
3114
3115 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3116 LinearLayout ll = (LinearLayout)factory
3117 .inflate(R.layout.ssl_warning, null);
3118 ((TextView)ll.findViewById(R.id.warning))
3119 .setText(R.string.ssl_not_yet_valid);
3120 placeholder.addView(ll);
3121 }
3122
3123 new AlertDialog.Builder(BrowserActivity.this)
3124 .setTitle(R.string.security_warning)
3125 .setIcon(android.R.drawable.ic_dialog_alert)
3126 .setView(warningsView)
3127 .setPositiveButton(R.string.ssl_continue,
3128 new DialogInterface.OnClickListener() {
3129 public void onClick(DialogInterface dialog, int whichButton) {
3130 handler.proceed();
3131 }
3132 })
3133 .setNeutralButton(R.string.view_certificate,
3134 new DialogInterface.OnClickListener() {
3135 public void onClick(DialogInterface dialog, int whichButton) {
3136 showSSLCertificateOnError(view, handler, error);
3137 }
3138 })
3139 .setNegativeButton(R.string.cancel,
3140 new DialogInterface.OnClickListener() {
3141 public void onClick(DialogInterface dialog, int whichButton) {
3142 handler.cancel();
3143 BrowserActivity.this.resetTitleAndRevertLockIcon();
3144 }
3145 })
3146 .setOnCancelListener(
3147 new DialogInterface.OnCancelListener() {
3148 public void onCancel(DialogInterface dialog) {
3149 handler.cancel();
3150 BrowserActivity.this.resetTitleAndRevertLockIcon();
3151 }
3152 })
3153 .show();
3154 } else {
3155 handler.proceed();
3156 }
3157 }
3158
3159 /**
3160 * Handles an HTTP authentication request.
3161 *
3162 * @param handler The authentication handler
3163 * @param host The host
3164 * @param realm The realm
3165 */
3166 @Override
3167 public void onReceivedHttpAuthRequest(WebView view,
3168 final HttpAuthHandler handler, final String host, final String realm) {
3169 String username = null;
3170 String password = null;
3171
3172 boolean reuseHttpAuthUsernamePassword =
3173 handler.useHttpAuthUsernamePassword();
3174
3175 if (reuseHttpAuthUsernamePassword &&
3176 (mTabControl.getCurrentWebView() != null)) {
3177 String[] credentials =
3178 mTabControl.getCurrentWebView()
3179 .getHttpAuthUsernamePassword(host, realm);
3180 if (credentials != null && credentials.length == 2) {
3181 username = credentials[0];
3182 password = credentials[1];
3183 }
3184 }
3185
3186 if (username != null && password != null) {
3187 handler.proceed(username, password);
3188 } else {
3189 showHttpAuthentication(handler, host, realm, null, null, null, 0);
3190 }
3191 }
3192
3193 @Override
3194 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
3195 if (mMenuIsDown) {
3196 // only check shortcut key when MENU is held
3197 return getWindow().isShortcutKey(event.getKeyCode(), event);
3198 } else {
3199 return false;
3200 }
3201 }
3202
3203 @Override
3204 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
3205 if (view != mTabControl.getCurrentTopWebView()) {
3206 return;
3207 }
3208 if (event.isDown()) {
3209 BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
3210 } else {
3211 BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
3212 }
3213 }
3214 };
3215
3216 //--------------------------------------------------------------------------
3217 // WebChromeClient implementation
3218 //--------------------------------------------------------------------------
3219
3220 /* package */ WebChromeClient getWebChromeClient() {
3221 return mWebChromeClient;
3222 }
3223
3224 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
3225 // Helper method to create a new tab or sub window.
3226 private void createWindow(final boolean dialog, final Message msg) {
3227 if (dialog) {
3228 mTabControl.createSubWindow();
3229 final TabControl.Tab t = mTabControl.getCurrentTab();
3230 attachSubWindow(t);
3231 WebView.WebViewTransport transport =
3232 (WebView.WebViewTransport) msg.obj;
3233 transport.setWebView(t.getSubWebView());
3234 msg.sendToTarget();
3235 } else {
3236 final TabControl.Tab parent = mTabControl.getCurrentTab();
3237 // openTabAndShow will dispatch the message after creating the
3238 // new WebView. This will prevent another request from coming
3239 // in during the animation.
Patrick Scott1536e732009-06-11 14:50:01 -04003240 final TabControl.Tab newTab =
3241 openTabAndShow(EMPTY_URL_DATA, msg, false, null);
Grace Klobac9181842009-04-14 08:53:22 -07003242 if (newTab != parent) {
3243 parent.addChildTab(newTab);
3244 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003245 WebView.WebViewTransport transport =
3246 (WebView.WebViewTransport) msg.obj;
3247 transport.setWebView(mTabControl.getCurrentWebView());
3248 }
3249 }
3250
3251 @Override
3252 public boolean onCreateWindow(WebView view, final boolean dialog,
3253 final boolean userGesture, final Message resultMsg) {
3254 // Ignore these requests during tab animations or if the tab
3255 // overview is showing.
3256 if (mAnimationCount > 0 || mTabOverview != null) {
3257 return false;
3258 }
3259 // Short-circuit if we can't create any more tabs or sub windows.
3260 if (dialog && mTabControl.getCurrentSubWindow() != null) {
3261 new AlertDialog.Builder(BrowserActivity.this)
3262 .setTitle(R.string.too_many_subwindows_dialog_title)
3263 .setIcon(android.R.drawable.ic_dialog_alert)
3264 .setMessage(R.string.too_many_subwindows_dialog_message)
3265 .setPositiveButton(R.string.ok, null)
3266 .show();
3267 return false;
3268 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
3269 new AlertDialog.Builder(BrowserActivity.this)
3270 .setTitle(R.string.too_many_windows_dialog_title)
3271 .setIcon(android.R.drawable.ic_dialog_alert)
3272 .setMessage(R.string.too_many_windows_dialog_message)
3273 .setPositiveButton(R.string.ok, null)
3274 .show();
3275 return false;
3276 }
3277
3278 // Short-circuit if this was a user gesture.
3279 if (userGesture) {
3280 // createWindow will call openTabAndShow for new Windows and
3281 // that will call tabPicker which will increment
3282 // mAnimationCount.
3283 createWindow(dialog, resultMsg);
3284 return true;
3285 }
3286
3287 // Allow the popup and create the appropriate window.
3288 final AlertDialog.OnClickListener allowListener =
3289 new AlertDialog.OnClickListener() {
3290 public void onClick(DialogInterface d,
3291 int which) {
3292 // Same comment as above for setting
3293 // mAnimationCount.
3294 createWindow(dialog, resultMsg);
3295 // Since we incremented mAnimationCount while the
3296 // dialog was up, we have to decrement it here.
3297 mAnimationCount--;
3298 }
3299 };
3300
3301 // Block the popup by returning a null WebView.
3302 final AlertDialog.OnClickListener blockListener =
3303 new AlertDialog.OnClickListener() {
3304 public void onClick(DialogInterface d, int which) {
3305 resultMsg.sendToTarget();
3306 // We are not going to trigger an animation so
3307 // unblock keys and animation requests.
3308 mAnimationCount--;
3309 }
3310 };
3311
3312 // Build a confirmation dialog to display to the user.
3313 final AlertDialog d =
3314 new AlertDialog.Builder(BrowserActivity.this)
3315 .setTitle(R.string.attention)
3316 .setIcon(android.R.drawable.ic_dialog_alert)
3317 .setMessage(R.string.popup_window_attempt)
3318 .setPositiveButton(R.string.allow, allowListener)
3319 .setNegativeButton(R.string.block, blockListener)
3320 .setCancelable(false)
3321 .create();
3322
3323 // Show the confirmation dialog.
3324 d.show();
3325 // We want to increment mAnimationCount here to prevent a
3326 // potential race condition. If the user allows a pop-up from a
3327 // site and that pop-up then triggers another pop-up, it is
3328 // possible to get the BACK key between here and when the dialog
3329 // appears.
3330 mAnimationCount++;
3331 return true;
3332 }
3333
3334 @Override
3335 public void onCloseWindow(WebView window) {
3336 final int currentIndex = mTabControl.getCurrentIndex();
3337 final TabControl.Tab parent =
3338 mTabControl.getCurrentTab().getParentTab();
3339 if (parent != null) {
3340 // JavaScript can only close popup window.
3341 switchTabs(currentIndex, mTabControl.getTabIndex(parent), true);
3342 }
3343 }
3344
3345 @Override
3346 public void onProgressChanged(WebView view, int newProgress) {
3347 // Block progress updates to the title bar while the tab overview
3348 // is animating or being displayed.
3349 if (mAnimationCount == 0 && mTabOverview == null) {
Leon Scroggins81db3662009-06-04 17:45:11 -04003350 if (CUSTOM_BROWSER_BAR) {
3351 mTitleBar.setProgress(newProgress);
3352 } else {
3353 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
3354 newProgress * 100);
3355
3356 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003357 }
3358
3359 if (newProgress == 100) {
3360 // onProgressChanged() is called for sub-frame too while
3361 // onPageFinished() is only called for the main frame. sync
3362 // cookie and cache promptly here.
3363 CookieSyncManager.getInstance().sync();
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07003364 if (mInLoad) {
3365 mInLoad = false;
3366 updateInLoadMenuItems();
3367 }
3368 } else {
3369 // onPageFinished may have already been called but a subframe
3370 // is still loading and updating the progress. Reset mInLoad
3371 // and update the menu items.
3372 if (!mInLoad) {
3373 mInLoad = true;
3374 updateInLoadMenuItems();
3375 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003376 }
3377 }
3378
3379 @Override
3380 public void onReceivedTitle(WebView view, String title) {
Patrick Scott598c9cc2009-06-04 11:10:38 -04003381 String url = view.getUrl();
The Android Open Source Project0c908882009-03-03 19:32:16 -08003382
3383 // here, if url is null, we want to reset the title
3384 setUrlTitle(url, title);
3385
3386 if (url == null ||
3387 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
3388 return;
3389 }
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003390 // See if we can find the current url in our history database and
3391 // add the new title to it.
The Android Open Source Project0c908882009-03-03 19:32:16 -08003392 if (url.startsWith("http://www.")) {
3393 url = url.substring(11);
3394 } else if (url.startsWith("http://")) {
3395 url = url.substring(4);
3396 }
3397 try {
3398 url = "%" + url;
3399 String [] selArgs = new String[] { url };
3400
3401 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
3402 + Browser.BookmarkColumns.BOOKMARK + " = 0";
3403 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
3404 Browser.HISTORY_PROJECTION, where, selArgs, null);
3405 if (c.moveToFirst()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003406 // Current implementation of database only has one entry per
3407 // url.
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003408 ContentValues map = new ContentValues();
3409 map.put(Browser.BookmarkColumns.TITLE, title);
3410 mResolver.update(Browser.BOOKMARKS_URI, map,
3411 "_id = " + c.getInt(0), null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003412 }
3413 c.close();
3414 } catch (IllegalStateException e) {
3415 Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3416 } catch (SQLiteException ex) {
3417 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3418 }
3419 }
3420
3421 @Override
3422 public void onReceivedIcon(WebView view, Bitmap icon) {
Patrick Scott3918d442009-08-04 13:22:29 -04003423 updateIcon(view, icon);
3424 }
3425
3426 @Override
3427 public void onReceivedTouchIconUrl(WebView view, String url) {
3428 final ContentResolver cr = getContentResolver();
3429 final Cursor c =
3430 BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04003431 view.getOriginalUrl(), view.getUrl(), true);
Patrick Scott3918d442009-08-04 13:22:29 -04003432 if (c != null) {
3433 if (c.getCount() > 0) {
3434 new DownloadTouchIcon(cr, c, view).execute(url);
3435 } else {
3436 c.close();
3437 }
3438 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003439 }
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003440
Andrei Popescuadc008d2009-06-26 14:11:30 +01003441 @Override
Andrei Popescuc9b55562009-07-07 10:51:15 +01003442 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01003443 if (mCustomView != null)
3444 return;
3445
3446 // Add the custom view to its container.
3447 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
3448 mCustomView = view;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003449 mCustomViewCallback = callback;
Andrei Popescuadc008d2009-06-26 14:11:30 +01003450 // Save the menu state and set it to empty while the custom
3451 // view is showing.
3452 mOldMenuState = mMenuState;
3453 mMenuState = EMPTY_MENU;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003454 // Hide the content view.
3455 mContentView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003456 // Finally show the custom view container.
Andrei Popescuc9b55562009-07-07 10:51:15 +01003457 mCustomViewContainer.setVisibility(View.VISIBLE);
3458 mCustomViewContainer.bringToFront();
Andrei Popescuadc008d2009-06-26 14:11:30 +01003459 }
3460
3461 @Override
3462 public void onHideCustomView() {
3463 if (mCustomView == null)
3464 return;
3465
Andrei Popescuc9b55562009-07-07 10:51:15 +01003466 // Hide the custom view.
3467 mCustomView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003468 // Remove the custom view from its container.
3469 mCustomViewContainer.removeView(mCustomView);
3470 mCustomView = null;
3471 // Reset the old menu state.
3472 mMenuState = mOldMenuState;
3473 mOldMenuState = EMPTY_MENU;
3474 mCustomViewContainer.setVisibility(View.GONE);
Andrei Popescuc9b55562009-07-07 10:51:15 +01003475 mCustomViewCallback.onCustomViewHidden();
3476 // Show the content view.
3477 mContentView.setVisibility(View.VISIBLE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003478 }
3479
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003480 /**
Andrei Popescu79e82b72009-07-27 12:01:59 +01003481 * The origin has exceeded its database quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003482 * @param url the URL that exceeded the quota
3483 * @param databaseIdentifier the identifier of the database on
3484 * which the transaction that caused the quota overflow was run
3485 * @param currentQuota the current quota for the origin.
Andrei Popescu79e82b72009-07-27 12:01:59 +01003486 * @param totalUsedQuota is the sum of all origins' quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003487 * @param quotaUpdater The callback to run when a decision to allow or
3488 * deny quota has been made. Don't forget to call this!
3489 */
3490 @Override
3491 public void onExceededDatabaseQuota(String url,
Andrei Popescu79e82b72009-07-27 12:01:59 +01003492 String databaseIdentifier, long currentQuota, long totalUsedQuota,
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003493 WebStorage.QuotaUpdater quotaUpdater) {
Andrei Popescu79e82b72009-07-27 12:01:59 +01003494 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
3495 url, databaseIdentifier, currentQuota, totalUsedQuota,
3496 quotaUpdater);
3497 }
3498
3499 /**
3500 * The Application Cache has exceeded its max size.
3501 * @param spaceNeeded is the amount of disk space that would be needed
3502 * in order for the last appcache operation to succeed.
3503 * @param totalUsedQuota is the sum of all origins' quota.
3504 * @param quotaUpdater A callback to inform the WebCore thread that a new
3505 * app cache size is available. This callback must always be executed at
3506 * some point to ensure that the sleeping WebCore thread is woken up.
3507 */
3508 @Override
3509 public void onReachedMaxAppCacheSize(long spaceNeeded,
3510 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3511 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3512 spaceNeeded, totalUsedQuota, quotaUpdater);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003513 }
Ben Murdoch7db26342009-06-03 18:21:19 +01003514
Steve Block2bc69912009-07-30 14:45:13 +01003515 /**
3516 * Instructs the browser to show a prompt to ask the user to set the
3517 * Geolocation permission state for the specified origin.
3518 * @param origin The origin for which Geolocation permissions are
3519 * requested.
3520 * @param callback The callback to call once the user has set the
3521 * Geolocation permission state.
3522 */
3523 @Override
3524 public void onGeolocationPermissionsShowPrompt(String origin,
3525 GeolocationPermissions.Callback callback) {
3526 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3527 origin, callback);
3528 }
3529
3530 /**
3531 * Instructs the browser to hide the Geolocation permissions prompt.
3532 */
3533 @Override
3534 public void onGeolocationPermissionsHidePrompt() {
3535 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3536 }
3537
Ben Murdoch7db26342009-06-03 18:21:19 +01003538 /* Adds a JavaScript error message to the system log.
3539 * @param message The error message to report.
3540 * @param lineNumber The line number of the error.
3541 * @param sourceID The name of the source file that caused the error.
3542 */
3543 @Override
3544 public void addMessageToConsole(String message, int lineNumber, String sourceID) {
Ben Murdochbff2d602009-07-01 20:19:05 +01003545 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3546 errorConsole.addErrorMessage(message, sourceID, lineNumber);
3547 if (mShouldShowErrorConsole &&
3548 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3549 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3550 }
3551 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
Ben Murdoch7db26342009-06-03 18:21:19 +01003552 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003553 };
3554
3555 /**
3556 * Notify the host application a download should be done, or that
3557 * the data should be streamed if a streaming viewer is available.
3558 * @param url The full url to the content that should be downloaded
3559 * @param contentDisposition Content-disposition http header, if
3560 * present.
3561 * @param mimetype The mimetype of the content reported by the server
3562 * @param contentLength The file size reported by the server
3563 */
3564 public void onDownloadStart(String url, String userAgent,
3565 String contentDisposition, String mimetype, long contentLength) {
3566 // if we're dealing wih A/V content that's not explicitly marked
3567 // for download, check if it's streamable.
3568 if (contentDisposition == null
3569 || !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) {
3570 // query the package manager to see if there's a registered handler
3571 // that matches.
3572 Intent intent = new Intent(Intent.ACTION_VIEW);
3573 intent.setDataAndType(Uri.parse(url), mimetype);
3574 if (getPackageManager().resolveActivity(intent,
3575 PackageManager.MATCH_DEFAULT_ONLY) != null) {
3576 // someone knows how to handle this mime type with this scheme, don't download.
3577 try {
3578 startActivity(intent);
3579 return;
3580 } catch (ActivityNotFoundException ex) {
Dave Bort31a6d1c2009-04-13 15:56:49 -07003581 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003582 Log.d(LOGTAG, "activity not found for " + mimetype
3583 + " over " + Uri.parse(url).getScheme(), ex);
3584 }
3585 // Best behavior is to fall back to a download in this case
3586 }
3587 }
3588 }
3589 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3590 }
3591
3592 /**
3593 * Notify the host application a download should be done, even if there
3594 * is a streaming viewer available for thise type.
3595 * @param url The full url to the content that should be downloaded
3596 * @param contentDisposition Content-disposition http header, if
3597 * present.
3598 * @param mimetype The mimetype of the content reported by the server
3599 * @param contentLength The file size reported by the server
3600 */
3601 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3602 String contentDisposition, String mimetype, long contentLength) {
3603
3604 String filename = URLUtil.guessFileName(url,
3605 contentDisposition, mimetype);
3606
3607 // Check to see if we have an SDCard
3608 String status = Environment.getExternalStorageState();
3609 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3610 int title;
3611 String msg;
3612
3613 // Check to see if the SDCard is busy, same as the music app
3614 if (status.equals(Environment.MEDIA_SHARED)) {
3615 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3616 title = R.string.download_sdcard_busy_dlg_title;
3617 } else {
3618 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3619 title = R.string.download_no_sdcard_dlg_title;
3620 }
3621
3622 new AlertDialog.Builder(this)
3623 .setTitle(title)
3624 .setIcon(android.R.drawable.ic_dialog_alert)
3625 .setMessage(msg)
3626 .setPositiveButton(R.string.ok, null)
3627 .show();
3628 return;
3629 }
3630
3631 // java.net.URI is a lot stricter than KURL so we have to undo
3632 // KURL's percent-encoding and redo the encoding using java.net.URI.
3633 URI uri = null;
3634 try {
3635 // Undo the percent-encoding that KURL may have done.
3636 String newUrl = new String(URLUtil.decode(url.getBytes()));
3637 // Parse the url into pieces
3638 WebAddress w = new WebAddress(newUrl);
3639 String frag = null;
3640 String query = null;
3641 String path = w.mPath;
3642 // Break the path into path, query, and fragment
3643 if (path.length() > 0) {
3644 // Strip the fragment
3645 int idx = path.lastIndexOf('#');
3646 if (idx != -1) {
3647 frag = path.substring(idx + 1);
3648 path = path.substring(0, idx);
3649 }
3650 idx = path.lastIndexOf('?');
3651 if (idx != -1) {
3652 query = path.substring(idx + 1);
3653 path = path.substring(0, idx);
3654 }
3655 }
3656 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3657 query, frag);
3658 } catch (Exception e) {
3659 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3660 return;
3661 }
3662
3663 // XXX: Have to use the old url since the cookies were stored using the
3664 // old percent-encoded url.
3665 String cookies = CookieManager.getInstance().getCookie(url);
3666
3667 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003668 values.put(Downloads.COLUMN_URI, uri.toString());
3669 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3670 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3671 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003672 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003673 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003674 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003675 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3676 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3677 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3678 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003679 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003680 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003681 }
3682 if (mimetype == null) {
3683 // We must have long pressed on a link or image to download it. We
3684 // are not sure of the mimetype in this case, so do a head request
3685 new FetchUrlMimeType(this).execute(values);
3686 } else {
3687 final Uri contentUri =
3688 getContentResolver().insert(Downloads.CONTENT_URI, values);
3689 viewDownloads(contentUri);
3690 }
3691
3692 }
3693
3694 /**
3695 * Resets the lock icon. This method is called when we start a new load and
3696 * know the url to be loaded.
3697 */
3698 private void resetLockIcon(String url) {
3699 // Save the lock-icon state (we revert to it if the load gets cancelled)
3700 saveLockIcon();
3701
3702 mLockIconType = LOCK_ICON_UNSECURE;
3703 if (URLUtil.isHttpsUrl(url)) {
3704 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003705 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003706 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3707 " reset lock icon to " + mLockIconType);
3708 }
3709 }
3710
3711 updateLockIconImage(LOCK_ICON_UNSECURE);
3712 }
3713
3714 /**
3715 * Resets the lock icon. This method is called when the icon needs to be
3716 * reset but we do not know whether we are loading a secure or not secure
3717 * page.
3718 */
3719 private void resetLockIcon() {
3720 // Save the lock-icon state (we revert to it if the load gets cancelled)
3721 saveLockIcon();
3722
3723 mLockIconType = LOCK_ICON_UNSECURE;
3724
Dave Bort31a6d1c2009-04-13 15:56:49 -07003725 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003726 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3727 " reset lock icon to " + mLockIconType);
3728 }
3729
3730 updateLockIconImage(LOCK_ICON_UNSECURE);
3731 }
3732
3733 /**
3734 * Updates the lock-icon image in the title-bar.
3735 */
3736 private void updateLockIconImage(int lockIconType) {
3737 Drawable d = null;
3738 if (lockIconType == LOCK_ICON_SECURE) {
3739 d = mSecLockIcon;
3740 } else if (lockIconType == LOCK_ICON_MIXED) {
3741 d = mMixLockIcon;
3742 }
3743 // If the tab overview is animating or being shown, do not update the
3744 // lock icon.
3745 if (mAnimationCount == 0 && mTabOverview == null) {
Leon Scroggins81db3662009-06-04 17:45:11 -04003746 if (CUSTOM_BROWSER_BAR) {
3747 mTitleBar.setLock(d);
3748 } else {
3749 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
3750 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003751 }
3752 }
3753
3754 /**
3755 * Displays a page-info dialog.
3756 * @param tab The tab to show info about
3757 * @param fromShowSSLCertificateOnError The flag that indicates whether
3758 * this dialog was opened from the SSL-certificate-on-error dialog or
3759 * not. This is important, since we need to know whether to return to
3760 * the parent dialog or simply dismiss.
3761 */
3762 private void showPageInfo(final TabControl.Tab tab,
3763 final boolean fromShowSSLCertificateOnError) {
3764 final LayoutInflater factory = LayoutInflater
3765 .from(this);
3766
3767 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3768
3769 final WebView view = tab.getWebView();
3770
3771 String url = null;
3772 String title = null;
3773
3774 if (view == null) {
3775 url = tab.getUrl();
3776 title = tab.getTitle();
3777 } else if (view == mTabControl.getCurrentWebView()) {
3778 // Use the cached title and url if this is the current WebView
3779 url = mUrl;
3780 title = mTitle;
3781 } else {
3782 url = view.getUrl();
3783 title = view.getTitle();
3784 }
3785
3786 if (url == null) {
3787 url = "";
3788 }
3789 if (title == null) {
3790 title = "";
3791 }
3792
3793 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3794 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3795
3796 mPageInfoView = tab;
3797 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3798
3799 AlertDialog.Builder alertDialogBuilder =
3800 new AlertDialog.Builder(this)
3801 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3802 .setView(pageInfoView)
3803 .setPositiveButton(
3804 R.string.ok,
3805 new DialogInterface.OnClickListener() {
3806 public void onClick(DialogInterface dialog,
3807 int whichButton) {
3808 mPageInfoDialog = null;
3809 mPageInfoView = null;
3810 mPageInfoFromShowSSLCertificateOnError = null;
3811
3812 // if we came here from the SSL error dialog
3813 if (fromShowSSLCertificateOnError) {
3814 // go back to the SSL error dialog
3815 showSSLCertificateOnError(
3816 mSSLCertificateOnErrorView,
3817 mSSLCertificateOnErrorHandler,
3818 mSSLCertificateOnErrorError);
3819 }
3820 }
3821 })
3822 .setOnCancelListener(
3823 new DialogInterface.OnCancelListener() {
3824 public void onCancel(DialogInterface dialog) {
3825 mPageInfoDialog = null;
3826 mPageInfoView = null;
3827 mPageInfoFromShowSSLCertificateOnError = null;
3828
3829 // if we came here from the SSL error dialog
3830 if (fromShowSSLCertificateOnError) {
3831 // go back to the SSL error dialog
3832 showSSLCertificateOnError(
3833 mSSLCertificateOnErrorView,
3834 mSSLCertificateOnErrorHandler,
3835 mSSLCertificateOnErrorError);
3836 }
3837 }
3838 });
3839
3840 // if we have a main top-level page SSL certificate set or a certificate
3841 // error
3842 if (fromShowSSLCertificateOnError ||
3843 (view != null && view.getCertificate() != null)) {
3844 // add a 'View Certificate' button
3845 alertDialogBuilder.setNeutralButton(
3846 R.string.view_certificate,
3847 new DialogInterface.OnClickListener() {
3848 public void onClick(DialogInterface dialog,
3849 int whichButton) {
3850 mPageInfoDialog = null;
3851 mPageInfoView = null;
3852 mPageInfoFromShowSSLCertificateOnError = null;
3853
3854 // if we came here from the SSL error dialog
3855 if (fromShowSSLCertificateOnError) {
3856 // go back to the SSL error dialog
3857 showSSLCertificateOnError(
3858 mSSLCertificateOnErrorView,
3859 mSSLCertificateOnErrorHandler,
3860 mSSLCertificateOnErrorError);
3861 } else {
3862 // otherwise, display the top-most certificate from
3863 // the chain
3864 if (view.getCertificate() != null) {
3865 showSSLCertificate(tab);
3866 }
3867 }
3868 }
3869 });
3870 }
3871
3872 mPageInfoDialog = alertDialogBuilder.show();
3873 }
3874
3875 /**
3876 * Displays the main top-level page SSL certificate dialog
3877 * (accessible from the Page-Info dialog).
3878 * @param tab The tab to show certificate for.
3879 */
3880 private void showSSLCertificate(final TabControl.Tab tab) {
3881 final View certificateView =
3882 inflateCertificateView(tab.getWebView().getCertificate());
3883 if (certificateView == null) {
3884 return;
3885 }
3886
3887 LayoutInflater factory = LayoutInflater.from(this);
3888
3889 final LinearLayout placeholder =
3890 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3891
3892 LinearLayout ll = (LinearLayout) factory.inflate(
3893 R.layout.ssl_success, placeholder);
3894 ((TextView)ll.findViewById(R.id.success))
3895 .setText(R.string.ssl_certificate_is_valid);
3896
3897 mSSLCertificateView = tab;
3898 mSSLCertificateDialog =
3899 new AlertDialog.Builder(this)
3900 .setTitle(R.string.ssl_certificate).setIcon(
3901 R.drawable.ic_dialog_browser_certificate_secure)
3902 .setView(certificateView)
3903 .setPositiveButton(R.string.ok,
3904 new DialogInterface.OnClickListener() {
3905 public void onClick(DialogInterface dialog,
3906 int whichButton) {
3907 mSSLCertificateDialog = null;
3908 mSSLCertificateView = null;
3909
3910 showPageInfo(tab, false);
3911 }
3912 })
3913 .setOnCancelListener(
3914 new DialogInterface.OnCancelListener() {
3915 public void onCancel(DialogInterface dialog) {
3916 mSSLCertificateDialog = null;
3917 mSSLCertificateView = null;
3918
3919 showPageInfo(tab, false);
3920 }
3921 })
3922 .show();
3923 }
3924
3925 /**
3926 * Displays the SSL error certificate dialog.
3927 * @param view The target web-view.
3928 * @param handler The SSL error handler responsible for cancelling the
3929 * connection that resulted in an SSL error or proceeding per user request.
3930 * @param error The SSL error object.
3931 */
3932 private void showSSLCertificateOnError(
3933 final WebView view, final SslErrorHandler handler, final SslError error) {
3934
3935 final View certificateView =
3936 inflateCertificateView(error.getCertificate());
3937 if (certificateView == null) {
3938 return;
3939 }
3940
3941 LayoutInflater factory = LayoutInflater.from(this);
3942
3943 final LinearLayout placeholder =
3944 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3945
3946 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3947 LinearLayout ll = (LinearLayout)factory
3948 .inflate(R.layout.ssl_warning, placeholder);
3949 ((TextView)ll.findViewById(R.id.warning))
3950 .setText(R.string.ssl_untrusted);
3951 }
3952
3953 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3954 LinearLayout ll = (LinearLayout)factory
3955 .inflate(R.layout.ssl_warning, placeholder);
3956 ((TextView)ll.findViewById(R.id.warning))
3957 .setText(R.string.ssl_mismatch);
3958 }
3959
3960 if (error.hasError(SslError.SSL_EXPIRED)) {
3961 LinearLayout ll = (LinearLayout)factory
3962 .inflate(R.layout.ssl_warning, placeholder);
3963 ((TextView)ll.findViewById(R.id.warning))
3964 .setText(R.string.ssl_expired);
3965 }
3966
3967 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3968 LinearLayout ll = (LinearLayout)factory
3969 .inflate(R.layout.ssl_warning, placeholder);
3970 ((TextView)ll.findViewById(R.id.warning))
3971 .setText(R.string.ssl_not_yet_valid);
3972 }
3973
3974 mSSLCertificateOnErrorHandler = handler;
3975 mSSLCertificateOnErrorView = view;
3976 mSSLCertificateOnErrorError = error;
3977 mSSLCertificateOnErrorDialog =
3978 new AlertDialog.Builder(this)
3979 .setTitle(R.string.ssl_certificate).setIcon(
3980 R.drawable.ic_dialog_browser_certificate_partially_secure)
3981 .setView(certificateView)
3982 .setPositiveButton(R.string.ok,
3983 new DialogInterface.OnClickListener() {
3984 public void onClick(DialogInterface dialog,
3985 int whichButton) {
3986 mSSLCertificateOnErrorDialog = null;
3987 mSSLCertificateOnErrorView = null;
3988 mSSLCertificateOnErrorHandler = null;
3989 mSSLCertificateOnErrorError = null;
3990
3991 mWebViewClient.onReceivedSslError(
3992 view, handler, error);
3993 }
3994 })
3995 .setNeutralButton(R.string.page_info_view,
3996 new DialogInterface.OnClickListener() {
3997 public void onClick(DialogInterface dialog,
3998 int whichButton) {
3999 mSSLCertificateOnErrorDialog = null;
4000
4001 // do not clear the dialog state: we will
4002 // need to show the dialog again once the
4003 // user is done exploring the page-info details
4004
4005 showPageInfo(mTabControl.getTabFromView(view),
4006 true);
4007 }
4008 })
4009 .setOnCancelListener(
4010 new DialogInterface.OnCancelListener() {
4011 public void onCancel(DialogInterface dialog) {
4012 mSSLCertificateOnErrorDialog = null;
4013 mSSLCertificateOnErrorView = null;
4014 mSSLCertificateOnErrorHandler = null;
4015 mSSLCertificateOnErrorError = null;
4016
4017 mWebViewClient.onReceivedSslError(
4018 view, handler, error);
4019 }
4020 })
4021 .show();
4022 }
4023
4024 /**
4025 * Inflates the SSL certificate view (helper method).
4026 * @param certificate The SSL certificate.
4027 * @return The resultant certificate view with issued-to, issued-by,
4028 * issued-on, expires-on, and possibly other fields set.
4029 * If the input certificate is null, returns null.
4030 */
4031 private View inflateCertificateView(SslCertificate certificate) {
4032 if (certificate == null) {
4033 return null;
4034 }
4035
4036 LayoutInflater factory = LayoutInflater.from(this);
4037
4038 View certificateView = factory.inflate(
4039 R.layout.ssl_certificate, null);
4040
4041 // issued to:
4042 SslCertificate.DName issuedTo = certificate.getIssuedTo();
4043 if (issuedTo != null) {
4044 ((TextView) certificateView.findViewById(R.id.to_common))
4045 .setText(issuedTo.getCName());
4046 ((TextView) certificateView.findViewById(R.id.to_org))
4047 .setText(issuedTo.getOName());
4048 ((TextView) certificateView.findViewById(R.id.to_org_unit))
4049 .setText(issuedTo.getUName());
4050 }
4051
4052 // issued by:
4053 SslCertificate.DName issuedBy = certificate.getIssuedBy();
4054 if (issuedBy != null) {
4055 ((TextView) certificateView.findViewById(R.id.by_common))
4056 .setText(issuedBy.getCName());
4057 ((TextView) certificateView.findViewById(R.id.by_org))
4058 .setText(issuedBy.getOName());
4059 ((TextView) certificateView.findViewById(R.id.by_org_unit))
4060 .setText(issuedBy.getUName());
4061 }
4062
4063 // issued on:
4064 String issuedOn = reformatCertificateDate(
4065 certificate.getValidNotBefore());
4066 ((TextView) certificateView.findViewById(R.id.issued_on))
4067 .setText(issuedOn);
4068
4069 // expires on:
4070 String expiresOn = reformatCertificateDate(
4071 certificate.getValidNotAfter());
4072 ((TextView) certificateView.findViewById(R.id.expires_on))
4073 .setText(expiresOn);
4074
4075 return certificateView;
4076 }
4077
4078 /**
4079 * Re-formats the certificate date (Date.toString()) string to
4080 * a properly localized date string.
4081 * @return Properly localized version of the certificate date string and
4082 * the original certificate date string if fails to localize.
4083 * If the original string is null, returns an empty string "".
4084 */
4085 private String reformatCertificateDate(String certificateDate) {
4086 String reformattedDate = null;
4087
4088 if (certificateDate != null) {
4089 Date date = null;
4090 try {
4091 date = java.text.DateFormat.getInstance().parse(certificateDate);
4092 } catch (ParseException e) {
4093 date = null;
4094 }
4095
4096 if (date != null) {
4097 reformattedDate =
4098 DateFormat.getDateFormat(this).format(date);
4099 }
4100 }
4101
4102 return reformattedDate != null ? reformattedDate :
4103 (certificateDate != null ? certificateDate : "");
4104 }
4105
4106 /**
4107 * Displays an http-authentication dialog.
4108 */
4109 private void showHttpAuthentication(final HttpAuthHandler handler,
4110 final String host, final String realm, final String title,
4111 final String name, final String password, int focusId) {
4112 LayoutInflater factory = LayoutInflater.from(this);
4113 final View v = factory
4114 .inflate(R.layout.http_authentication, null);
4115 if (name != null) {
4116 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
4117 }
4118 if (password != null) {
4119 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
4120 }
4121
4122 String titleText = title;
4123 if (titleText == null) {
4124 titleText = getText(R.string.sign_in_to).toString().replace(
4125 "%s1", host).replace("%s2", realm);
4126 }
4127
4128 mHttpAuthHandler = handler;
4129 AlertDialog dialog = new AlertDialog.Builder(this)
4130 .setTitle(titleText)
4131 .setIcon(android.R.drawable.ic_dialog_alert)
4132 .setView(v)
4133 .setPositiveButton(R.string.action,
4134 new DialogInterface.OnClickListener() {
4135 public void onClick(DialogInterface dialog,
4136 int whichButton) {
4137 String nm = ((EditText) v
4138 .findViewById(R.id.username_edit))
4139 .getText().toString();
4140 String pw = ((EditText) v
4141 .findViewById(R.id.password_edit))
4142 .getText().toString();
4143 BrowserActivity.this.setHttpAuthUsernamePassword
4144 (host, realm, nm, pw);
4145 handler.proceed(nm, pw);
4146 mHttpAuthenticationDialog = null;
4147 mHttpAuthHandler = null;
4148 }})
4149 .setNegativeButton(R.string.cancel,
4150 new DialogInterface.OnClickListener() {
4151 public void onClick(DialogInterface dialog,
4152 int whichButton) {
4153 handler.cancel();
4154 BrowserActivity.this.resetTitleAndRevertLockIcon();
4155 mHttpAuthenticationDialog = null;
4156 mHttpAuthHandler = null;
4157 }})
4158 .setOnCancelListener(new DialogInterface.OnCancelListener() {
4159 public void onCancel(DialogInterface dialog) {
4160 handler.cancel();
4161 BrowserActivity.this.resetTitleAndRevertLockIcon();
4162 mHttpAuthenticationDialog = null;
4163 mHttpAuthHandler = null;
4164 }})
4165 .create();
4166 // Make the IME appear when the dialog is displayed if applicable.
4167 dialog.getWindow().setSoftInputMode(
4168 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
4169 dialog.show();
4170 if (focusId != 0) {
4171 dialog.findViewById(focusId).requestFocus();
4172 } else {
4173 v.findViewById(R.id.username_edit).requestFocus();
4174 }
4175 mHttpAuthenticationDialog = dialog;
4176 }
4177
4178 public int getProgress() {
4179 WebView w = mTabControl.getCurrentWebView();
4180 if (w != null) {
4181 return w.getProgress();
4182 } else {
4183 return 100;
4184 }
4185 }
4186
4187 /**
4188 * Set HTTP authentication password.
4189 *
4190 * @param host The host for the password
4191 * @param realm The realm for the password
4192 * @param username The username for the password. If it is null, it means
4193 * password can't be saved.
4194 * @param password The password
4195 */
4196 public void setHttpAuthUsernamePassword(String host, String realm,
4197 String username,
4198 String password) {
4199 WebView w = mTabControl.getCurrentWebView();
4200 if (w != null) {
4201 w.setHttpAuthUsernamePassword(host, realm, username, password);
4202 }
4203 }
4204
4205 /**
4206 * connectivity manager says net has come or gone... inform the user
4207 * @param up true if net has come up, false if net has gone down
4208 */
4209 public void onNetworkToggle(boolean up) {
4210 if (up == mIsNetworkUp) {
4211 return;
4212 } else if (up) {
4213 mIsNetworkUp = true;
4214 if (mAlertDialog != null) {
4215 mAlertDialog.cancel();
4216 mAlertDialog = null;
4217 }
4218 } else {
4219 mIsNetworkUp = false;
4220 if (mInLoad && mAlertDialog == null) {
4221 mAlertDialog = new AlertDialog.Builder(this)
4222 .setTitle(R.string.loadSuspendedTitle)
4223 .setMessage(R.string.loadSuspended)
4224 .setPositiveButton(R.string.ok, null)
4225 .show();
4226 }
4227 }
4228 WebView w = mTabControl.getCurrentWebView();
4229 if (w != null) {
4230 w.setNetworkAvailable(up);
4231 }
4232 }
4233
4234 @Override
4235 protected void onActivityResult(int requestCode, int resultCode,
4236 Intent intent) {
4237 switch (requestCode) {
4238 case COMBO_PAGE:
4239 if (resultCode == RESULT_OK && intent != null) {
4240 String data = intent.getAction();
4241 Bundle extras = intent.getExtras();
4242 if (extras != null && extras.getBoolean("new_window", false)) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04004243 final TabControl.Tab newTab = openTab(data);
4244 if (mSettings.openInBackground() &&
4245 newTab != null && mTabOverview != null) {
4246 mTabControl.populatePickerData(newTab);
4247 mTabControl.setCurrentTab(newTab);
4248 mTabOverview.add(newTab);
4249 mTabOverview.setCurrentIndex(
4250 mTabControl.getCurrentIndex());
4251 sendAnimateFromOverview(newTab, false,
4252 EMPTY_URL_DATA, TAB_OVERVIEW_DELAY, null);
4253 }
Leon Scroggins64b80f32009-08-07 12:03:34 -04004254 } else if (intent.getBooleanExtra("open_search", false)) {
4255 onSearchRequested();
The Android Open Source Project0c908882009-03-03 19:32:16 -08004256 } else {
4257 final TabControl.Tab currentTab =
4258 mTabControl.getCurrentTab();
4259 // If the Window overview is up and we are not in the
4260 // middle of an animation, animate away from it to the
4261 // current tab.
4262 if (mTabOverview != null && mAnimationCount == 0) {
Grace Klobaec7eb372009-06-16 13:45:56 -07004263 sendAnimateFromOverview(currentTab, false,
4264 new UrlData(data), TAB_OVERVIEW_DELAY, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004265 } else {
4266 dismissSubWindow(currentTab);
4267 if (data != null && data.length() != 0) {
4268 getTopWindow().loadUrl(data);
4269 }
4270 }
4271 }
4272 }
4273 break;
4274 default:
4275 break;
4276 }
4277 getTopWindow().requestFocus();
4278 }
4279
4280 /*
4281 * This method is called as a result of the user selecting the options
4282 * menu to see the download window, or when a download changes state. It
4283 * shows the download window ontop of the current window.
4284 */
4285 /* package */ void viewDownloads(Uri downloadRecord) {
4286 Intent intent = new Intent(this,
4287 BrowserDownloadPage.class);
4288 intent.setData(downloadRecord);
4289 startActivityForResult(intent, this.DOWNLOAD_PAGE);
4290
4291 }
4292
4293 /**
4294 * Handle results from Tab Switcher mTabOverview tool
4295 */
4296 private class TabListener implements ImageGrid.Listener {
4297 public void remove(int position) {
4298 // Note: Remove is not enabled if we have only one tab.
Dave Bort31a6d1c2009-04-13 15:56:49 -07004299 if (DEBUG && mTabControl.getTabCount() == 1) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004300 throw new AssertionError();
4301 }
4302
4303 // Remember the current tab.
4304 TabControl.Tab current = mTabControl.getCurrentTab();
4305 final TabControl.Tab remove = mTabControl.getTab(position);
4306 mTabControl.removeTab(remove);
4307 // If we removed the current tab, use the tab at position - 1 if
4308 // possible.
4309 if (current == remove) {
4310 // If the user removes the last tab, act like the New Tab item
4311 // was clicked on.
4312 if (mTabControl.getTabCount() == 0) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07004313 current = mTabControl.createNewTab();
Leon Scroggins64b80f32009-08-07 12:03:34 -04004314 sendAnimateFromOverview(current, true, EMPTY_URL_DATA,
4315 TAB_OVERVIEW_DELAY, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004316 } else {
4317 final int index = position > 0 ? (position - 1) : 0;
4318 current = mTabControl.getTab(index);
4319 }
4320 }
4321
4322 // The tab overview could have been dismissed before this method is
4323 // called.
4324 if (mTabOverview != null) {
4325 // Remove the tab and change the index.
4326 mTabOverview.remove(position);
4327 mTabOverview.setCurrentIndex(mTabControl.getTabIndex(current));
4328 }
4329
4330 // Only the current tab ensures its WebView is non-null. This
4331 // implies that we are reloading the freed tab.
4332 mTabControl.setCurrentTab(current);
4333 }
4334 public void onClick(int index) {
4335 // Change the tab if necessary.
4336 // Index equals ImageGrid.CANCEL when pressing back from the tab
4337 // overview.
4338 if (index == ImageGrid.CANCEL) {
4339 index = mTabControl.getCurrentIndex();
4340 // The current index is -1 if the current tab was removed.
4341 if (index == -1) {
4342 // Take the last tab as a fallback.
4343 index = mTabControl.getTabCount() - 1;
4344 }
4345 }
4346
The Android Open Source Project0c908882009-03-03 19:32:16 -08004347 // NEW_TAB means that the "New Tab" cell was clicked on.
4348 if (index == ImageGrid.NEW_TAB) {
Leon Scroggins64b80f32009-08-07 12:03:34 -04004349 openTabAndShow(EMPTY_URL_DATA, null, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004350 } else {
Grace Klobaec7eb372009-06-16 13:45:56 -07004351 sendAnimateFromOverview(mTabControl.getTab(index), false,
4352 EMPTY_URL_DATA, 0, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004353 }
4354 }
4355 }
4356
4357 // A fake View that draws the WebView's picture with a fast zoom filter.
4358 // The View is used in case the tab is freed during the animation because
4359 // of low memory.
4360 private static class AnimatingView extends View {
4361 private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
4362 Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG;
4363 private static final DrawFilter sZoomFilter =
4364 new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
4365 private final Picture mPicture;
4366 private final float mScale;
4367 private final int mScrollX;
4368 private final int mScrollY;
4369 final TabControl.Tab mTab;
4370
4371 AnimatingView(Context ctxt, TabControl.Tab t) {
4372 super(ctxt);
4373 mTab = t;
Patrick Scottae641ac2009-04-20 13:51:49 -04004374 if (t != null && t.getTopWindow() != null) {
4375 // Use the top window in the animation since the tab overview
4376 // will display the top window in each cell.
4377 final WebView w = t.getTopWindow();
4378 mPicture = w.capturePicture();
4379 mScale = w.getScale() / w.getWidth();
4380 mScrollX = w.getScrollX();
4381 mScrollY = w.getScrollY();
4382 } else {
4383 mPicture = null;
4384 mScale = 1.0f;
4385 mScrollX = mScrollY = 0;
4386 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08004387 }
4388
4389 @Override
4390 protected void onDraw(Canvas canvas) {
4391 canvas.save();
4392 canvas.drawColor(Color.WHITE);
4393 if (mPicture != null) {
4394 canvas.setDrawFilter(sZoomFilter);
4395 float scale = getWidth() * mScale;
4396 canvas.scale(scale, scale);
4397 canvas.translate(-mScrollX, -mScrollY);
4398 canvas.drawPicture(mPicture);
4399 }
4400 canvas.restore();
4401 }
4402 }
4403
4404 /**
4405 * Open the tab picker. This function will always use the current tab in
4406 * its animation.
4407 * @param stay boolean stating whether the tab picker is to remain open
4408 * (in which case it needs a listener and its menu) or not.
4409 * @param index The index of the tab to show as the selection in the tab
4410 * overview.
4411 * @param remove If true, the tab at index will be removed after the
4412 * animation completes.
4413 */
4414 private void tabPicker(final boolean stay, final int index,
4415 final boolean remove) {
4416 if (mTabOverview != null) {
4417 return;
4418 }
4419
4420 int size = mTabControl.getTabCount();
4421
4422 TabListener l = null;
4423 if (stay) {
4424 l = mTabListener = new TabListener();
4425 }
4426 mTabOverview = new ImageGrid(this, stay, l);
4427
4428 for (int i = 0; i < size; i++) {
4429 final TabControl.Tab t = mTabControl.getTab(i);
4430 mTabControl.populatePickerData(t);
4431 mTabOverview.add(t);
4432 }
4433
4434 // Tell the tab overview to show the current tab, the tab overview will
4435 // handle the "New Tab" case.
4436 int currentIndex = mTabControl.getCurrentIndex();
4437 mTabOverview.setCurrentIndex(currentIndex);
4438
4439 // Attach the tab overview.
4440 mContentView.addView(mTabOverview, COVER_SCREEN_PARAMS);
4441
4442 // Create a fake AnimatingView to animate the WebView's picture.
4443 final TabControl.Tab current = mTabControl.getCurrentTab();
4444 final AnimatingView v = new AnimatingView(this, current);
4445 mContentView.addView(v, COVER_SCREEN_PARAMS);
4446 removeTabFromContentView(current);
4447 // Pause timers to get the animation smoother.
4448 current.getWebView().pauseTimers();
4449
4450 // Send a message so the tab picker has a chance to layout and get
4451 // positions for all the cells.
4452 mHandler.sendMessage(mHandler.obtainMessage(ANIMATE_TO_OVERVIEW,
4453 index, remove ? 1 : 0, v));
4454 // Setting this will indicate that we are animating to the overview. We
4455 // set it here to prevent another request to animate from coming in
4456 // between now and when ANIMATE_TO_OVERVIEW is handled.
4457 mAnimationCount++;
4458 // Always change the title bar to the window overview title while
4459 // animating.
Leon Scroggins81db3662009-06-04 17:45:11 -04004460 if (CUSTOM_BROWSER_BAR) {
4461 mTitleBar.setToTabPicker();
4462 } else {
4463 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, null);
4464 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null);
4465 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
4466 Window.PROGRESS_VISIBILITY_OFF);
4467 setTitle(R.string.tab_picker_title);
4468 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08004469 // Make the menu empty until the animation completes.
4470 mMenuState = EMPTY_MENU;
4471 }
4472
Leon Scrogginse4b3bda2009-06-09 15:46:41 -04004473 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004474 WebView current = mTabControl.getCurrentWebView();
4475 if (current == null) {
4476 return;
4477 }
4478 Intent intent = new Intent(this,
4479 CombinedBookmarkHistoryActivity.class);
4480 String title = current.getTitle();
4481 String url = current.getUrl();
4482 // Just in case the user opens bookmarks before a page finishes loading
4483 // so the current history item, and therefore the page, is null.
4484 if (null == url) {
4485 url = mLastEnteredUrl;
4486 // This can happen.
4487 if (null == url) {
4488 url = mSettings.getHomePage();
4489 }
4490 }
4491 // In case the web page has not yet received its associated title.
4492 if (title == null) {
4493 title = url;
4494 }
4495 intent.putExtra("title", title);
4496 intent.putExtra("url", url);
4497 intent.putExtra("maxTabsOpen",
4498 mTabControl.getTabCount() >= TabControl.MAX_TABS);
Patrick Scott3918d442009-08-04 13:22:29 -04004499 intent.putExtra("touch_icon_url", current.getTouchIconUrl());
The Android Open Source Project0c908882009-03-03 19:32:16 -08004500 if (startWithHistory) {
4501 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
4502 CombinedBookmarkHistoryActivity.HISTORY_TAB);
4503 }
4504 startActivityForResult(intent, COMBO_PAGE);
4505 }
4506
4507 // Called when loading from context menu or LOAD_URL message
4508 private void loadURL(WebView view, String url) {
4509 // In case the user enters nothing.
4510 if (url != null && url.length() != 0 && view != null) {
4511 url = smartUrlFilter(url);
4512 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
4513 view.loadUrl(url);
4514 }
4515 }
4516 }
4517
The Android Open Source Project0c908882009-03-03 19:32:16 -08004518 private String smartUrlFilter(Uri inUri) {
4519 if (inUri != null) {
4520 return smartUrlFilter(inUri.toString());
4521 }
4522 return null;
4523 }
4524
4525
4526 // get window count
4527
4528 int getWindowCount(){
4529 if(mTabControl != null){
4530 return mTabControl.getTabCount();
4531 }
4532 return 0;
4533 }
4534
Feng Qianb34f87a2009-03-24 21:27:26 -07004535 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08004536 "(?i)" + // switch on case insensitive matching
4537 "(" + // begin group for schema
4538 "(?:http|https|file):\\/\\/" +
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004539 "|(?:inline|data|about|content|javascript):" +
The Android Open Source Project0c908882009-03-03 19:32:16 -08004540 ")" +
4541 "(.*)" );
4542
4543 /**
4544 * Attempts to determine whether user input is a URL or search
4545 * terms. Anything with a space is passed to search.
4546 *
4547 * Converts to lowercase any mistakenly uppercased schema (i.e.,
4548 * "Http://" converts to "http://"
4549 *
4550 * @return Original or modified URL
4551 *
4552 */
4553 String smartUrlFilter(String url) {
4554
4555 String inUrl = url.trim();
4556 boolean hasSpace = inUrl.indexOf(' ') != -1;
4557
4558 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4559 if (matcher.matches()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004560 // force scheme to lowercase
4561 String scheme = matcher.group(1);
4562 String lcScheme = scheme.toLowerCase();
4563 if (!lcScheme.equals(scheme)) {
Mitsuru Oshima123ecfb2009-05-18 19:11:14 -07004564 inUrl = lcScheme + matcher.group(2);
4565 }
4566 if (hasSpace) {
4567 inUrl = inUrl.replace(" ", "%20");
The Android Open Source Project0c908882009-03-03 19:32:16 -08004568 }
4569 return inUrl;
4570 }
4571 if (hasSpace) {
Satish Sampath565505b2009-05-29 15:37:27 +01004572 // FIXME: Is this the correct place to add to searches?
4573 // what if someone else calls this function?
4574 int shortcut = parseUrlShortcut(inUrl);
4575 if (shortcut != SHORTCUT_INVALID) {
4576 Browser.addSearchUrl(mResolver, inUrl);
4577 String query = inUrl.substring(2);
4578 switch (shortcut) {
4579 case SHORTCUT_GOOGLE_SEARCH:
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004580 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
Satish Sampath565505b2009-05-29 15:37:27 +01004581 case SHORTCUT_WIKIPEDIA_SEARCH:
4582 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4583 case SHORTCUT_DICTIONARY_SEARCH:
4584 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4585 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
The Android Open Source Project0c908882009-03-03 19:32:16 -08004586 // FIXME: we need location in this case
Satish Sampath565505b2009-05-29 15:37:27 +01004587 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004588 }
4589 }
4590 } else {
4591 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4592 return URLUtil.guessUrl(inUrl);
4593 }
4594 }
4595
4596 Browser.addSearchUrl(mResolver, inUrl);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004597 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004598 }
4599
Ben Murdochbff2d602009-07-01 20:19:05 +01004600 /* package */ void setShouldShowErrorConsole(boolean flag) {
4601 if (flag == mShouldShowErrorConsole) {
4602 // Nothing to do.
4603 return;
4604 }
4605
4606 mShouldShowErrorConsole = flag;
4607
4608 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4609
4610 if (flag) {
4611 // Setting the show state of the console will cause it's the layout to be inflated.
4612 if (errorConsole.numberOfErrors() > 0) {
4613 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4614 } else {
4615 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4616 }
4617
4618 // Now we can add it to the main view.
4619 mErrorConsoleContainer.addView(errorConsole,
4620 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4621 ViewGroup.LayoutParams.WRAP_CONTENT));
4622 } else {
4623 mErrorConsoleContainer.removeView(errorConsole);
4624 }
4625
4626 }
4627
The Android Open Source Project0c908882009-03-03 19:32:16 -08004628 private final static int LOCK_ICON_UNSECURE = 0;
4629 private final static int LOCK_ICON_SECURE = 1;
4630 private final static int LOCK_ICON_MIXED = 2;
4631
4632 private int mLockIconType = LOCK_ICON_UNSECURE;
4633 private int mPrevLockType = LOCK_ICON_UNSECURE;
4634
4635 private BrowserSettings mSettings;
4636 private TabControl mTabControl;
4637 private ContentResolver mResolver;
4638 private FrameLayout mContentView;
4639 private ImageGrid mTabOverview;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004640 private View mCustomView;
4641 private FrameLayout mCustomViewContainer;
Andrei Popescuc9b55562009-07-07 10:51:15 +01004642 private WebChromeClient.CustomViewCallback mCustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004643
4644 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4645 // view, we should rewrite this.
4646 private int mCurrentMenuState = 0;
4647 private int mMenuState = R.id.MAIN_MENU;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004648 private int mOldMenuState = EMPTY_MENU;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004649 private static final int EMPTY_MENU = -1;
4650 private Menu mMenu;
4651
4652 private FindDialog mFindDialog;
4653 // Used to prevent chording to result in firing two shortcuts immediately
4654 // one after another. Fixes bug 1211714.
4655 boolean mCanChord;
4656
4657 private boolean mInLoad;
4658 private boolean mIsNetworkUp;
4659
4660 private boolean mPageStarted;
4661 private boolean mActivityInPause = true;
4662
4663 private boolean mMenuIsDown;
4664
4665 private final KeyTracker mKeyTracker = new KeyTracker(this);
4666
4667 // As trackball doesn't send repeat down, we have to track it ourselves
4668 private boolean mTrackTrackball;
4669
4670 private static boolean mInTrace;
4671
4672 // Performance probe
4673 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4674 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4675 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4676 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4677 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4678 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4679 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4680 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4681 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4682 };
4683
4684 private long mStart;
4685 private long mProcessStart;
4686 private long mUserStart;
4687 private long mSystemStart;
4688 private long mIdleStart;
4689 private long mIrqStart;
4690
4691 private long mUiStart;
4692
4693 private Drawable mMixLockIcon;
4694 private Drawable mSecLockIcon;
4695 private Drawable mGenericFavicon;
4696
4697 /* hold a ref so we can auto-cancel if necessary */
4698 private AlertDialog mAlertDialog;
4699
4700 // Wait for credentials before loading google.com
4701 private ProgressDialog mCredsDlg;
4702
4703 // The up-to-date URL and title (these can be different from those stored
4704 // in WebView, since it takes some time for the information in WebView to
4705 // get updated)
4706 private String mUrl;
4707 private String mTitle;
4708
4709 // As PageInfo has different style for landscape / portrait, we have
4710 // to re-open it when configuration changed
4711 private AlertDialog mPageInfoDialog;
4712 private TabControl.Tab mPageInfoView;
4713 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4714 // dialog, we should not just dismiss it, but should get back to the
4715 // SSL-certificate-on-error dialog. This flag is used to store this state
4716 private Boolean mPageInfoFromShowSSLCertificateOnError;
4717
4718 // as SSLCertificateOnError has different style for landscape / portrait,
4719 // we have to re-open it when configuration changed
4720 private AlertDialog mSSLCertificateOnErrorDialog;
4721 private WebView mSSLCertificateOnErrorView;
4722 private SslErrorHandler mSSLCertificateOnErrorHandler;
4723 private SslError mSSLCertificateOnErrorError;
4724
4725 // as SSLCertificate has different style for landscape / portrait, we
4726 // have to re-open it when configuration changed
4727 private AlertDialog mSSLCertificateDialog;
4728 private TabControl.Tab mSSLCertificateView;
4729
4730 // as HttpAuthentication has different style for landscape / portrait, we
4731 // have to re-open it when configuration changed
4732 private AlertDialog mHttpAuthenticationDialog;
4733 private HttpAuthHandler mHttpAuthHandler;
4734
4735 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4736 new FrameLayout.LayoutParams(
4737 ViewGroup.LayoutParams.FILL_PARENT,
4738 ViewGroup.LayoutParams.FILL_PARENT);
Andrei Popescuadc008d2009-06-26 14:11:30 +01004739 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4740 new FrameLayout.LayoutParams(
4741 ViewGroup.LayoutParams.FILL_PARENT,
4742 ViewGroup.LayoutParams.FILL_PARENT,
4743 Gravity.CENTER);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004744 // Google search
4745 final static String QuickSearch_G = "http://www.google.com/m?q=%s";
The Android Open Source Project0c908882009-03-03 19:32:16 -08004746 // Wikipedia search
4747 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4748 // Dictionary search
4749 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4750 // Google Mobile Local search
4751 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4752
4753 final static String QUERY_PLACE_HOLDER = "%s";
4754
4755 // "source" parameter for Google search through search key
4756 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4757 // "source" parameter for Google search through goto menu
4758 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4759 // "source" parameter for Google search through simplily type
4760 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4761 // "source" parameter for Google search suggested by the browser
4762 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4763 // "source" parameter for Google search from unknown source
4764 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4765
4766 private final static String LOGTAG = "browser";
4767
4768 private TabListener mTabListener;
4769
4770 private String mLastEnteredUrl;
4771
4772 private PowerManager.WakeLock mWakeLock;
4773 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4774
4775 private Toast mStopToast;
4776
Leon Scroggins81db3662009-06-04 17:45:11 -04004777 private TitleBar mTitleBar;
4778
Ben Murdochbff2d602009-07-01 20:19:05 +01004779 private LinearLayout mErrorConsoleContainer = null;
4780 private boolean mShouldShowErrorConsole = false;
4781
The Android Open Source Project0c908882009-03-03 19:32:16 -08004782 // Used during animations to prevent other animations from being triggered.
4783 // A count is used since the animation to and from the Window overview can
4784 // overlap. A count of 0 means no animation where a count of > 0 means
4785 // there are animations in progress.
4786 private int mAnimationCount;
4787
4788 // As the ids are dynamically created, we can't guarantee that they will
4789 // be in sequence, so this static array maps ids to a window number.
4790 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4791 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4792 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4793 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4794
4795 // monitor platform changes
4796 private IntentFilter mNetworkStateChangedFilter;
4797 private BroadcastReceiver mNetworkStateIntentReceiver;
4798
Grace Klobab4da0ad2009-05-14 14:45:40 -07004799 private BroadcastReceiver mPackageInstallationReceiver;
4800
The Android Open Source Project0c908882009-03-03 19:32:16 -08004801 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004802 final static int COMBO_PAGE = 1;
4803 final static int DOWNLOAD_PAGE = 2;
4804 final static int PREFERENCES_PAGE = 3;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004805
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004806 /**
4807 * A UrlData class to abstract how the content will be set to WebView.
4808 * This base class uses loadUrl to show the content.
4809 */
4810 private static class UrlData {
4811 String mUrl;
Grace Kloba60e095c2009-06-16 11:50:55 -07004812 byte[] mPostData;
4813
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004814 UrlData(String url) {
4815 this.mUrl = url;
4816 }
Grace Kloba60e095c2009-06-16 11:50:55 -07004817
4818 void setPostData(byte[] postData) {
4819 mPostData = postData;
4820 }
4821
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004822 boolean isEmpty() {
4823 return mUrl == null || mUrl.length() == 0;
4824 }
4825
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004826 public void loadIn(WebView webView) {
Grace Kloba60e095c2009-06-16 11:50:55 -07004827 if (mPostData != null) {
4828 webView.postUrl(mUrl, mPostData);
4829 } else {
4830 webView.loadUrl(mUrl);
4831 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004832 }
4833 };
4834
4835 /**
4836 * A subclass of UrlData class that can display inlined content using
4837 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4838 */
4839 private static class InlinedUrlData extends UrlData {
4840 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4841 super(failUrl);
4842 mInlined = inlined;
4843 mMimeType = mimeType;
4844 mEncoding = encoding;
4845 }
4846 String mMimeType;
4847 String mInlined;
4848 String mEncoding;
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004849 @Override
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004850 boolean isEmpty() {
Ben Murdochbff2d602009-07-01 20:19:05 +01004851 return mInlined == null || mInlined.length() == 0 || super.isEmpty();
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004852 }
4853
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004854 @Override
4855 public void loadIn(WebView webView) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004856 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4857 }
4858 }
4859
4860 private static final UrlData EMPTY_URL_DATA = new UrlData(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004861}