blob: 1c270bea0da4aac61e3e6e46c69844c963e4425f [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;
The Android Open Source Project0c908882009-03-03 19:32:16 -080049import android.graphics.DrawFilter;
50import android.graphics.Paint;
51import android.graphics.PaintFlagsDrawFilter;
52import android.graphics.Picture;
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -040053import android.graphics.PixelFormat;
54import android.graphics.Rect;
The Android Open Source Project0c908882009-03-03 19:32:16 -080055import android.graphics.drawable.Drawable;
The Android Open Source Project0c908882009-03-03 19:32:16 -080056import android.hardware.SensorListener;
57import android.hardware.SensorManager;
58import android.net.ConnectivityManager;
59import android.net.Uri;
60import android.net.WebAddress;
61import android.net.http.EventHandler;
62import android.net.http.SslCertificate;
63import android.net.http.SslError;
64import android.os.AsyncTask;
65import android.os.Bundle;
66import android.os.Debug;
67import android.os.Environment;
68import android.os.Handler;
69import android.os.IBinder;
70import android.os.Message;
71import android.os.PowerManager;
72import android.os.Process;
73import android.os.RemoteException;
74import android.os.ServiceManager;
75import android.os.SystemClock;
The Android Open Source Project0c908882009-03-03 19:32:16 -080076import android.provider.Browser;
77import android.provider.Contacts;
78import android.provider.Downloads;
79import android.provider.MediaStore;
80import android.provider.Contacts.Intents.Insert;
81import android.text.IClipboard;
82import android.text.TextUtils;
83import android.text.format.DateFormat;
84import android.text.util.Regex;
The Android Open Source Project0c908882009-03-03 19:32:16 -080085import android.util.Log;
86import android.view.ContextMenu;
87import android.view.Gravity;
88import android.view.KeyEvent;
89import android.view.LayoutInflater;
90import android.view.Menu;
91import android.view.MenuInflater;
92import android.view.MenuItem;
93import android.view.View;
94import android.view.ViewGroup;
95import android.view.Window;
96import android.view.WindowManager;
97import android.view.ContextMenu.ContextMenuInfo;
98import android.view.MenuItem.OnMenuItemClickListener;
99import android.view.animation.AlphaAnimation;
100import android.view.animation.Animation;
101import android.view.animation.AnimationSet;
102import android.view.animation.DecelerateInterpolator;
103import android.view.animation.ScaleAnimation;
104import android.view.animation.TranslateAnimation;
105import android.webkit.CookieManager;
106import android.webkit.CookieSyncManager;
107import android.webkit.DownloadListener;
Steve Block2bc69912009-07-30 14:45:13 +0100108import android.webkit.GeolocationPermissions;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800109import android.webkit.HttpAuthHandler;
Grace Klobab4da0ad2009-05-14 14:45:40 -0700110import android.webkit.PluginManager;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800111import android.webkit.SslErrorHandler;
112import android.webkit.URLUtil;
113import android.webkit.WebChromeClient;
Andrei Popescuc9b55562009-07-07 10:51:15 +0100114import android.webkit.WebChromeClient.CustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800115import android.webkit.WebHistoryItem;
116import android.webkit.WebIconDatabase;
Ben Murdoch092dd5d2009-04-22 12:34:12 +0100117import android.webkit.WebStorage;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800118import android.webkit.WebView;
119import android.webkit.WebViewClient;
120import android.widget.EditText;
121import android.widget.FrameLayout;
122import android.widget.LinearLayout;
123import android.widget.TextView;
124import android.widget.Toast;
125
126import java.io.BufferedOutputStream;
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -0400127import java.io.ByteArrayOutputStream;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800128import java.io.File;
129import java.io.FileInputStream;
130import java.io.FileOutputStream;
131import java.io.IOException;
132import java.io.InputStream;
133import java.net.MalformedURLException;
134import java.net.URI;
Dianne Hackborn99189432009-06-17 18:06:18 -0700135import java.net.URISyntaxException;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800136import java.net.URL;
137import java.net.URLEncoder;
138import java.text.ParseException;
139import java.util.Date;
140import java.util.Enumeration;
141import java.util.HashMap;
Patrick Scott37911c72009-03-24 18:02:58 -0700142import java.util.LinkedList;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800143import java.util.Vector;
144import java.util.regex.Matcher;
145import java.util.regex.Pattern;
146import java.util.zip.ZipEntry;
147import java.util.zip.ZipFile;
148
149public class BrowserActivity extends Activity
150 implements KeyTracker.OnKeyTracker,
151 View.OnCreateContextMenuListener,
152 DownloadListener {
153
Dave Bort31a6d1c2009-04-13 15:56:49 -0700154 /* Define some aliases to make these debugging flags easier to refer to.
155 * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
156 */
157 private final static boolean DEBUG = com.android.browser.Browser.DEBUG;
158 private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
159 private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
160
The Android Open Source Project0c908882009-03-03 19:32:16 -0800161 private IGoogleLoginService mGls = null;
162 private ServiceConnection mGlsConnection = null;
163
164 private SensorManager mSensorManager = null;
165
Satish Sampath565505b2009-05-29 15:37:27 +0100166 // These are single-character shortcuts for searching popular sources.
167 private static final int SHORTCUT_INVALID = 0;
168 private static final int SHORTCUT_GOOGLE_SEARCH = 1;
169 private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2;
170 private static final int SHORTCUT_DICTIONARY_SEARCH = 3;
171 private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4;
172
The Android Open Source Project0c908882009-03-03 19:32:16 -0800173 /* Whitelisted webpages
174 private static HashSet<String> sWhiteList;
175
176 static {
177 sWhiteList = new HashSet<String>();
178 sWhiteList.add("cnn.com/");
179 sWhiteList.add("espn.go.com/");
180 sWhiteList.add("nytimes.com/");
181 sWhiteList.add("engadget.com/");
182 sWhiteList.add("yahoo.com/");
183 sWhiteList.add("msn.com/");
184 sWhiteList.add("amazon.com/");
185 sWhiteList.add("consumerist.com/");
186 sWhiteList.add("google.com/m/news");
187 }
188 */
189
190 private void setupHomePage() {
191 final Runnable getAccount = new Runnable() {
192 public void run() {
193 // Lower priority
194 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
195 // get the default home page
196 String homepage = mSettings.getHomePage();
197
198 try {
199 if (mGls == null) return;
200
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700201 if (!homepage.startsWith("http://www.google.")) return;
202 if (homepage.indexOf('?') == -1) return;
203
The Android Open Source Project0c908882009-03-03 19:32:16 -0800204 String hostedUser = mGls.getAccount(GoogleLoginServiceConstants.PREFER_HOSTED);
205 String googleUser = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE);
206
207 // three cases:
208 //
209 // hostedUser == googleUser
210 // The device has only a google account
211 //
212 // hostedUser != googleUser
213 // The device has a hosted account and a google account
214 //
215 // hostedUser != null, googleUser == null
216 // The device has only a hosted account (so far)
217
218 // developers might have no accounts at all
219 if (hostedUser == null) return;
220
221 if (googleUser == null || !hostedUser.equals(googleUser)) {
222 String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1);
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700223 homepage = homepage.replace("?", "/a/" + domain + "?");
The Android Open Source Project0c908882009-03-03 19:32:16 -0800224 }
225 } catch (RemoteException ignore) {
226 // Login service died; carry on
227 } catch (RuntimeException ignore) {
228 // Login service died; carry on
229 } finally {
230 finish(homepage);
231 }
232 }
233
234 private void finish(final String homepage) {
235 mHandler.post(new Runnable() {
236 public void run() {
237 mSettings.setHomePage(BrowserActivity.this, homepage);
238 resumeAfterCredentials();
239
240 // as this is running in a separate thread,
241 // BrowserActivity's onDestroy() may have been called,
242 // which also calls unbindService().
243 if (mGlsConnection != null) {
244 // we no longer need to keep GLS open
245 unbindService(mGlsConnection);
246 mGlsConnection = null;
247 }
248 } });
249 } };
250
251 final boolean[] done = { false };
252
253 // Open a connection to the Google Login Service. The first
254 // time the connection is established, set up the homepage depending on
255 // the account in a background thread.
256 mGlsConnection = new ServiceConnection() {
257 public void onServiceConnected(ComponentName className, IBinder service) {
258 mGls = IGoogleLoginService.Stub.asInterface(service);
259 if (done[0] == false) {
260 done[0] = true;
261 Thread account = new Thread(getAccount);
262 account.setName("GLSAccount");
263 account.start();
264 }
265 }
266 public void onServiceDisconnected(ComponentName className) {
267 mGls = null;
268 }
269 };
270
271 bindService(GoogleLoginServiceConstants.SERVICE_INTENT,
272 mGlsConnection, Context.BIND_AUTO_CREATE);
273 }
274
Cary Clarka9771242009-08-11 16:42:26 -0400275 private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800276 @Override
277 public Void doInBackground(File... files) {
278 if (files != null) {
279 for (File f : files) {
Cary Clarkd6be1752009-08-12 12:56:42 -0400280 if (!f.delete()) {
281 Log.e(LOGTAG, f.getPath() + " was not deleted");
282 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800283 }
284 }
285 return null;
286 }
287 }
288
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -0400289 /**
290 * This layout holds everything you see below the status bar, including the
291 * error console, the custom view container, and the webviews.
292 */
293 private FrameLayout mBrowserFrameLayout;
Leon Scroggins81db3662009-06-04 17:45:11 -0400294
The Android Open Source Project0c908882009-03-03 19:32:16 -0800295 @Override public void onCreate(Bundle icicle) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700296 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800297 Log.v(LOGTAG, this + " onStart");
298 }
299 super.onCreate(icicle);
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -0400300 this.requestWindowFeature(Window.FEATURE_NO_TITLE);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800301 // test the browser in OpenGL
302 // requestWindowFeature(Window.FEATURE_OPENGL);
303
304 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
305
306 mResolver = getContentResolver();
307
The Android Open Source Project0c908882009-03-03 19:32:16 -0800308 //
309 // start MASF proxy service
310 //
311 //Intent proxyServiceIntent = new Intent();
312 //proxyServiceIntent.setComponent
313 // (new ComponentName(
314 // "com.android.masfproxyservice",
315 // "com.android.masfproxyservice.MasfProxyService"));
316 //startService(proxyServiceIntent, null);
317
318 mSecLockIcon = Resources.getSystem().getDrawable(
319 android.R.drawable.ic_secure);
320 mMixLockIcon = Resources.getSystem().getDrawable(
321 android.R.drawable.ic_partial_secure);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800322
Leon Scroggins81db3662009-06-04 17:45:11 -0400323 FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
324 .findViewById(com.android.internal.R.id.content);
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -0400325 mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this)
326 .inflate(R.layout.custom_screen, null);
327 mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(
328 R.id.main_content);
329 mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout
330 .findViewById(R.id.error_console);
331 mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
332 .findViewById(R.id.fullscreen_custom_content);
333 frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
334 mTitleBar = new TitleBarSet(this);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800335
336 // Create the tab control and our initial tab
337 mTabControl = new TabControl(this);
338
339 // Open the icon database and retain all the bookmark urls for favicons
340 retainIconsOnStartup();
341
342 // Keep a settings instance handy.
343 mSettings = BrowserSettings.getInstance();
344 mSettings.setTabControl(mTabControl);
345 mSettings.loadFromDb(this);
346
347 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
348 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
349
Grace Klobaa34f6862009-07-31 16:28:17 -0700350 /* enables registration for changes in network status from
351 http stack */
352 mNetworkStateChangedFilter = new IntentFilter();
353 mNetworkStateChangedFilter.addAction(
354 ConnectivityManager.CONNECTIVITY_ACTION);
355 mNetworkStateIntentReceiver = new BroadcastReceiver() {
356 @Override
357 public void onReceive(Context context, Intent intent) {
358 if (intent.getAction().equals(
359 ConnectivityManager.CONNECTIVITY_ACTION)) {
360 boolean down = intent.getBooleanExtra(
361 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
362 onNetworkToggle(!down);
363 }
364 }
365 };
366
Grace Kloba615c6c92009-08-03 10:22:44 -0700367 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
368 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
369 filter.addDataScheme("package");
370 mPackageInstallationReceiver = new BroadcastReceiver() {
371 @Override
372 public void onReceive(Context context, Intent intent) {
373 final String action = intent.getAction();
374 final String packageName = intent.getData()
375 .getSchemeSpecificPart();
376 final boolean replacing = intent.getBooleanExtra(
377 Intent.EXTRA_REPLACING, false);
378 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
379 // if it is replacing, refreshPlugins() when adding
380 return;
381 }
382 PackageManager pm = BrowserActivity.this.getPackageManager();
383 PackageInfo pkgInfo = null;
384 try {
385 pkgInfo = pm.getPackageInfo(packageName,
386 PackageManager.GET_PERMISSIONS);
387 } catch (PackageManager.NameNotFoundException e) {
388 return;
389 }
390 if (pkgInfo != null) {
391 String permissions[] = pkgInfo.requestedPermissions;
392 if (permissions == null) {
393 return;
394 }
395 boolean permissionOk = false;
396 for (String permit : permissions) {
397 if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
398 permissionOk = true;
399 break;
400 }
401 }
402 if (permissionOk) {
403 PluginManager.getInstance(BrowserActivity.this)
404 .refreshPlugins(
405 Intent.ACTION_PACKAGE_ADDED
406 .equals(action));
407 }
408 }
409 }
410 };
411 registerReceiver(mPackageInstallationReceiver, filter);
412
Satish Sampath565505b2009-05-29 15:37:27 +0100413 // If this was a web search request, pass it on to the default web search provider.
414 if (handleWebSearchIntent(getIntent())) {
415 moveTaskToBack(true);
416 return;
417 }
418
The Android Open Source Project0c908882009-03-03 19:32:16 -0800419 if (!mTabControl.restoreState(icicle)) {
420 // clear up the thumbnail directory if we can't restore the state as
421 // none of the files in the directory are referenced any more.
422 new ClearThumbnails().execute(
423 mTabControl.getThumbnailDir().listFiles());
Grace Klobaaab3f092009-07-30 12:29:51 -0700424 // there is no quit on Android. But if we can't restore the state,
425 // we can treat it as a new Browser, remove the old session cookies.
426 CookieManager.getInstance().removeSessionCookie();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800427 final Intent intent = getIntent();
428 final Bundle extra = intent.getExtras();
429 // Create an initial tab.
430 // If the intent is ACTION_VIEW and data is not null, the Browser is
431 // invoked to view the content by another application. In this case,
432 // the tab will be close when exit.
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700433 UrlData urlData = getUrlDataFromIntent(intent);
434
The Android Open Source Project0c908882009-03-03 19:32:16 -0800435 final TabControl.Tab t = mTabControl.createNewTab(
436 Intent.ACTION_VIEW.equals(intent.getAction()) &&
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700437 intent.getData() != null,
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700438 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800439 mTabControl.setCurrentTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800440 attachTabToContentView(t);
441 WebView webView = t.getWebView();
442 if (extra != null) {
443 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
444 if (scale > 0 && scale <= 1000) {
445 webView.setInitialScale(scale);
446 }
447 }
448 // If we are not restoring from an icicle, then there is a high
449 // likely hood this is the first run. So, check to see if the
450 // homepage needs to be configured and copy any plugins from our
451 // asset directory to the data partition.
452 if ((extra == null || !extra.getBoolean("testing"))
453 && !mSettings.isLoginInitialized()) {
454 setupHomePage();
455 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800456
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700457 if (urlData.isEmpty()) {
Leon Scroggins30444232009-09-04 18:36:20 -0400458 if (mSettings.isLoginInitialized()) {
459 webView.loadUrl(mSettings.getHomePage());
460 } else {
461 waitForCredentials();
462 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800463 } else {
Grace Kloba81678d92009-06-30 07:09:56 -0700464 if (extra != null) {
465 urlData.setPostData(extra
466 .getByteArray(Browser.EXTRA_POST_DATA));
467 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700468 urlData.loadIn(webView);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800469 }
470 } else {
471 // TabControl.restoreState() will create a new tab even if
Leon Scroggins1f005d32009-08-10 17:36:42 -0400472 // restoring the state fails.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800473 attachTabToContentView(mTabControl.getCurrentTab());
474 }
Grace Kloba615c6c92009-08-03 10:22:44 -0700475
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -0400476 mTitleBar.init(this);
477 // Create title bars for all of the tabs that have been created
478 for (int i = 0; i < mTabControl.getTabCount(); i ++) {
479 WebView view = mTabControl.getTab(i).getWebView();
480 mTitleBar.addTab(view, false);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400481 }
482
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -0400483 mTitleBar.setCurrentTab(mTabControl.getCurrentIndex());
484
Feng Qianb3c02da2009-06-29 15:58:08 -0700485 // Read JavaScript flags if it exists.
486 String jsFlags = mSettings.getJsFlags();
487 if (jsFlags.trim().length() != 0) {
488 mTabControl.getCurrentWebView().setJsFlags(jsFlags);
489 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800490 }
491
492 @Override
493 protected void onNewIntent(Intent intent) {
494 TabControl.Tab current = mTabControl.getCurrentTab();
495 // When a tab is closed on exit, the current tab index is set to -1.
496 // Reset before proceed as Browser requires the current tab to be set.
497 if (current == null) {
498 // Try to reset the tab in case the index was incorrect.
499 current = mTabControl.getTab(0);
500 if (current == null) {
501 // No tabs at all so just ignore this intent.
502 return;
503 }
504 mTabControl.setCurrentTab(current);
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -0400505 mTitleBar.setCurrentTab(mTabControl.getTabIndex(current));
The Android Open Source Project0c908882009-03-03 19:32:16 -0800506 attachTabToContentView(current);
507 resetTitleAndIcon(current.getWebView());
508 }
509 final String action = intent.getAction();
510 final int flags = intent.getFlags();
511 if (Intent.ACTION_MAIN.equals(action) ||
512 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
513 // just resume the browser
514 return;
515 }
516 if (Intent.ACTION_VIEW.equals(action)
517 || Intent.ACTION_SEARCH.equals(action)
518 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
519 || Intent.ACTION_WEB_SEARCH.equals(action)) {
Satish Sampath565505b2009-05-29 15:37:27 +0100520 // If this was a search request (e.g. search query directly typed into the address bar),
521 // pass it on to the default web search provider.
522 if (handleWebSearchIntent(intent)) {
523 return;
524 }
525
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700526 UrlData urlData = getUrlDataFromIntent(intent);
527 if (urlData.isEmpty()) {
528 urlData = new UrlData(mSettings.getHomePage());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800529 }
Grace Kloba81678d92009-06-30 07:09:56 -0700530 urlData.setPostData(intent
531 .getByteArrayExtra(Browser.EXTRA_POST_DATA));
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700532
Grace Klobacc634032009-07-28 15:58:19 -0700533 final String appId = intent
534 .getStringExtra(Browser.EXTRA_APPLICATION_ID);
535 if (Intent.ACTION_VIEW.equals(action)
536 && !getPackageName().equals(appId)
537 && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
Patrick Scottcd115892009-07-16 09:42:58 -0400538 TabControl.Tab appTab = mTabControl.getTabFromId(appId);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700539 if (appTab != null) {
540 Log.i(LOGTAG, "Reusing tab for " + appId);
541 // Dismiss the subwindow if applicable.
542 dismissSubWindow(appTab);
543 // Since we might kill the WebView, remove it from the
544 // content view first.
545 removeTabFromContentView(appTab);
546 // Recreate the main WebView after destroying the old one.
547 // If the WebView has the same original url and is on that
548 // page, it can be reused.
549 boolean needsLoad =
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700550 mTabControl.recreateWebView(appTab, urlData.mUrl);
Ben Murdochbff2d602009-07-01 20:19:05 +0100551
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700552 if (current != appTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -0400553 switchToTab(mTabControl.getTabIndex(appTab));
554 if (needsLoad) {
555 urlData.loadIn(appTab.getWebView());
556 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700557 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -0400558 // If the tab was the current tab, we have to attach
559 // it to the view system again.
560 attachTabToContentView(appTab);
561 if (needsLoad) {
562 urlData.loadIn(appTab.getWebView());
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700563 }
564 }
565 return;
Patrick Scottcd115892009-07-16 09:42:58 -0400566 } else {
567 // No matching application tab, try to find a regular tab
568 // with a matching url.
569 appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
Leon Scroggins25515f82009-08-19 15:31:58 -0400570 if (appTab != null) {
571 if (current != appTab) {
572 switchToTab(mTabControl.getTabIndex(appTab));
573 }
574 // Otherwise, we are already viewing the correct tab.
Patrick Scottcd115892009-07-16 09:42:58 -0400575 } else {
576 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
577 // will be opened in a new tab unless we have reached
578 // MAX_TABS. Then the url will be opened in the current
579 // tab. If a new tab is created, it will have "true" for
580 // exit on close.
Leon Scroggins1f005d32009-08-10 17:36:42 -0400581 openTabAndShow(urlData, true, appId);
Patrick Scottcd115892009-07-16 09:42:58 -0400582 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700583 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800584 } else {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700585 if ("about:debug".equals(urlData.mUrl)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800586 mSettings.toggleDebugSettings();
587 return;
588 }
Leon Scroggins1f005d32009-08-10 17:36:42 -0400589 // Get rid of the subwindow if it exists
590 dismissSubWindow(current);
591 urlData.loadIn(current.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800592 }
593 }
594 }
595
Satish Sampath565505b2009-05-29 15:37:27 +0100596 private int parseUrlShortcut(String url) {
597 if (url == null) return SHORTCUT_INVALID;
598
599 // FIXME: quick search, need to be customized by setting
600 if (url.length() > 2 && url.charAt(1) == ' ') {
601 switch (url.charAt(0)) {
602 case 'g': return SHORTCUT_GOOGLE_SEARCH;
603 case 'w': return SHORTCUT_WIKIPEDIA_SEARCH;
604 case 'd': return SHORTCUT_DICTIONARY_SEARCH;
605 case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH;
606 }
607 }
608 return SHORTCUT_INVALID;
609 }
610
611 /**
612 * Launches the default web search activity with the query parameters if the given intent's data
613 * are identified as plain search terms and not URLs/shortcuts.
614 * @return true if the intent was handled and web search activity was launched, false if not.
615 */
616 private boolean handleWebSearchIntent(Intent intent) {
617 if (intent == null) return false;
618
619 String url = null;
620 final String action = intent.getAction();
621 if (Intent.ACTION_VIEW.equals(action)) {
622 url = intent.getData().toString();
623 } else if (Intent.ACTION_SEARCH.equals(action)
624 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
625 || Intent.ACTION_WEB_SEARCH.equals(action)) {
626 url = intent.getStringExtra(SearchManager.QUERY);
627 }
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100628 return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA));
Satish Sampath565505b2009-05-29 15:37:27 +0100629 }
630
631 /**
632 * Launches the default web search activity with the query parameters if the given url string
633 * was identified as plain search terms and not URL/shortcut.
634 * @return true if the request was handled and web search activity was launched, false if not.
635 */
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100636 private boolean handleWebSearchRequest(String inUrl, Bundle appData) {
Satish Sampath565505b2009-05-29 15:37:27 +0100637 if (inUrl == null) return false;
638
639 // In general, we shouldn't modify URL from Intent.
640 // But currently, we get the user-typed URL from search box as well.
641 String url = fixUrl(inUrl).trim();
642
643 // URLs and site specific search shortcuts are handled by the regular flow of control, so
644 // return early.
645 if (Regex.WEB_URL_PATTERN.matcher(url).matches()
Satish Sampathbc5b9f32009-06-04 18:21:40 +0100646 || ACCEPTED_URI_SCHEMA.matcher(url).matches()
Satish Sampath565505b2009-05-29 15:37:27 +0100647 || parseUrlShortcut(url) != SHORTCUT_INVALID) {
648 return false;
649 }
650
651 Browser.updateVisitedHistory(mResolver, url, false);
652 Browser.addSearchUrl(mResolver, url);
653
654 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
655 intent.addCategory(Intent.CATEGORY_DEFAULT);
656 intent.putExtra(SearchManager.QUERY, url);
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100657 if (appData != null) {
658 intent.putExtra(SearchManager.APP_DATA, appData);
659 }
Grace Klobacc634032009-07-28 15:58:19 -0700660 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
Satish Sampath565505b2009-05-29 15:37:27 +0100661 startActivity(intent);
662
663 return true;
664 }
665
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700666 private UrlData getUrlDataFromIntent(Intent intent) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800667 String url = null;
668 if (intent != null) {
669 final String action = intent.getAction();
670 if (Intent.ACTION_VIEW.equals(action)) {
671 url = smartUrlFilter(intent.getData());
672 if (url != null && url.startsWith("content:")) {
673 /* Append mimetype so webview knows how to display */
674 String mimeType = intent.resolveType(getContentResolver());
675 if (mimeType != null) {
676 url += "?" + mimeType;
677 }
678 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700679 if ("inline:".equals(url)) {
680 return new InlinedUrlData(
681 intent.getStringExtra(Browser.EXTRA_INLINE_CONTENT),
682 intent.getType(),
683 intent.getStringExtra(Browser.EXTRA_INLINE_ENCODING),
684 intent.getStringExtra(Browser.EXTRA_INLINE_FAILURL));
685 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800686 } else if (Intent.ACTION_SEARCH.equals(action)
687 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
688 || Intent.ACTION_WEB_SEARCH.equals(action)) {
689 url = intent.getStringExtra(SearchManager.QUERY);
690 if (url != null) {
691 mLastEnteredUrl = url;
692 // Don't add Urls, just search terms.
693 // Urls will get added when the page is loaded.
694 if (!Regex.WEB_URL_PATTERN.matcher(url).matches()) {
695 Browser.updateVisitedHistory(mResolver, url, false);
696 }
697 // In general, we shouldn't modify URL from Intent.
698 // But currently, we get the user-typed URL from search box as well.
699 url = fixUrl(url);
700 url = smartUrlFilter(url);
701 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
702 if (url.contains(searchSource)) {
703 String source = null;
704 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
705 if (appData != null) {
706 source = appData.getString(SearchManager.SOURCE);
707 }
708 if (TextUtils.isEmpty(source)) {
709 source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
710 }
711 url = url.replace(searchSource, "&source=android-"+source+"&");
712 }
713 }
714 }
715 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700716 return new UrlData(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800717 }
718
719 /* package */ static String fixUrl(String inUrl) {
Cary Clark652ff872009-09-10 13:34:44 -0400720 // FIXME: Converting the url to lower case
721 // duplicates functionality in smartUrlFilter().
722 // However, changing all current callers of fixUrl to
723 // call smartUrlFilter in addition may have unwanted
724 // consequences, and is deferred for now.
725 int colon = inUrl.indexOf(':');
726 boolean allLower = true;
727 for (int index = 0; index < colon; index++) {
728 char ch = inUrl.charAt(index);
729 if (!Character.isLetter(ch)) {
730 break;
731 }
732 allLower &= Character.isLowerCase(ch);
733 if (index == colon - 1 && !allLower) {
734 inUrl = inUrl.substring(0, colon).toLowerCase()
735 + inUrl.substring(colon);
736 }
737 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800738 if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
739 return inUrl;
740 if (inUrl.startsWith("http:") ||
741 inUrl.startsWith("https:")) {
742 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
743 inUrl = inUrl.replaceFirst("/", "//");
744 } else inUrl = inUrl.replaceFirst(":", "://");
745 }
746 return inUrl;
747 }
748
749 /**
750 * Looking for the pattern like this
751 *
752 * *
753 * * *
754 * *** * *******
755 * * *
756 * * *
757 * *
758 */
759 private final SensorListener mSensorListener = new SensorListener() {
760 private long mLastGestureTime;
761 private float[] mPrev = new float[3];
762 private float[] mPrevDiff = new float[3];
763 private float[] mDiff = new float[3];
764 private float[] mRevertDiff = new float[3];
765
766 public void onSensorChanged(int sensor, float[] values) {
767 boolean show = false;
768 float[] diff = new float[3];
769
770 for (int i = 0; i < 3; i++) {
771 diff[i] = values[i] - mPrev[i];
772 if (Math.abs(diff[i]) > 1) {
773 show = true;
774 }
775 if ((diff[i] > 1.0 && mDiff[i] < 0.2)
776 || (diff[i] < -1.0 && mDiff[i] > -0.2)) {
777 // start track when there is a big move, or revert
778 mRevertDiff[i] = mDiff[i];
779 mDiff[i] = 0;
780 } else if (diff[i] > -0.2 && diff[i] < 0.2) {
781 // reset when it is flat
782 mDiff[i] = mRevertDiff[i] = 0;
783 }
784 mDiff[i] += diff[i];
785 mPrevDiff[i] = diff[i];
786 mPrev[i] = values[i];
787 }
788
789 if (false) {
790 // only shows if we think the delta is big enough, in an attempt
791 // to detect "serious" moves left/right or up/down
792 Log.d("BrowserSensorHack", "sensorChanged " + sensor + " ("
793 + values[0] + ", " + values[1] + ", " + values[2] + ")"
794 + " diff(" + diff[0] + " " + diff[1] + " " + diff[2]
795 + ")");
796 Log.d("BrowserSensorHack", " mDiff(" + mDiff[0] + " "
797 + mDiff[1] + " " + mDiff[2] + ")" + " mRevertDiff("
798 + mRevertDiff[0] + " " + mRevertDiff[1] + " "
799 + mRevertDiff[2] + ")");
800 }
801
802 long now = android.os.SystemClock.uptimeMillis();
803 if (now - mLastGestureTime > 1000) {
804 mLastGestureTime = 0;
805
806 float y = mDiff[1];
807 float z = mDiff[2];
808 float ay = Math.abs(y);
809 float az = Math.abs(z);
810 float ry = mRevertDiff[1];
811 float rz = mRevertDiff[2];
812 float ary = Math.abs(ry);
813 float arz = Math.abs(rz);
814 boolean gestY = ay > 2.5f && ary > 1.0f && ay > ary;
815 boolean gestZ = az > 3.5f && arz > 1.0f && az > arz;
816
817 if ((gestY || gestZ) && !(gestY && gestZ)) {
818 WebView view = mTabControl.getCurrentWebView();
819
820 if (view != null) {
821 if (gestZ) {
822 if (z < 0) {
823 view.zoomOut();
824 } else {
825 view.zoomIn();
826 }
827 } else {
828 view.flingScroll(0, Math.round(y * 100));
829 }
830 }
831 mLastGestureTime = now;
832 }
833 }
834 }
835
836 public void onAccuracyChanged(int sensor, int accuracy) {
837 // TODO Auto-generated method stub
838
839 }
840 };
841
842 @Override protected void onResume() {
843 super.onResume();
Dave Bort31a6d1c2009-04-13 15:56:49 -0700844 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800845 Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
846 }
847
848 if (!mActivityInPause) {
849 Log.e(LOGTAG, "BrowserActivity is already resumed.");
850 return;
851 }
852
Mike Reed7bfa63b2009-05-28 11:08:32 -0400853 mTabControl.resumeCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800854 mActivityInPause = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400855 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800856
857 if (mWakeLock.isHeld()) {
858 mHandler.removeMessages(RELEASE_WAKELOCK);
859 mWakeLock.release();
860 }
861
862 if (mCredsDlg != null) {
863 if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
864 // In case credential request never comes back
865 mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
866 }
867 }
868
869 registerReceiver(mNetworkStateIntentReceiver,
870 mNetworkStateChangedFilter);
871 WebView.enablePlatformNotifications();
872
873 if (mSettings.doFlick()) {
874 if (mSensorManager == null) {
875 mSensorManager = (SensorManager) getSystemService(
876 Context.SENSOR_SERVICE);
877 }
878 mSensorManager.registerListener(mSensorListener,
879 SensorManager.SENSOR_ACCELEROMETER,
880 SensorManager.SENSOR_DELAY_FASTEST);
881 } else {
882 mSensorManager = null;
883 }
884 }
885
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -0400886 /**
887 * Since the actual title bar is embedded in the WebView, and removing it
888 * would change its appearance, create a temporary title bar to go at
889 * the top of the screen while the menu is open.
890 */
891 private TitleBar mFakeTitleBar;
892
893 /**
894 * Keeps track of whether the options menu is open. This is important in
895 * determining whether to show or hide the title bar overlay.
896 */
897 private boolean mOptionsMenuOpen;
898
899 /**
900 * Only meaningful when mOptionsMenuOpen is true. This variable keeps track
901 * of whether the configuration has changed. The first onMenuOpened call
902 * after a configuration change is simply a reopening of the same menu
903 * (i.e. mIconView did not change).
904 */
905 private boolean mConfigChanged;
906
907 /**
908 * Whether or not the options menu is in its smaller, icon menu form. When
909 * true, we want the title bar overlay to be up. When false, we do not.
910 * Only meaningful if mOptionsMenuOpen is true.
911 */
912 private boolean mIconView;
913
Leon Scrogginsa81a7642009-08-31 17:05:41 -0400914 @Override
915 public boolean onMenuOpened(int featureId, Menu menu) {
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -0400916 if (Window.FEATURE_OPTIONS_PANEL == featureId) {
917 if (mOptionsMenuOpen) {
918 if (mConfigChanged) {
919 // We do not need to make any changes to the state of the
920 // title bar, since the only thing that happened was a
921 // change in orientation
922 mConfigChanged = false;
923 } else {
924 if (mIconView) {
925 // Switching the menu to expanded view, so hide the
926 // title bar.
927 hideFakeTitleBar();
928 mIconView = false;
929 } else {
930 // Switching the menu back to icon view, so show the
931 // title bar once again.
932 showFakeTitleBar();
933 mIconView = true;
934 }
935 }
936 } else {
937 // The options menu is closed, so open it, and show the title
938 showFakeTitleBar();
939 mOptionsMenuOpen = true;
940 mConfigChanged = false;
941 mIconView = true;
942 }
943 }
Leon Scrogginsa81a7642009-08-31 17:05:41 -0400944 return true;
945 }
946
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -0400947 private void showFakeTitleBar() {
948 if (mFakeTitleBar == null) {
949 WebView webView = getTopWindow();
950 mFakeTitleBar = new TitleBar(this, webView);
951 mFakeTitleBar.setTitleAndUrl(null, webView.getUrl());
952 mFakeTitleBar.setProgress(webView.getProgress());
953 mFakeTitleBar.setFavicon(webView.getFavicon());
954 updateLockIconToLatest();
955 View title = mFakeTitleBar.findViewById(R.id.title);
956 title.setOnClickListener(new View.OnClickListener() {
957 public void onClick(View v) {
958 onSearchRequested();
959 closeOptionsMenu();
960 }
961 });
962
963 WindowManager manager
964 = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
965
966 // Add the title bar to the window manager so it can receive touches
967 // while the menu is up
968 WindowManager.LayoutParams params
969 = new WindowManager.LayoutParams(
970 ViewGroup.LayoutParams.FILL_PARENT,
971 ViewGroup.LayoutParams.WRAP_CONTENT,
972 WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL,
973 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
974 PixelFormat.OPAQUE);
975 params.gravity = Gravity.TOP;
976 params.windowAnimations = getTopWindow().getScrollY() == 0 ? 0
977 : com.android.internal.R.style.Animation_DropDownDown;
978 // XXX : Without providing an offset, the fake title bar will be
979 // placed underneath the status bar. Use the global visible rect
980 // of mBrowserFrameLayout to determine the bottom of the status bar
981 Rect rectangle = new Rect();
982 mBrowserFrameLayout.getGlobalVisibleRect(rectangle);
983 params.y = rectangle.top;
984 manager.addView(mFakeTitleBar, params);
985 }
986 }
987
988 @Override
989 public void onOptionsMenuClosed(Menu menu) {
990 mOptionsMenuOpen = false;
991 hideFakeTitleBar();
992 }
993 private void hideFakeTitleBar() {
994 if (mFakeTitleBar == null) return;
995 WindowManager manager
996 = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
997 manager.removeView(mFakeTitleBar);
998 mFakeTitleBar = null;
999 }
1000
The Android Open Source Project0c908882009-03-03 19:32:16 -08001001 /**
1002 * onSaveInstanceState(Bundle map)
1003 * onSaveInstanceState is called right before onStop(). The map contains
1004 * the saved state.
1005 */
1006 @Override protected void onSaveInstanceState(Bundle outState) {
Dave Bort31a6d1c2009-04-13 15:56:49 -07001007 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001008 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
1009 }
1010 // the default implementation requires each view to have an id. As the
1011 // browser handles the state itself and it doesn't use id for the views,
1012 // don't call the default implementation. Otherwise it will trigger the
1013 // warning like this, "couldn't save which view has focus because the
1014 // focused view XXX has no id".
1015
1016 // Save all the tabs
1017 mTabControl.saveState(outState);
1018 }
1019
1020 @Override protected void onPause() {
1021 super.onPause();
1022
1023 if (mActivityInPause) {
1024 Log.e(LOGTAG, "BrowserActivity is already paused.");
1025 return;
1026 }
1027
Mike Reed7bfa63b2009-05-28 11:08:32 -04001028 mTabControl.pauseCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001029 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04001030 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001031 mWakeLock.acquire();
1032 mHandler.sendMessageDelayed(mHandler
1033 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
1034 }
1035
1036 // Clear the credentials toast if it is up
1037 if (mCredsDlg != null && mCredsDlg.isShowing()) {
1038 mCredsDlg.dismiss();
1039 }
1040 mCredsDlg = null;
1041
Leon Scrogginsa2ab6a72009-09-11 11:49:52 -04001042 // FIXME: This removes the active tabs page and resets the menu to
1043 // MAIN_MENU. A better solution might be to do this work in onNewIntent
1044 // but then we would need to save it in onSaveInstanceState and restore
1045 // it in onCreate/onRestoreInstanceState
1046 if (mActiveTabsPage != null) {
1047 removeActiveTabPage(true);
1048 }
1049
The Android Open Source Project0c908882009-03-03 19:32:16 -08001050 cancelStopToast();
1051
1052 // unregister network state listener
1053 unregisterReceiver(mNetworkStateIntentReceiver);
1054 WebView.disablePlatformNotifications();
1055
1056 if (mSensorManager != null) {
1057 mSensorManager.unregisterListener(mSensorListener);
1058 }
1059 }
1060
1061 @Override protected void onDestroy() {
Dave Bort31a6d1c2009-04-13 15:56:49 -07001062 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001063 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
1064 }
1065 super.onDestroy();
1066 // Remove the current tab and sub window
1067 TabControl.Tab t = mTabControl.getCurrentTab();
Patrick Scottfb5e77f2009-04-08 19:17:37 -07001068 if (t != null) {
1069 dismissSubWindow(t);
1070 removeTabFromContentView(t);
1071 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001072 // Destroy all the tabs
1073 mTabControl.destroy();
1074 WebIconDatabase.getInstance().close();
1075 if (mGlsConnection != null) {
1076 unbindService(mGlsConnection);
1077 mGlsConnection = null;
1078 }
1079
1080 //
1081 // stop MASF proxy service
1082 //
1083 //Intent proxyServiceIntent = new Intent();
1084 //proxyServiceIntent.setComponent
1085 // (new ComponentName(
1086 // "com.android.masfproxyservice",
1087 // "com.android.masfproxyservice.MasfProxyService"));
1088 //stopService(proxyServiceIntent);
Grace Klobab4da0ad2009-05-14 14:45:40 -07001089
1090 unregisterReceiver(mPackageInstallationReceiver);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001091 }
1092
1093 @Override
1094 public void onConfigurationChanged(Configuration newConfig) {
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04001095 mConfigChanged = true;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001096 super.onConfigurationChanged(newConfig);
1097
1098 if (mPageInfoDialog != null) {
1099 mPageInfoDialog.dismiss();
1100 showPageInfo(
1101 mPageInfoView,
1102 mPageInfoFromShowSSLCertificateOnError.booleanValue());
1103 }
1104 if (mSSLCertificateDialog != null) {
1105 mSSLCertificateDialog.dismiss();
1106 showSSLCertificate(
1107 mSSLCertificateView);
1108 }
1109 if (mSSLCertificateOnErrorDialog != null) {
1110 mSSLCertificateOnErrorDialog.dismiss();
1111 showSSLCertificateOnError(
1112 mSSLCertificateOnErrorView,
1113 mSSLCertificateOnErrorHandler,
1114 mSSLCertificateOnErrorError);
1115 }
1116 if (mHttpAuthenticationDialog != null) {
1117 String title = ((TextView) mHttpAuthenticationDialog
1118 .findViewById(com.android.internal.R.id.alertTitle)).getText()
1119 .toString();
1120 String name = ((TextView) mHttpAuthenticationDialog
1121 .findViewById(R.id.username_edit)).getText().toString();
1122 String password = ((TextView) mHttpAuthenticationDialog
1123 .findViewById(R.id.password_edit)).getText().toString();
1124 int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1125 .getId();
1126 mHttpAuthenticationDialog.dismiss();
1127 showHttpAuthentication(mHttpAuthHandler, null, null, title,
1128 name, password, focusId);
1129 }
1130 if (mFindDialog != null && mFindDialog.isShowing()) {
1131 mFindDialog.onConfigurationChanged(newConfig);
1132 }
1133 }
1134
1135 @Override public void onLowMemory() {
1136 super.onLowMemory();
1137 mTabControl.freeMemory();
1138 }
1139
Mike Reed7bfa63b2009-05-28 11:08:32 -04001140 private boolean resumeWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001141 if ((!mActivityInPause && !mPageStarted) ||
1142 (mActivityInPause && mPageStarted)) {
1143 CookieSyncManager.getInstance().startSync();
1144 WebView w = mTabControl.getCurrentWebView();
1145 if (w != null) {
1146 w.resumeTimers();
1147 }
1148 return true;
1149 } else {
1150 return false;
1151 }
1152 }
1153
Mike Reed7bfa63b2009-05-28 11:08:32 -04001154 private boolean pauseWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001155 if (mActivityInPause && !mPageStarted) {
1156 CookieSyncManager.getInstance().stopSync();
1157 WebView w = mTabControl.getCurrentWebView();
1158 if (w != null) {
1159 w.pauseTimers();
1160 }
1161 return true;
1162 } else {
1163 return false;
1164 }
1165 }
1166
Leon Scroggins1f005d32009-08-10 17:36:42 -04001167 // FIXME: Do we want to call this when loading google for the first time?
The Android Open Source Project0c908882009-03-03 19:32:16 -08001168 /*
1169 * This function is called when we are launching for the first time. We
1170 * are waiting for the login credentials before loading Google home
1171 * pages. This way the user will be logged in straight away.
1172 */
1173 private void waitForCredentials() {
1174 // Show a toast
1175 mCredsDlg = new ProgressDialog(this);
1176 mCredsDlg.setIndeterminate(true);
1177 mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1178 // If the user cancels the operation, then cancel the Google
1179 // Credentials request.
1180 mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1181 mCredsDlg.show();
1182
1183 // We set a timeout for the retrieval of credentials in onResume()
1184 // as that is when we have freed up some CPU time to get
1185 // the login credentials.
1186 }
1187
1188 /*
1189 * If we have received the credentials or we have timed out and we are
1190 * showing the credentials dialog, then it is time to move on.
1191 */
1192 private void resumeAfterCredentials() {
1193 if (mCredsDlg == null) {
1194 return;
1195 }
1196
1197 // Clear the toast
1198 if (mCredsDlg.isShowing()) {
1199 mCredsDlg.dismiss();
1200 }
1201 mCredsDlg = null;
1202
1203 // Clear any pending timeout
1204 mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1205
1206 // Load the page
1207 WebView w = mTabControl.getCurrentWebView();
1208 if (w != null) {
1209 w.loadUrl(mSettings.getHomePage());
1210 }
1211
1212 // Update the settings, need to do this last as it can take a moment
1213 // to persist the settings. In the mean time we could be loading
1214 // content.
1215 mSettings.setLoginInitialized(this);
1216 }
1217
1218 // Open the icon database and retain all the icons for visited sites.
1219 private void retainIconsOnStartup() {
1220 final WebIconDatabase db = WebIconDatabase.getInstance();
1221 db.open(getDir("icons", 0).getPath());
1222 try {
1223 Cursor c = Browser.getAllBookmarks(mResolver);
1224 if (!c.moveToFirst()) {
1225 c.deactivate();
1226 return;
1227 }
1228 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1229 do {
1230 String url = c.getString(urlIndex);
1231 db.retainIconForPageUrl(url);
1232 } while (c.moveToNext());
1233 c.deactivate();
1234 } catch (IllegalStateException e) {
1235 Log.e(LOGTAG, "retainIconsOnStartup", e);
1236 }
1237 }
1238
1239 // Helper method for getting the top window.
1240 WebView getTopWindow() {
1241 return mTabControl.getCurrentTopWebView();
1242 }
1243
1244 @Override
1245 public boolean onCreateOptionsMenu(Menu menu) {
1246 super.onCreateOptionsMenu(menu);
1247
1248 MenuInflater inflater = getMenuInflater();
1249 inflater.inflate(R.menu.browser, menu);
1250 mMenu = menu;
1251 updateInLoadMenuItems();
1252 return true;
1253 }
1254
1255 /**
1256 * As the menu can be open when loading state changes
1257 * we must manually update the state of the stop/reload menu
1258 * item
1259 */
1260 private void updateInLoadMenuItems() {
1261 if (mMenu == null) {
1262 return;
1263 }
1264 MenuItem src = mInLoad ?
1265 mMenu.findItem(R.id.stop_menu_id):
1266 mMenu.findItem(R.id.reload_menu_id);
1267 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1268 dest.setIcon(src.getIcon());
1269 dest.setTitle(src.getTitle());
1270 }
1271
1272 @Override
1273 public boolean onContextItemSelected(MenuItem item) {
1274 // chording is not an issue with context menus, but we use the same
1275 // options selector, so set mCanChord to true so we can access them.
1276 mCanChord = true;
1277 int id = item.getItemId();
1278 final WebView webView = getTopWindow();
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001279 if (null == webView) {
1280 return false;
1281 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001282 final HashMap hrefMap = new HashMap();
1283 hrefMap.put("webview", webView);
1284 final Message msg = mHandler.obtainMessage(
1285 FOCUS_NODE_HREF, id, 0, hrefMap);
1286 switch (id) {
1287 // -- Browser context menu
1288 case R.id.open_context_menu_id:
1289 case R.id.open_newtab_context_menu_id:
1290 case R.id.bookmark_context_menu_id:
1291 case R.id.save_link_context_menu_id:
1292 case R.id.share_link_context_menu_id:
1293 case R.id.copy_link_context_menu_id:
1294 webView.requestFocusNodeHref(msg);
1295 break;
1296
1297 default:
1298 // For other context menus
1299 return onOptionsItemSelected(item);
1300 }
1301 mCanChord = false;
1302 return true;
1303 }
1304
1305 private Bundle createGoogleSearchSourceBundle(String source) {
1306 Bundle bundle = new Bundle();
1307 bundle.putString(SearchManager.SOURCE, source);
1308 return bundle;
1309 }
1310
1311 /**
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001312 * Overriding this to insert a local information bundle
The Android Open Source Project0c908882009-03-03 19:32:16 -08001313 */
1314 @Override
1315 public boolean onSearchRequested() {
Leon Scroggins5bbe9802009-07-31 13:10:55 -04001316 String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
Grace Kloba83f47342009-07-20 10:44:31 -07001317 startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001318 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001319 return true;
1320 }
1321
1322 @Override
1323 public void startSearch(String initialQuery, boolean selectInitialQuery,
1324 Bundle appSearchData, boolean globalSearch) {
1325 if (appSearchData == null) {
1326 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1327 }
1328 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1329 }
1330
Leon Scroggins1f005d32009-08-10 17:36:42 -04001331 /**
1332 * Switch tabs. Called by the TitleBarSet when sliding the title bar
1333 * results in changing tabs.
Leon Scroggins160a7e72009-08-14 18:28:01 -04001334 * @param index Index of the tab to change to, as defined by
1335 * mTabControl.getTabIndex(Tab t).
1336 * @return boolean True if we successfully switched to a different tab. If
1337 * the indexth tab is null, or if that tab is the same as
1338 * the current one, return false.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001339 */
Leon Scroggins160a7e72009-08-14 18:28:01 -04001340 /* package */ boolean switchToTab(int index) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001341 TabControl.Tab tab = mTabControl.getTab(index);
1342 TabControl.Tab currentTab = mTabControl.getCurrentTab();
1343 if (tab == null || tab == currentTab) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001344 return false;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001345 }
1346 if (currentTab != null) {
1347 // currentTab may be null if it was just removed. In that case,
1348 // we do not need to remove it
1349 removeTabFromContentView(currentTab);
1350 }
1351 removeTabFromContentView(tab);
1352 mTabControl.setCurrentTab(tab);
1353 attachTabToContentView(tab);
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04001354 mTitleBar.setCurrentTab(index);
Leon Scroggins160a7e72009-08-14 18:28:01 -04001355 return true;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001356 }
1357
Leon Scroggins0a64ba52009-09-08 15:35:33 -04001358 /* package */ TabControl.Tab openTabToHomePage() {
1359 return openTabAndShow(mSettings.getHomePage(), false, null);
1360 }
1361
Leon Scroggins1f005d32009-08-10 17:36:42 -04001362 /* package */ void closeCurrentWindow() {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001363 final TabControl.Tab current = mTabControl.getCurrentTab();
Leon Scroggins160a7e72009-08-14 18:28:01 -04001364 if (mTabControl.getTabCount() == 1) {
Leon Scroggins30444232009-09-04 18:36:20 -04001365 // This is the last tab. Open a new one, with the home
1366 // page and close the current one.
Leon Scroggins0a64ba52009-09-08 15:35:33 -04001367 TabControl.Tab newTab = openTabToHomePage();
Leon Scroggins160a7e72009-08-14 18:28:01 -04001368 closeTab(current);
Leon Scroggins160a7e72009-08-14 18:28:01 -04001369 return;
1370 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001371 final TabControl.Tab parent = current.getParentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04001372 int indexToShow = -1;
1373 if (parent != null) {
1374 indexToShow = mTabControl.getTabIndex(parent);
1375 } else {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001376 final int currentIndex = mTabControl.getCurrentIndex();
1377 // Try to move to the tab to the right
1378 indexToShow = currentIndex + 1;
1379 if (indexToShow > mTabControl.getTabCount() - 1) {
1380 // Try to move to the tab to the left
1381 indexToShow = currentIndex - 1;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001382 }
1383 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001384 if (switchToTab(indexToShow)) {
1385 // Close window
1386 closeTab(current);
1387 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001388 }
1389
Leon Scroggins0a64ba52009-09-08 15:35:33 -04001390 private ActiveTabsPage mActiveTabsPage;
1391
1392 /**
1393 * Remove the active tabs page.
1394 * @param needToAttach If true, the active tabs page did not attach a tab
1395 * to the content view, so we need to do that here.
1396 */
1397 /* package */ void removeActiveTabPage(boolean needToAttach) {
1398 mContentView.removeView(mActiveTabsPage);
1399 mActiveTabsPage = null;
1400 mMenuState = R.id.MAIN_MENU;
1401 if (needToAttach) {
1402 attachTabToContentView(mTabControl.getCurrentTab());
1403 }
1404 getTopWindow().requestFocus();
1405 }
1406
The Android Open Source Project0c908882009-03-03 19:32:16 -08001407 @Override
1408 public boolean onOptionsItemSelected(MenuItem item) {
1409 if (!mCanChord) {
1410 // The user has already fired a shortcut with this hold down of the
1411 // menu key.
1412 return false;
1413 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001414 if (null == getTopWindow()) {
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001415 return false;
1416 }
Grace Kloba6ee9c492009-07-13 10:04:34 -07001417 if (mMenuIsDown) {
1418 // The shortcut action consumes the MENU. Even if it is still down,
1419 // it won't trigger the next shortcut action. In the case of the
1420 // shortcut action triggering a new activity, like Bookmarks, we
1421 // won't get onKeyUp for MENU. So it is important to reset it here.
1422 mMenuIsDown = false;
1423 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001424 switch (item.getItemId()) {
1425 // -- Main menu
Leon Scrogginsa81a7642009-08-31 17:05:41 -04001426 case R.id.new_tab_menu_id:
Leon Scroggins0a64ba52009-09-08 15:35:33 -04001427 openTabToHomePage();
Leon Scrogginsa81a7642009-08-31 17:05:41 -04001428 break;
1429
Leon Scroggins64b80f32009-08-07 12:03:34 -04001430 case R.id.goto_menu_id:
Leon Scroggins30444232009-09-04 18:36:20 -04001431 bookmarksOrHistoryPicker(false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001432 break;
1433
Leon Scroggins0a64ba52009-09-08 15:35:33 -04001434 case R.id.active_tabs_menu_id:
1435 mActiveTabsPage = new ActiveTabsPage(this, mTabControl);
1436 removeTabFromContentView(mTabControl.getCurrentTab());
1437 mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
1438 mActiveTabsPage.requestFocus();
1439 mMenuState = EMPTY_MENU;
1440 break;
1441
Leon Scroggins1f005d32009-08-10 17:36:42 -04001442 case R.id.add_bookmark_menu_id:
1443 Intent i = new Intent(BrowserActivity.this,
1444 AddBookmarkPage.class);
1445 WebView w = getTopWindow();
1446 i.putExtra("url", w.getUrl());
1447 i.putExtra("title", w.getTitle());
1448 startActivity(i);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001449 break;
1450
1451 case R.id.stop_reload_menu_id:
1452 if (mInLoad) {
1453 stopLoading();
1454 } else {
1455 getTopWindow().reload();
1456 }
1457 break;
1458
1459 case R.id.back_menu_id:
1460 getTopWindow().goBack();
1461 break;
1462
1463 case R.id.forward_menu_id:
1464 getTopWindow().goForward();
1465 break;
1466
1467 case R.id.close_menu_id:
1468 // Close the subwindow if it exists.
1469 if (mTabControl.getCurrentSubWindow() != null) {
1470 dismissSubWindow(mTabControl.getCurrentTab());
1471 break;
1472 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001473 closeCurrentWindow();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001474 break;
1475
1476 case R.id.homepage_menu_id:
1477 TabControl.Tab current = mTabControl.getCurrentTab();
1478 if (current != null) {
1479 dismissSubWindow(current);
1480 current.getWebView().loadUrl(mSettings.getHomePage());
1481 }
1482 break;
1483
1484 case R.id.preferences_menu_id:
1485 Intent intent = new Intent(this,
1486 BrowserPreferencesPage.class);
1487 startActivityForResult(intent, PREFERENCES_PAGE);
1488 break;
1489
1490 case R.id.find_menu_id:
1491 if (null == mFindDialog) {
1492 mFindDialog = new FindDialog(this);
1493 }
1494 mFindDialog.setWebView(getTopWindow());
1495 mFindDialog.show();
1496 mMenuState = EMPTY_MENU;
1497 break;
1498
1499 case R.id.select_text_id:
1500 getTopWindow().emulateShiftHeld();
1501 break;
1502 case R.id.page_info_menu_id:
1503 showPageInfo(mTabControl.getCurrentTab(), false);
1504 break;
1505
1506 case R.id.classic_history_menu_id:
Leon Scroggins30444232009-09-04 18:36:20 -04001507 bookmarksOrHistoryPicker(true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001508 break;
1509
1510 case R.id.share_page_menu_id:
1511 Browser.sendString(this, getTopWindow().getUrl());
1512 break;
1513
1514 case R.id.dump_nav_menu_id:
1515 getTopWindow().debugDump();
1516 break;
1517
1518 case R.id.zoom_in_menu_id:
1519 getTopWindow().zoomIn();
1520 break;
1521
1522 case R.id.zoom_out_menu_id:
1523 getTopWindow().zoomOut();
1524 break;
1525
1526 case R.id.view_downloads_menu_id:
1527 viewDownloads(null);
1528 break;
1529
The Android Open Source Project0c908882009-03-03 19:32:16 -08001530 case R.id.window_one_menu_id:
1531 case R.id.window_two_menu_id:
1532 case R.id.window_three_menu_id:
1533 case R.id.window_four_menu_id:
1534 case R.id.window_five_menu_id:
1535 case R.id.window_six_menu_id:
1536 case R.id.window_seven_menu_id:
1537 case R.id.window_eight_menu_id:
1538 {
1539 int menuid = item.getItemId();
1540 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1541 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1542 TabControl.Tab desiredTab = mTabControl.getTab(id);
1543 if (desiredTab != null &&
1544 desiredTab != mTabControl.getCurrentTab()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001545 switchToTab(id);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001546 }
1547 break;
1548 }
1549 }
1550 }
1551 break;
1552
1553 default:
1554 if (!super.onOptionsItemSelected(item)) {
1555 return false;
1556 }
1557 // Otherwise fall through.
1558 }
1559 mCanChord = false;
1560 return true;
1561 }
1562
1563 public void closeFind() {
1564 mMenuState = R.id.MAIN_MENU;
1565 }
1566
1567 @Override public boolean onPrepareOptionsMenu(Menu menu)
1568 {
1569 // This happens when the user begins to hold down the menu key, so
1570 // allow them to chord to get a shortcut.
1571 mCanChord = true;
1572 // Note: setVisible will decide whether an item is visible; while
1573 // setEnabled() will decide whether an item is enabled, which also means
1574 // whether the matching shortcut key will function.
1575 super.onPrepareOptionsMenu(menu);
1576 switch (mMenuState) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001577 case EMPTY_MENU:
1578 if (mCurrentMenuState != mMenuState) {
1579 menu.setGroupVisible(R.id.MAIN_MENU, false);
1580 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1581 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001582 }
1583 break;
1584 default:
1585 if (mCurrentMenuState != mMenuState) {
1586 menu.setGroupVisible(R.id.MAIN_MENU, true);
1587 menu.setGroupEnabled(R.id.MAIN_MENU, true);
1588 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001589 }
1590 final WebView w = getTopWindow();
1591 boolean canGoBack = false;
1592 boolean canGoForward = false;
1593 boolean isHome = false;
1594 if (w != null) {
1595 canGoBack = w.canGoBack();
1596 canGoForward = w.canGoForward();
1597 isHome = mSettings.getHomePage().equals(w.getUrl());
1598 }
1599 final MenuItem back = menu.findItem(R.id.back_menu_id);
1600 back.setEnabled(canGoBack);
1601
1602 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1603 home.setEnabled(!isHome);
1604
1605 menu.findItem(R.id.forward_menu_id)
1606 .setEnabled(canGoForward);
1607
Leon Scrogginsa81a7642009-08-31 17:05:41 -04001608 menu.findItem(R.id.new_tab_menu_id).setEnabled(
1609 mTabControl.getTabCount() < TabControl.MAX_TABS);
1610
The Android Open Source Project0c908882009-03-03 19:32:16 -08001611 // decide whether to show the share link option
1612 PackageManager pm = getPackageManager();
1613 Intent send = new Intent(Intent.ACTION_SEND);
1614 send.setType("text/plain");
1615 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1616 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1617
The Android Open Source Project0c908882009-03-03 19:32:16 -08001618 boolean isNavDump = mSettings.isNavDump();
1619 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1620 nav.setVisible(isNavDump);
1621 nav.setEnabled(isNavDump);
1622 break;
1623 }
1624 mCurrentMenuState = mMenuState;
1625 return true;
1626 }
1627
1628 @Override
1629 public void onCreateContextMenu(ContextMenu menu, View v,
1630 ContextMenuInfo menuInfo) {
1631 WebView webview = (WebView) v;
1632 WebView.HitTestResult result = webview.getHitTestResult();
1633 if (result == null) {
1634 return;
1635 }
1636
1637 int type = result.getType();
1638 if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1639 Log.w(LOGTAG,
1640 "We should not show context menu when nothing is touched");
1641 return;
1642 }
1643 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1644 // let TextView handles context menu
1645 return;
1646 }
1647
1648 // Note, http://b/issue?id=1106666 is requesting that
1649 // an inflated menu can be used again. This is not available
1650 // yet, so inflate each time (yuk!)
1651 MenuInflater inflater = getMenuInflater();
1652 inflater.inflate(R.menu.browsercontext, menu);
1653
1654 // Show the correct menu group
1655 String extra = result.getExtra();
1656 menu.setGroupVisible(R.id.PHONE_MENU,
1657 type == WebView.HitTestResult.PHONE_TYPE);
1658 menu.setGroupVisible(R.id.EMAIL_MENU,
1659 type == WebView.HitTestResult.EMAIL_TYPE);
1660 menu.setGroupVisible(R.id.GEO_MENU,
1661 type == WebView.HitTestResult.GEO_TYPE);
1662 menu.setGroupVisible(R.id.IMAGE_MENU,
1663 type == WebView.HitTestResult.IMAGE_TYPE
1664 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1665 menu.setGroupVisible(R.id.ANCHOR_MENU,
1666 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1667 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1668
1669 // Setup custom handling depending on the type
1670 switch (type) {
1671 case WebView.HitTestResult.PHONE_TYPE:
1672 menu.setHeaderTitle(Uri.decode(extra));
1673 menu.findItem(R.id.dial_context_menu_id).setIntent(
1674 new Intent(Intent.ACTION_VIEW, Uri
1675 .parse(WebView.SCHEME_TEL + extra)));
1676 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1677 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1678 addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1679 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1680 addIntent);
1681 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1682 new Copy(extra));
1683 break;
1684
1685 case WebView.HitTestResult.EMAIL_TYPE:
1686 menu.setHeaderTitle(extra);
1687 menu.findItem(R.id.email_context_menu_id).setIntent(
1688 new Intent(Intent.ACTION_VIEW, Uri
1689 .parse(WebView.SCHEME_MAILTO + extra)));
1690 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1691 new Copy(extra));
1692 break;
1693
1694 case WebView.HitTestResult.GEO_TYPE:
1695 menu.setHeaderTitle(extra);
1696 menu.findItem(R.id.map_context_menu_id).setIntent(
1697 new Intent(Intent.ACTION_VIEW, Uri
1698 .parse(WebView.SCHEME_GEO
1699 + URLEncoder.encode(extra))));
1700 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1701 new Copy(extra));
1702 break;
1703
1704 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1705 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1706 TextView titleView = (TextView) LayoutInflater.from(this)
1707 .inflate(android.R.layout.browser_link_context_header,
1708 null);
1709 titleView.setText(extra);
1710 menu.setHeaderView(titleView);
1711 // decide whether to show the open link in new tab option
1712 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1713 mTabControl.getTabCount() < TabControl.MAX_TABS);
1714 PackageManager pm = getPackageManager();
1715 Intent send = new Intent(Intent.ACTION_SEND);
1716 send.setType("text/plain");
1717 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1718 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1719 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1720 break;
1721 }
1722 // otherwise fall through to handle image part
1723 case WebView.HitTestResult.IMAGE_TYPE:
1724 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1725 menu.setHeaderTitle(extra);
1726 }
1727 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1728 new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1729 menu.findItem(R.id.download_context_menu_id).
1730 setOnMenuItemClickListener(new Download(extra));
1731 break;
1732
1733 default:
1734 Log.w(LOGTAG, "We should not get here.");
1735 break;
1736 }
1737 }
1738
The Android Open Source Project0c908882009-03-03 19:32:16 -08001739 // Attach the given tab to the content view.
1740 private void attachTabToContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001741 // Attach the container that contains the main WebView and any other UI
1742 // associated with the tab.
1743 mContentView.addView(t.getContainer(), COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +01001744
1745 if (mShouldShowErrorConsole) {
1746 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
1747 if (errorConsole.numberOfErrors() == 0) {
1748 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1749 } else {
1750 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1751 }
1752
1753 mErrorConsoleContainer.addView(errorConsole,
1754 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
1755 ViewGroup.LayoutParams.WRAP_CONTENT));
1756 }
1757
Leon Scroggins39ab28e2009-09-02 21:20:30 -04001758 WebView view = t.getWebView();
Leon Scroggins55a5bc22009-09-04 17:00:08 -04001759 view.setEmbeddedTitleBar(mTitleBar);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001760 // Attach the sub window if necessary
1761 attachSubWindow(t);
1762 // Request focus on the top window.
1763 t.getTopWindow().requestFocus();
1764 }
1765
1766 // Attach a sub window to the main WebView of the given tab.
1767 private void attachSubWindow(TabControl.Tab t) {
1768 // If a sub window exists, attach it to the content view.
1769 final WebView subView = t.getSubWebView();
1770 if (subView != null) {
1771 final View container = t.getSubWebViewContainer();
1772 mContentView.addView(container, COVER_SCREEN_PARAMS);
1773 subView.requestFocus();
1774 }
1775 }
1776
1777 // Remove the given tab from the content view.
1778 private void removeTabFromContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001779 // Remove the container that contains the main WebView.
1780 mContentView.removeView(t.getContainer());
Ben Murdochbff2d602009-07-01 20:19:05 +01001781
1782 if (mTabControl.getCurrentErrorConsole(false) != null) {
1783 mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
1784 }
1785
Leon Scroggins39ab28e2009-09-02 21:20:30 -04001786 WebView view = t.getWebView();
Leon Scroggins55a5bc22009-09-04 17:00:08 -04001787 view.setEmbeddedTitleBar(null);
Leon Scroggins39ab28e2009-09-02 21:20:30 -04001788
The Android Open Source Project0c908882009-03-03 19:32:16 -08001789 // Remove the sub window if it exists.
1790 if (t.getSubWebView() != null) {
1791 mContentView.removeView(t.getSubWebViewContainer());
1792 }
1793 }
1794
1795 // Remove the sub window if it exists. Also called by TabControl when the
1796 // user clicks the 'X' to dismiss a sub window.
1797 /* package */ void dismissSubWindow(TabControl.Tab t) {
1798 final WebView mainView = t.getWebView();
1799 if (t.getSubWebView() != null) {
1800 // Remove the container view and request focus on the main WebView.
1801 mContentView.removeView(t.getSubWebViewContainer());
1802 mainView.requestFocus();
1803 // Tell the TabControl to dismiss the subwindow. This will destroy
1804 // the WebView.
1805 mTabControl.dismissSubWindow(t);
1806 }
1807 }
1808
Leon Scroggins1f005d32009-08-10 17:36:42 -04001809 // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001810 // that accepts url as string.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001811 private TabControl.Tab openTabAndShow(String url, boolean closeOnExit,
1812 String appId) {
1813 return openTabAndShow(new UrlData(url), closeOnExit, appId);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001814 }
1815
1816 // This method does a ton of stuff. It will attempt to create a new tab
1817 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
Leon Scroggins1f005d32009-08-10 17:36:42 -04001818 // url isn't null, it will load the given url.
1819 /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001820 boolean closeOnExit, String appId) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001821 final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1822 final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1823 if (newTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001824 final TabControl.Tab tab = mTabControl.createNewTab(
1825 closeOnExit, appId, urlData.mUrl);
1826 WebView webview = tab.getWebView();
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04001827 mTitleBar.addTab(webview, true);
Leon Scroggins0a64ba52009-09-08 15:35:33 -04001828 // If the last tab was removed from the active tabs page, currentTab
1829 // will be null.
1830 if (currentTab != null) {
1831 removeTabFromContentView(currentTab);
1832 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001833 attachTabToContentView(tab);
Patrick Scott8bbd69f2009-08-14 13:35:53 -04001834 // We must set the new tab as the current tab to reflect the old
1835 // animation behavior.
1836 mTabControl.setCurrentTab(tab);
Leon Scroggins160a7e72009-08-14 18:28:01 -04001837 if (!urlData.isEmpty()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001838 urlData.loadIn(webview);
1839 }
1840 return tab;
1841 } else {
1842 // Get rid of the subwindow if it exists
1843 dismissSubWindow(currentTab);
1844 if (!urlData.isEmpty()) {
1845 // Load the given url.
1846 urlData.loadIn(currentTab.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001847 }
1848 }
Grace Klobac9181842009-04-14 08:53:22 -07001849 return currentTab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001850 }
1851
Grace Klobac9181842009-04-14 08:53:22 -07001852 private TabControl.Tab openTab(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001853 if (mSettings.openInBackground()) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001854 TabControl.Tab t = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001855 if (t != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001856 WebView view = t.getWebView();
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04001857 mTitleBar.addTab(view, false);
Leon Scroggins1f005d32009-08-10 17:36:42 -04001858 view.loadUrl(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001859 }
Grace Klobac9181842009-04-14 08:53:22 -07001860 return t;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001861 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001862 return openTabAndShow(url, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001863 }
1864 }
1865
1866 private class Copy implements OnMenuItemClickListener {
1867 private CharSequence mText;
1868
1869 public boolean onMenuItemClick(MenuItem item) {
1870 copy(mText);
1871 return true;
1872 }
1873
1874 public Copy(CharSequence toCopy) {
1875 mText = toCopy;
1876 }
1877 }
1878
1879 private class Download implements OnMenuItemClickListener {
1880 private String mText;
1881
1882 public boolean onMenuItemClick(MenuItem item) {
1883 onDownloadStartNoStream(mText, null, null, null, -1);
1884 return true;
1885 }
1886
1887 public Download(String toDownload) {
1888 mText = toDownload;
1889 }
1890 }
1891
1892 private void copy(CharSequence text) {
1893 try {
1894 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
1895 if (clip != null) {
1896 clip.setClipboardText(text);
1897 }
1898 } catch (android.os.RemoteException e) {
1899 Log.e(LOGTAG, "Copy failed", e);
1900 }
1901 }
1902
1903 /**
1904 * Resets the browser title-view to whatever it must be (for example, if we
1905 * load a page from history).
1906 */
1907 private void resetTitle() {
1908 resetLockIcon();
1909 resetTitleIconAndProgress();
1910 }
1911
1912 /**
1913 * Resets the browser title-view to whatever it must be
1914 * (for example, if we had a loading error)
1915 * When we have a new page, we call resetTitle, when we
1916 * have to reset the titlebar to whatever it used to be
1917 * (for example, if the user chose to stop loading), we
1918 * call resetTitleAndRevertLockIcon.
1919 */
1920 /* package */ void resetTitleAndRevertLockIcon() {
1921 revertLockIcon();
1922 resetTitleIconAndProgress();
1923 }
1924
1925 /**
1926 * Reset the title, favicon, and progress.
1927 */
1928 private void resetTitleIconAndProgress() {
1929 WebView current = mTabControl.getCurrentWebView();
1930 if (current == null) {
1931 return;
1932 }
1933 resetTitleAndIcon(current);
1934 int progress = current.getProgress();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001935 mWebChromeClient.onProgressChanged(current, progress);
1936 }
1937
1938 // Reset the title and the icon based on the given item.
1939 private void resetTitleAndIcon(WebView view) {
1940 WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
1941 if (item != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001942 setUrlTitle(item.getUrl(), item.getTitle(), view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001943 setFavicon(item.getFavicon());
1944 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001945 setUrlTitle(null, null, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001946 setFavicon(null);
1947 }
1948 }
1949
1950 /**
1951 * Sets a title composed of the URL and the title string.
1952 * @param url The URL of the site being loaded.
1953 * @param title The title of the site being loaded.
1954 */
Leon Scroggins1f005d32009-08-10 17:36:42 -04001955 private void setUrlTitle(String url, String title, WebView view) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001956 mUrl = url;
1957 mTitle = title;
1958
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04001959 mTitleBar.setTitleAndUrl(title, url, view);
1960 if (mFakeTitleBar != null) {
1961 mFakeTitleBar.setTitleAndUrl(title, url);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001962 }
1963 }
1964
1965 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -08001966 * @param url The URL to build a title version of the URL from.
1967 * @return The title version of the URL or null if fails.
1968 * The title version of the URL can be either the URL hostname,
1969 * or the hostname with an "https://" prefix (for secure URLs),
1970 * or an empty string if, for example, the URL in question is a
1971 * file:// URL with no hostname.
1972 */
Leon Scroggins32e14a62009-06-11 10:26:34 -04001973 /* package */ static String buildTitleUrl(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001974 String titleUrl = null;
1975
1976 if (url != null) {
1977 try {
1978 // parse the url string
1979 URL urlObj = new URL(url);
1980 if (urlObj != null) {
1981 titleUrl = "";
1982
1983 String protocol = urlObj.getProtocol();
1984 String host = urlObj.getHost();
1985
1986 if (host != null && 0 < host.length()) {
1987 titleUrl = host;
1988 if (protocol != null) {
1989 // if a secure site, add an "https://" prefix!
1990 if (protocol.equalsIgnoreCase("https")) {
1991 titleUrl = protocol + "://" + host;
1992 }
1993 }
1994 }
1995 }
1996 } catch (MalformedURLException e) {}
1997 }
1998
1999 return titleUrl;
2000 }
2001
2002 // Set the favicon in the title bar.
2003 private void setFavicon(Bitmap icon) {
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04002004 mTitleBar.setFavicon(icon, getTopWindow());
2005 if (mFakeTitleBar != null) {
2006 mFakeTitleBar.setFavicon(icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002007 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002008 }
2009
2010 /**
2011 * Saves the current lock-icon state before resetting
2012 * the lock icon. If we have an error, we may need to
2013 * roll back to the previous state.
2014 */
2015 private void saveLockIcon() {
2016 mPrevLockType = mLockIconType;
2017 }
2018
2019 /**
2020 * Reverts the lock-icon state to the last saved state,
2021 * for example, if we had an error, and need to cancel
2022 * the load.
2023 */
2024 private void revertLockIcon() {
2025 mLockIconType = mPrevLockType;
2026
Dave Bort31a6d1c2009-04-13 15:56:49 -07002027 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002028 Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
2029 " revert lock icon to " + mLockIconType);
2030 }
2031
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04002032 updateLockIconToLatest();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002033 }
2034
Leon Scroggins1f005d32009-08-10 17:36:42 -04002035 /**
Leon Scroggins0a64ba52009-09-08 15:35:33 -04002036 * Close the tab, remove its associated title bar, and adjust mTabControl's
2037 * current tab to a valid value.
Leon Scroggins1f005d32009-08-10 17:36:42 -04002038 */
Leon Scroggins0a64ba52009-09-08 15:35:33 -04002039 /* package */ void closeTab(TabControl.Tab t) {
2040 int currentIndex = mTabControl.getCurrentIndex();
2041 int removeIndex = mTabControl.getTabIndex(t);
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04002042 mTitleBar.removeTab(removeIndex);
Leon Scroggins1f005d32009-08-10 17:36:42 -04002043 mTabControl.removeTab(t);
Leon Scroggins0a64ba52009-09-08 15:35:33 -04002044 if (currentIndex >= removeIndex && currentIndex != 0) {
2045 currentIndex--;
2046 }
2047 mTabControl.setCurrentTab(mTabControl.getTab(currentIndex));
The Android Open Source Project0c908882009-03-03 19:32:16 -08002048 }
2049
2050 private void goBackOnePageOrQuit() {
2051 TabControl.Tab current = mTabControl.getCurrentTab();
2052 if (current == null) {
2053 /*
2054 * Instead of finishing the activity, simply push this to the back
2055 * of the stack and let ActivityManager to choose the foreground
2056 * activity. As BrowserActivity is singleTask, it will be always the
2057 * root of the task. So we can use either true or false for
2058 * moveTaskToBack().
2059 */
2060 moveTaskToBack(true);
2061 }
2062 WebView w = current.getWebView();
2063 if (w.canGoBack()) {
2064 w.goBack();
2065 } else {
2066 // Check to see if we are closing a window that was created by
2067 // another window. If so, we switch back to that window.
2068 TabControl.Tab parent = current.getParentTab();
2069 if (parent != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002070 switchToTab(mTabControl.getTabIndex(parent));
2071 // Now we close the other tab
2072 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002073 } else {
2074 if (current.closeOnExit()) {
Grace Klobabb0af5c2009-09-01 00:56:09 -07002075 // force mPageStarted to be false as we are going to either
2076 // finish the activity or remove the tab. This will ensure
2077 // pauseWebView() taking action.
2078 mPageStarted = false;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002079 if (mTabControl.getTabCount() == 1) {
2080 finish();
2081 return;
2082 }
Mike Reed7bfa63b2009-05-28 11:08:32 -04002083 // call pauseWebViewTimers() now, we won't be able to call
2084 // it in onPause() as the WebView won't be valid.
Grace Klobaec1b5ad2009-08-18 08:42:32 -07002085 // Temporarily change mActivityInPause to be true as
2086 // pauseWebViewTimers() will do nothing if mActivityInPause
2087 // is false.
Grace Kloba918e1d72009-08-13 14:55:06 -07002088 boolean savedState = mActivityInPause;
2089 if (savedState) {
Grace Klobaec1b5ad2009-08-18 08:42:32 -07002090 Log.e(LOGTAG, "BrowserActivity is already paused "
2091 + "while handing goBackOnePageOrQuit.");
Grace Kloba918e1d72009-08-13 14:55:06 -07002092 }
2093 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002094 pauseWebViewTimers();
Grace Kloba918e1d72009-08-13 14:55:06 -07002095 mActivityInPause = savedState;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002096 removeTabFromContentView(current);
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04002097 mTitleBar.removeTab(mTabControl.getTabIndex(current));
The Android Open Source Project0c908882009-03-03 19:32:16 -08002098 mTabControl.removeTab(current);
2099 }
2100 /*
2101 * Instead of finishing the activity, simply push this to the back
2102 * of the stack and let ActivityManager to choose the foreground
2103 * activity. As BrowserActivity is singleTask, it will be always the
2104 * root of the task. So we can use either true or false for
2105 * moveTaskToBack().
2106 */
2107 moveTaskToBack(true);
2108 }
2109 }
2110 }
2111
2112 public KeyTracker.State onKeyTracker(int keyCode,
2113 KeyEvent event,
2114 KeyTracker.Stage stage,
2115 int duration) {
2116 // if onKeyTracker() is called after activity onStop()
2117 // because of accumulated key events,
2118 // we should ignore it as browser is not active any more.
2119 WebView topWindow = getTopWindow();
Andrei Popescuadc008d2009-06-26 14:11:30 +01002120 if (topWindow == null && mCustomView == null)
The Android Open Source Project0c908882009-03-03 19:32:16 -08002121 return KeyTracker.State.NOT_TRACKING;
2122
2123 if (keyCode == KeyEvent.KEYCODE_BACK) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01002124 // Check if a custom view is currently showing and, if it is, hide it.
2125 if (mCustomView != null) {
2126 mWebChromeClient.onHideCustomView();
2127 return KeyTracker.State.DONE_TRACKING;
2128 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002129 if (stage == KeyTracker.Stage.LONG_REPEAT) {
Leon Scroggins30444232009-09-04 18:36:20 -04002130 bookmarksOrHistoryPicker(true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002131 return KeyTracker.State.DONE_TRACKING;
2132 } else if (stage == KeyTracker.Stage.UP) {
2133 // FIXME: Currently, we do not have a notion of the
2134 // history picker for the subwindow, but maybe we
2135 // should?
2136 WebView subwindow = mTabControl.getCurrentSubWindow();
2137 if (subwindow != null) {
2138 if (subwindow.canGoBack()) {
2139 subwindow.goBack();
2140 } else {
2141 dismissSubWindow(mTabControl.getCurrentTab());
2142 }
2143 } else {
2144 goBackOnePageOrQuit();
2145 }
2146 return KeyTracker.State.DONE_TRACKING;
2147 }
2148 return KeyTracker.State.KEEP_TRACKING;
2149 }
2150 return KeyTracker.State.NOT_TRACKING;
2151 }
2152
2153 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2154 if (keyCode == KeyEvent.KEYCODE_MENU) {
2155 mMenuIsDown = true;
Grace Kloba6ee9c492009-07-13 10:04:34 -07002156 } else if (mMenuIsDown) {
2157 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2158 // still down, we don't want to trigger the search. Pretend to
2159 // consume the key and do nothing.
2160 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002161 }
2162 boolean handled = mKeyTracker.doKeyDown(keyCode, event);
2163 if (!handled) {
2164 switch (keyCode) {
2165 case KeyEvent.KEYCODE_SPACE:
2166 if (event.isShiftPressed()) {
2167 getTopWindow().pageUp(false);
2168 } else {
2169 getTopWindow().pageDown(false);
2170 }
2171 handled = true;
2172 break;
2173
2174 default:
2175 break;
2176 }
2177 }
2178 return handled || super.onKeyDown(keyCode, event);
2179 }
2180
2181 @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2182 if (keyCode == KeyEvent.KEYCODE_MENU) {
2183 mMenuIsDown = false;
2184 }
2185 return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2186 }
2187
2188 private void stopLoading() {
2189 resetTitleAndRevertLockIcon();
2190 WebView w = getTopWindow();
2191 w.stopLoading();
2192 mWebViewClient.onPageFinished(w, w.getUrl());
2193
2194 cancelStopToast();
2195 mStopToast = Toast
2196 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2197 mStopToast.show();
2198 }
2199
2200 private void cancelStopToast() {
2201 if (mStopToast != null) {
2202 mStopToast.cancel();
2203 mStopToast = null;
2204 }
2205 }
2206
2207 // called by a non-UI thread to post the message
2208 public void postMessage(int what, int arg1, int arg2, Object obj) {
2209 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2210 }
2211
2212 // public message ids
2213 public final static int LOAD_URL = 1001;
2214 public final static int STOP_LOAD = 1002;
2215
2216 // Message Ids
2217 private static final int FOCUS_NODE_HREF = 102;
2218 private static final int CANCEL_CREDS_REQUEST = 103;
Grace Kloba92c18a52009-07-31 23:48:32 -07002219 private static final int RELEASE_WAKELOCK = 107;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002220
2221 // Private handler for handling javascript and saving passwords
2222 private Handler mHandler = new Handler() {
2223
2224 public void handleMessage(Message msg) {
2225 switch (msg.what) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002226 case FOCUS_NODE_HREF:
2227 String url = (String) msg.getData().get("url");
2228 if (url == null || url.length() == 0) {
2229 break;
2230 }
2231 HashMap focusNodeMap = (HashMap) msg.obj;
2232 WebView view = (WebView) focusNodeMap.get("webview");
2233 // Only apply the action if the top window did not change.
2234 if (getTopWindow() != view) {
2235 break;
2236 }
2237 switch (msg.arg1) {
2238 case R.id.open_context_menu_id:
2239 case R.id.view_image_context_menu_id:
2240 loadURL(getTopWindow(), url);
2241 break;
2242 case R.id.open_newtab_context_menu_id:
Grace Klobac9181842009-04-14 08:53:22 -07002243 final TabControl.Tab parent = mTabControl
2244 .getCurrentTab();
2245 final TabControl.Tab newTab = openTab(url);
2246 if (newTab != parent) {
2247 parent.addChildTab(newTab);
2248 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002249 break;
2250 case R.id.bookmark_context_menu_id:
2251 Intent intent = new Intent(BrowserActivity.this,
2252 AddBookmarkPage.class);
2253 intent.putExtra("url", url);
2254 startActivity(intent);
2255 break;
2256 case R.id.share_link_context_menu_id:
2257 Browser.sendString(BrowserActivity.this, url);
2258 break;
2259 case R.id.copy_link_context_menu_id:
2260 copy(url);
2261 break;
2262 case R.id.save_link_context_menu_id:
2263 case R.id.download_context_menu_id:
2264 onDownloadStartNoStream(url, null, null, null, -1);
2265 break;
2266 }
2267 break;
2268
2269 case LOAD_URL:
2270 loadURL(getTopWindow(), (String) msg.obj);
2271 break;
2272
2273 case STOP_LOAD:
2274 stopLoading();
2275 break;
2276
2277 case CANCEL_CREDS_REQUEST:
2278 resumeAfterCredentials();
2279 break;
2280
The Android Open Source Project0c908882009-03-03 19:32:16 -08002281 case RELEASE_WAKELOCK:
2282 if (mWakeLock.isHeld()) {
2283 mWakeLock.release();
2284 }
2285 break;
2286 }
2287 }
2288 };
2289
Leon Scroggins89c6d362009-07-15 16:54:37 -04002290 private void updateScreenshot(WebView view) {
2291 // If this is a bookmarked site, add a screenshot to the database.
2292 // FIXME: When should we update? Every time?
2293 // FIXME: Would like to make sure there is actually something to
2294 // draw, but the API for that (WebViewCore.pictureReady()) is not
2295 // currently accessible here.
Patrick Scott3918d442009-08-04 13:22:29 -04002296 ContentResolver cr = getContentResolver();
2297 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04002298 cr, view.getOriginalUrl(), view.getUrl(), false);
Patrick Scott3918d442009-08-04 13:22:29 -04002299 if (c != null) {
Leon Scroggins89c6d362009-07-15 16:54:37 -04002300 boolean succeed = c.moveToFirst();
2301 ContentValues values = null;
2302 while (succeed) {
2303 if (values == null) {
2304 final ByteArrayOutputStream os
2305 = new ByteArrayOutputStream();
2306 Picture thumbnail = view.capturePicture();
2307 // Keep width and height in sync with BrowserBookmarksPage
2308 // and bookmark_thumb
2309 Bitmap bm = Bitmap.createBitmap(100, 80,
2310 Bitmap.Config.ARGB_4444);
2311 Canvas canvas = new Canvas(bm);
2312 // May need to tweak these values to determine what is the
2313 // best scale factor
2314 canvas.scale(.5f, .5f);
2315 thumbnail.draw(canvas);
2316 bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2317 values = new ContentValues();
2318 values.put(Browser.BookmarkColumns.THUMBNAIL,
2319 os.toByteArray());
2320 }
2321 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2322 c.getInt(0)), values, null, null);
2323 succeed = c.moveToNext();
2324 }
2325 c.close();
2326 }
2327 }
2328
The Android Open Source Project0c908882009-03-03 19:32:16 -08002329 // -------------------------------------------------------------------------
2330 // WebViewClient implementation.
2331 //-------------------------------------------------------------------------
2332
2333 // Use in overrideUrlLoading
2334 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2335 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2336 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2337 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2338
2339 /* package */ WebViewClient getWebViewClient() {
2340 return mWebViewClient;
2341 }
2342
Patrick Scott3918d442009-08-04 13:22:29 -04002343 private void updateIcon(WebView view, Bitmap icon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002344 if (icon != null) {
2345 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
Patrick Scott3918d442009-08-04 13:22:29 -04002346 view, icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002347 }
2348 setFavicon(icon);
2349 }
2350
2351 private final WebViewClient mWebViewClient = new WebViewClient() {
2352 @Override
2353 public void onPageStarted(WebView view, String url, Bitmap favicon) {
2354 resetLockIcon(url);
Leon Scroggins1f005d32009-08-10 17:36:42 -04002355 setUrlTitle(url, null, view);
Ben Murdochbff2d602009-07-01 20:19:05 +01002356
2357 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2358 if (errorConsole != null) {
2359 errorConsole.clearErrorMessages();
2360 if (mShouldShowErrorConsole) {
2361 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2362 }
2363 }
2364
The Android Open Source Project0c908882009-03-03 19:32:16 -08002365 // Call updateIcon instead of setFavicon so the bookmark
2366 // database can be updated.
Patrick Scott3918d442009-08-04 13:22:29 -04002367 updateIcon(view, favicon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002368
Grace Kloba4d7880f2009-08-12 09:35:42 -07002369 if (mSettings.isTracing()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002370 String host;
2371 try {
2372 WebAddress uri = new WebAddress(url);
2373 host = uri.mHost;
2374 } catch (android.net.ParseException ex) {
Grace Kloba4d7880f2009-08-12 09:35:42 -07002375 host = "browser";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002376 }
2377 host = host.replace('.', '_');
Grace Kloba4d7880f2009-08-12 09:35:42 -07002378 host += ".trace";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002379 mInTrace = true;
Grace Kloba4d7880f2009-08-12 09:35:42 -07002380 Debug.startMethodTracing(host, 20 * 1024 * 1024);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002381 }
2382
2383 // Performance probe
2384 if (false) {
2385 mStart = SystemClock.uptimeMillis();
2386 mProcessStart = Process.getElapsedCpuTime();
2387 long[] sysCpu = new long[7];
2388 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2389 sysCpu, null)) {
2390 mUserStart = sysCpu[0] + sysCpu[1];
2391 mSystemStart = sysCpu[2];
2392 mIdleStart = sysCpu[3];
2393 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2394 }
2395 mUiStart = SystemClock.currentThreadTimeMillis();
2396 }
2397
2398 if (!mPageStarted) {
2399 mPageStarted = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002400 // if onResume() has been called, resumeWebViewTimers() does
2401 // nothing.
2402 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002403 }
2404
2405 // reset sync timer to avoid sync starts during loading a page
2406 CookieSyncManager.getInstance().resetSync();
2407
2408 mInLoad = true;
2409 updateInLoadMenuItems();
2410 if (!mIsNetworkUp) {
2411 if ( mAlertDialog == null) {
2412 mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
2413 .setTitle(R.string.loadSuspendedTitle)
2414 .setMessage(R.string.loadSuspended)
2415 .setPositiveButton(R.string.ok, null)
2416 .show();
2417 }
2418 if (view != null) {
2419 view.setNetworkAvailable(false);
2420 }
2421 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002422 }
2423
2424 @Override
2425 public void onPageFinished(WebView view, String url) {
2426 // Reset the title and icon in case we stopped a provisional
2427 // load.
2428 resetTitleAndIcon(view);
2429
2430 // Update the lock icon image only once we are done loading
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04002431 updateLockIconToLatest();
Leon Scroggins89c6d362009-07-15 16:54:37 -04002432 updateScreenshot(view);
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -04002433
The Android Open Source Project0c908882009-03-03 19:32:16 -08002434 // Performance probe
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002435 if (false) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002436 long[] sysCpu = new long[7];
2437 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2438 sysCpu, null)) {
2439 String uiInfo = "UI thread used "
2440 + (SystemClock.currentThreadTimeMillis() - mUiStart)
2441 + " ms";
Dave Bort31a6d1c2009-04-13 15:56:49 -07002442 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002443 Log.d(LOGTAG, uiInfo);
2444 }
2445 //The string that gets written to the log
2446 String performanceString = "It took total "
2447 + (SystemClock.uptimeMillis() - mStart)
2448 + " ms clock time to load the page."
2449 + "\nbrowser process used "
2450 + (Process.getElapsedCpuTime() - mProcessStart)
2451 + " ms, user processes used "
2452 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2453 + " ms, kernel used "
2454 + (sysCpu[2] - mSystemStart) * 10
2455 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2456 + " ms and irq took "
2457 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2458 * 10 + " ms, " + uiInfo;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002459 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002460 Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2461 }
2462 if (url != null) {
2463 // strip the url to maintain consistency
2464 String newUrl = new String(url);
2465 if (newUrl.startsWith("http://www.")) {
2466 newUrl = newUrl.substring(11);
2467 } else if (newUrl.startsWith("http://")) {
2468 newUrl = newUrl.substring(7);
2469 } else if (newUrl.startsWith("https://www.")) {
2470 newUrl = newUrl.substring(12);
2471 } else if (newUrl.startsWith("https://")) {
2472 newUrl = newUrl.substring(8);
2473 }
Dave Bort31a6d1c2009-04-13 15:56:49 -07002474 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002475 Log.d(LOGTAG, newUrl + " loaded");
2476 }
2477 /*
2478 if (sWhiteList.contains(newUrl)) {
2479 // The string that gets pushed to the statistcs
2480 // service
2481 performanceString = performanceString
2482 + "\nWebpage: "
2483 + newUrl
2484 + "\nCarrier: "
2485 + android.os.SystemProperties
2486 .get("gsm.sim.operator.alpha");
2487 if (mWebView != null
2488 && mWebView.getContext() != null
2489 && mWebView.getContext().getSystemService(
2490 Context.CONNECTIVITY_SERVICE) != null) {
2491 ConnectivityManager cManager =
2492 (ConnectivityManager) mWebView
2493 .getContext().getSystemService(
2494 Context.CONNECTIVITY_SERVICE);
2495 NetworkInfo nInfo = cManager
2496 .getActiveNetworkInfo();
2497 if (nInfo != null) {
2498 performanceString = performanceString
2499 + "\nNetwork Type: "
2500 + nInfo.getType().toString();
2501 }
2502 }
2503 Checkin.logEvent(mResolver,
2504 Checkin.Events.Tag.WEBPAGE_LOAD,
2505 performanceString);
2506 Log.w(LOGTAG, "pushed to the statistics service");
2507 }
2508 */
2509 }
2510 }
2511 }
2512
2513 if (mInTrace) {
2514 mInTrace = false;
2515 Debug.stopMethodTracing();
2516 }
2517
2518 if (mPageStarted) {
2519 mPageStarted = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002520 // pauseWebViewTimers() will do nothing and return false if
2521 // onPause() is not called yet.
2522 if (pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002523 if (mWakeLock.isHeld()) {
2524 mHandler.removeMessages(RELEASE_WAKELOCK);
2525 mWakeLock.release();
2526 }
2527 }
2528 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002529 }
2530
2531 // return true if want to hijack the url to let another app to handle it
2532 @Override
2533 public boolean shouldOverrideUrlLoading(WebView view, String url) {
2534 if (url.startsWith(SCHEME_WTAI)) {
2535 // wtai://wp/mc;number
2536 // number=string(phone-number)
2537 if (url.startsWith(SCHEME_WTAI_MC)) {
2538 Intent intent = new Intent(Intent.ACTION_VIEW,
2539 Uri.parse(WebView.SCHEME_TEL +
2540 url.substring(SCHEME_WTAI_MC.length())));
2541 startActivity(intent);
2542 return true;
2543 }
2544 // wtai://wp/sd;dtmf
2545 // dtmf=string(dialstring)
2546 if (url.startsWith(SCHEME_WTAI_SD)) {
2547 // TODO
2548 // only send when there is active voice connection
2549 return false;
2550 }
2551 // wtai://wp/ap;number;name
2552 // number=string(phone-number)
2553 // name=string
2554 if (url.startsWith(SCHEME_WTAI_AP)) {
2555 // TODO
2556 return false;
2557 }
2558 }
2559
Dianne Hackborn99189432009-06-17 18:06:18 -07002560 // The "about:" schemes are internal to the browser; don't
2561 // want these to be dispatched to other apps.
2562 if (url.startsWith("about:")) {
2563 return false;
2564 }
Ben Murdochbff2d602009-07-01 20:19:05 +01002565
Dianne Hackborn99189432009-06-17 18:06:18 -07002566 Intent intent;
Ben Murdochbff2d602009-07-01 20:19:05 +01002567
Dianne Hackborn99189432009-06-17 18:06:18 -07002568 // perform generic parsing of the URI to turn it into an Intent.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002569 try {
Dianne Hackborn99189432009-06-17 18:06:18 -07002570 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2571 } catch (URISyntaxException ex) {
2572 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
The Android Open Source Project0c908882009-03-03 19:32:16 -08002573 return false;
2574 }
2575
Grace Kloba5b078b52009-06-24 20:23:41 -07002576 // check whether the intent can be resolved. If not, we will see
2577 // whether we can download it from the Market.
2578 if (getPackageManager().resolveActivity(intent, 0) == null) {
2579 String packagename = intent.getPackage();
2580 if (packagename != null) {
2581 intent = new Intent(Intent.ACTION_VIEW, Uri
2582 .parse("market://search?q=pname:" + packagename));
2583 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2584 startActivity(intent);
2585 return true;
2586 } else {
2587 return false;
2588 }
2589 }
2590
Dianne Hackborn99189432009-06-17 18:06:18 -07002591 // sanitize the Intent, ensuring web pages can not bypass browser
2592 // security (only access to BROWSABLE activities).
The Android Open Source Project0c908882009-03-03 19:32:16 -08002593 intent.addCategory(Intent.CATEGORY_BROWSABLE);
Dianne Hackborn99189432009-06-17 18:06:18 -07002594 intent.setComponent(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002595 try {
2596 if (startActivityIfNeeded(intent, -1)) {
2597 return true;
2598 }
2599 } catch (ActivityNotFoundException ex) {
2600 // ignore the error. If no application can handle the URL,
2601 // eg about:blank, assume the browser can handle it.
2602 }
2603
2604 if (mMenuIsDown) {
2605 openTab(url);
2606 closeOptionsMenu();
2607 return true;
2608 }
2609
2610 return false;
2611 }
2612
2613 /**
2614 * Updates the lock icon. This method is called when we discover another
2615 * resource to be loaded for this page (for example, javascript). While
2616 * we update the icon type, we do not update the lock icon itself until
2617 * we are done loading, it is slightly more secure this way.
2618 */
2619 @Override
2620 public void onLoadResource(WebView view, String url) {
2621 if (url != null && url.length() > 0) {
2622 // It is only if the page claims to be secure
2623 // that we may have to update the lock:
2624 if (mLockIconType == LOCK_ICON_SECURE) {
2625 // If NOT a 'safe' url, change the lock to mixed content!
2626 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2627 mLockIconType = LOCK_ICON_MIXED;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002628 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002629 Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2630 " updated lock icon to " + mLockIconType + " due to " + url);
2631 }
2632 }
2633 }
2634 }
2635 }
2636
2637 /**
2638 * Show the dialog, asking the user if they would like to continue after
2639 * an excessive number of HTTP redirects.
2640 */
2641 @Override
2642 public void onTooManyRedirects(WebView view, final Message cancelMsg,
2643 final Message continueMsg) {
2644 new AlertDialog.Builder(BrowserActivity.this)
2645 .setTitle(R.string.browserFrameRedirect)
2646 .setMessage(R.string.browserFrame307Post)
2647 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2648 public void onClick(DialogInterface dialog, int which) {
2649 continueMsg.sendToTarget();
2650 }})
2651 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2652 public void onClick(DialogInterface dialog, int which) {
2653 cancelMsg.sendToTarget();
2654 }})
2655 .setOnCancelListener(new OnCancelListener() {
2656 public void onCancel(DialogInterface dialog) {
2657 cancelMsg.sendToTarget();
2658 }})
2659 .show();
2660 }
2661
Patrick Scott37911c72009-03-24 18:02:58 -07002662 // Container class for the next error dialog that needs to be
2663 // displayed.
2664 class ErrorDialog {
2665 public final int mTitle;
2666 public final String mDescription;
2667 public final int mError;
2668 ErrorDialog(int title, String desc, int error) {
2669 mTitle = title;
2670 mDescription = desc;
2671 mError = error;
2672 }
2673 };
2674
2675 private void processNextError() {
2676 if (mQueuedErrors == null) {
2677 return;
2678 }
2679 // The first one is currently displayed so just remove it.
2680 mQueuedErrors.removeFirst();
2681 if (mQueuedErrors.size() == 0) {
2682 mQueuedErrors = null;
2683 return;
2684 }
2685 showError(mQueuedErrors.getFirst());
2686 }
2687
2688 private DialogInterface.OnDismissListener mDialogListener =
2689 new DialogInterface.OnDismissListener() {
2690 public void onDismiss(DialogInterface d) {
2691 processNextError();
2692 }
2693 };
2694 private LinkedList<ErrorDialog> mQueuedErrors;
2695
2696 private void queueError(int err, String desc) {
2697 if (mQueuedErrors == null) {
2698 mQueuedErrors = new LinkedList<ErrorDialog>();
2699 }
2700 for (ErrorDialog d : mQueuedErrors) {
2701 if (d.mError == err) {
2702 // Already saw a similar error, ignore the new one.
2703 return;
2704 }
2705 }
2706 ErrorDialog errDialog = new ErrorDialog(
Patrick Scott5d61a6c2009-08-25 13:52:46 -04002707 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
Patrick Scott37911c72009-03-24 18:02:58 -07002708 R.string.browserFrameFileErrorLabel :
2709 R.string.browserFrameNetworkErrorLabel,
2710 desc, err);
2711 mQueuedErrors.addLast(errDialog);
2712
2713 // Show the dialog now if the queue was empty.
2714 if (mQueuedErrors.size() == 1) {
2715 showError(errDialog);
2716 }
2717 }
2718
2719 private void showError(ErrorDialog errDialog) {
2720 AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
2721 .setTitle(errDialog.mTitle)
2722 .setMessage(errDialog.mDescription)
2723 .setPositiveButton(R.string.ok, null)
2724 .create();
2725 d.setOnDismissListener(mDialogListener);
2726 d.show();
2727 }
2728
The Android Open Source Project0c908882009-03-03 19:32:16 -08002729 /**
2730 * Show a dialog informing the user of the network error reported by
2731 * WebCore.
2732 */
2733 @Override
2734 public void onReceivedError(WebView view, int errorCode,
2735 String description, String failingUrl) {
Patrick Scott5d61a6c2009-08-25 13:52:46 -04002736 if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
2737 errorCode != WebViewClient.ERROR_CONNECT &&
2738 errorCode != WebViewClient.ERROR_BAD_URL &&
2739 errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
2740 errorCode != WebViewClient.ERROR_FILE) {
Patrick Scott37911c72009-03-24 18:02:58 -07002741 queueError(errorCode, description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002742 }
Patrick Scott37911c72009-03-24 18:02:58 -07002743 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
2744 + " " + description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002745
2746 // We need to reset the title after an error.
2747 resetTitleAndRevertLockIcon();
2748 }
2749
2750 /**
2751 * Check with the user if it is ok to resend POST data as the page they
2752 * are trying to navigate to is the result of a POST.
2753 */
2754 @Override
2755 public void onFormResubmission(WebView view, final Message dontResend,
2756 final Message resend) {
2757 new AlertDialog.Builder(BrowserActivity.this)
2758 .setTitle(R.string.browserFrameFormResubmitLabel)
2759 .setMessage(R.string.browserFrameFormResubmitMessage)
2760 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2761 public void onClick(DialogInterface dialog, int which) {
2762 resend.sendToTarget();
2763 }})
2764 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2765 public void onClick(DialogInterface dialog, int which) {
2766 dontResend.sendToTarget();
2767 }})
2768 .setOnCancelListener(new OnCancelListener() {
2769 public void onCancel(DialogInterface dialog) {
2770 dontResend.sendToTarget();
2771 }})
2772 .show();
2773 }
2774
2775 /**
2776 * Insert the url into the visited history database.
2777 * @param url The url to be inserted.
2778 * @param isReload True if this url is being reloaded.
2779 * FIXME: Not sure what to do when reloading the page.
2780 */
2781 @Override
2782 public void doUpdateVisitedHistory(WebView view, String url,
2783 boolean isReload) {
2784 if (url.regionMatches(true, 0, "about:", 0, 6)) {
2785 return;
2786 }
Grace Kloba6b52a552009-09-03 16:29:56 -07002787 // remove "client" before updating it to the history so that it wont
2788 // show up in the auto-complete list.
2789 int index = url.indexOf("client=ms-");
2790 if (index > 0 && url.contains(".google.")) {
2791 int end = url.indexOf('&', index);
2792 if (end > 0) {
2793 url = url.substring(0, index-1).concat(url.substring(end));
2794 } else {
2795 url = url.substring(0, index-1);
2796 }
2797 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002798 Browser.updateVisitedHistory(mResolver, url, true);
2799 WebIconDatabase.getInstance().retainIconForPageUrl(url);
2800 }
2801
2802 /**
2803 * Displays SSL error(s) dialog to the user.
2804 */
2805 @Override
2806 public void onReceivedSslError(
2807 final WebView view, final SslErrorHandler handler, final SslError error) {
2808
2809 if (mSettings.showSecurityWarnings()) {
2810 final LayoutInflater factory =
2811 LayoutInflater.from(BrowserActivity.this);
2812 final View warningsView =
2813 factory.inflate(R.layout.ssl_warnings, null);
2814 final LinearLayout placeholder =
2815 (LinearLayout)warningsView.findViewById(R.id.placeholder);
2816
2817 if (error.hasError(SslError.SSL_UNTRUSTED)) {
2818 LinearLayout ll = (LinearLayout)factory
2819 .inflate(R.layout.ssl_warning, null);
2820 ((TextView)ll.findViewById(R.id.warning))
2821 .setText(R.string.ssl_untrusted);
2822 placeholder.addView(ll);
2823 }
2824
2825 if (error.hasError(SslError.SSL_IDMISMATCH)) {
2826 LinearLayout ll = (LinearLayout)factory
2827 .inflate(R.layout.ssl_warning, null);
2828 ((TextView)ll.findViewById(R.id.warning))
2829 .setText(R.string.ssl_mismatch);
2830 placeholder.addView(ll);
2831 }
2832
2833 if (error.hasError(SslError.SSL_EXPIRED)) {
2834 LinearLayout ll = (LinearLayout)factory
2835 .inflate(R.layout.ssl_warning, null);
2836 ((TextView)ll.findViewById(R.id.warning))
2837 .setText(R.string.ssl_expired);
2838 placeholder.addView(ll);
2839 }
2840
2841 if (error.hasError(SslError.SSL_NOTYETVALID)) {
2842 LinearLayout ll = (LinearLayout)factory
2843 .inflate(R.layout.ssl_warning, null);
2844 ((TextView)ll.findViewById(R.id.warning))
2845 .setText(R.string.ssl_not_yet_valid);
2846 placeholder.addView(ll);
2847 }
2848
2849 new AlertDialog.Builder(BrowserActivity.this)
2850 .setTitle(R.string.security_warning)
2851 .setIcon(android.R.drawable.ic_dialog_alert)
2852 .setView(warningsView)
2853 .setPositiveButton(R.string.ssl_continue,
2854 new DialogInterface.OnClickListener() {
2855 public void onClick(DialogInterface dialog, int whichButton) {
2856 handler.proceed();
2857 }
2858 })
2859 .setNeutralButton(R.string.view_certificate,
2860 new DialogInterface.OnClickListener() {
2861 public void onClick(DialogInterface dialog, int whichButton) {
2862 showSSLCertificateOnError(view, handler, error);
2863 }
2864 })
2865 .setNegativeButton(R.string.cancel,
2866 new DialogInterface.OnClickListener() {
2867 public void onClick(DialogInterface dialog, int whichButton) {
2868 handler.cancel();
2869 BrowserActivity.this.resetTitleAndRevertLockIcon();
2870 }
2871 })
2872 .setOnCancelListener(
2873 new DialogInterface.OnCancelListener() {
2874 public void onCancel(DialogInterface dialog) {
2875 handler.cancel();
2876 BrowserActivity.this.resetTitleAndRevertLockIcon();
2877 }
2878 })
2879 .show();
2880 } else {
2881 handler.proceed();
2882 }
2883 }
2884
2885 /**
2886 * Handles an HTTP authentication request.
2887 *
2888 * @param handler The authentication handler
2889 * @param host The host
2890 * @param realm The realm
2891 */
2892 @Override
2893 public void onReceivedHttpAuthRequest(WebView view,
2894 final HttpAuthHandler handler, final String host, final String realm) {
2895 String username = null;
2896 String password = null;
2897
2898 boolean reuseHttpAuthUsernamePassword =
2899 handler.useHttpAuthUsernamePassword();
2900
2901 if (reuseHttpAuthUsernamePassword &&
2902 (mTabControl.getCurrentWebView() != null)) {
2903 String[] credentials =
2904 mTabControl.getCurrentWebView()
2905 .getHttpAuthUsernamePassword(host, realm);
2906 if (credentials != null && credentials.length == 2) {
2907 username = credentials[0];
2908 password = credentials[1];
2909 }
2910 }
2911
2912 if (username != null && password != null) {
2913 handler.proceed(username, password);
2914 } else {
2915 showHttpAuthentication(handler, host, realm, null, null, null, 0);
2916 }
2917 }
2918
2919 @Override
2920 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
2921 if (mMenuIsDown) {
2922 // only check shortcut key when MENU is held
2923 return getWindow().isShortcutKey(event.getKeyCode(), event);
2924 } else {
2925 return false;
2926 }
2927 }
2928
2929 @Override
2930 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
2931 if (view != mTabControl.getCurrentTopWebView()) {
2932 return;
2933 }
2934 if (event.isDown()) {
2935 BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
2936 } else {
2937 BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
2938 }
2939 }
2940 };
2941
2942 //--------------------------------------------------------------------------
2943 // WebChromeClient implementation
2944 //--------------------------------------------------------------------------
2945
2946 /* package */ WebChromeClient getWebChromeClient() {
2947 return mWebChromeClient;
2948 }
2949
2950 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
2951 // Helper method to create a new tab or sub window.
2952 private void createWindow(final boolean dialog, final Message msg) {
2953 if (dialog) {
2954 mTabControl.createSubWindow();
2955 final TabControl.Tab t = mTabControl.getCurrentTab();
2956 attachSubWindow(t);
2957 WebView.WebViewTransport transport =
2958 (WebView.WebViewTransport) msg.obj;
2959 transport.setWebView(t.getSubWebView());
2960 msg.sendToTarget();
2961 } else {
2962 final TabControl.Tab parent = mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04002963 final TabControl.Tab newTab
2964 = openTabAndShow(EMPTY_URL_DATA, false, null);
Grace Klobac9181842009-04-14 08:53:22 -07002965 if (newTab != parent) {
2966 parent.addChildTab(newTab);
2967 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002968 WebView.WebViewTransport transport =
2969 (WebView.WebViewTransport) msg.obj;
2970 transport.setWebView(mTabControl.getCurrentWebView());
Leon Scroggins1f005d32009-08-10 17:36:42 -04002971 msg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002972 }
2973 }
2974
2975 @Override
2976 public boolean onCreateWindow(WebView view, final boolean dialog,
2977 final boolean userGesture, final Message resultMsg) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002978 // Short-circuit if we can't create any more tabs or sub windows.
2979 if (dialog && mTabControl.getCurrentSubWindow() != null) {
2980 new AlertDialog.Builder(BrowserActivity.this)
2981 .setTitle(R.string.too_many_subwindows_dialog_title)
2982 .setIcon(android.R.drawable.ic_dialog_alert)
2983 .setMessage(R.string.too_many_subwindows_dialog_message)
2984 .setPositiveButton(R.string.ok, null)
2985 .show();
2986 return false;
2987 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
2988 new AlertDialog.Builder(BrowserActivity.this)
2989 .setTitle(R.string.too_many_windows_dialog_title)
2990 .setIcon(android.R.drawable.ic_dialog_alert)
2991 .setMessage(R.string.too_many_windows_dialog_message)
2992 .setPositiveButton(R.string.ok, null)
2993 .show();
2994 return false;
2995 }
2996
2997 // Short-circuit if this was a user gesture.
2998 if (userGesture) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002999 createWindow(dialog, resultMsg);
3000 return true;
3001 }
3002
3003 // Allow the popup and create the appropriate window.
3004 final AlertDialog.OnClickListener allowListener =
3005 new AlertDialog.OnClickListener() {
3006 public void onClick(DialogInterface d,
3007 int which) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003008 createWindow(dialog, resultMsg);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003009 }
3010 };
3011
3012 // Block the popup by returning a null WebView.
3013 final AlertDialog.OnClickListener blockListener =
3014 new AlertDialog.OnClickListener() {
3015 public void onClick(DialogInterface d, int which) {
3016 resultMsg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08003017 }
3018 };
3019
3020 // Build a confirmation dialog to display to the user.
3021 final AlertDialog d =
3022 new AlertDialog.Builder(BrowserActivity.this)
3023 .setTitle(R.string.attention)
3024 .setIcon(android.R.drawable.ic_dialog_alert)
3025 .setMessage(R.string.popup_window_attempt)
3026 .setPositiveButton(R.string.allow, allowListener)
3027 .setNegativeButton(R.string.block, blockListener)
3028 .setCancelable(false)
3029 .create();
3030
3031 // Show the confirmation dialog.
3032 d.show();
The Android Open Source Project0c908882009-03-03 19:32:16 -08003033 return true;
3034 }
3035
3036 @Override
3037 public void onCloseWindow(WebView window) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04003038 final TabControl.Tab current = mTabControl.getCurrentTab();
3039 final TabControl.Tab parent = current.getParentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08003040 if (parent != null) {
3041 // JavaScript can only close popup window.
Leon Scroggins1f005d32009-08-10 17:36:42 -04003042 switchToTab(mTabControl.getTabIndex(parent));
3043 // Now we need to close the window
3044 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003045 }
3046 }
3047
3048 @Override
3049 public void onProgressChanged(WebView view, int newProgress) {
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04003050 mTitleBar.setProgress(newProgress, view);
3051 if (mFakeTitleBar != null) {
3052 mFakeTitleBar.setProgress(newProgress);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003053 }
3054
3055 if (newProgress == 100) {
3056 // onProgressChanged() is called for sub-frame too while
3057 // onPageFinished() is only called for the main frame. sync
3058 // cookie and cache promptly here.
3059 CookieSyncManager.getInstance().sync();
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07003060 if (mInLoad) {
3061 mInLoad = false;
3062 updateInLoadMenuItems();
3063 }
3064 } else {
3065 // onPageFinished may have already been called but a subframe
3066 // is still loading and updating the progress. Reset mInLoad
3067 // and update the menu items.
3068 if (!mInLoad) {
3069 mInLoad = true;
3070 updateInLoadMenuItems();
3071 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003072 }
3073 }
3074
3075 @Override
3076 public void onReceivedTitle(WebView view, String title) {
Patrick Scott598c9cc2009-06-04 11:10:38 -04003077 String url = view.getUrl();
The Android Open Source Project0c908882009-03-03 19:32:16 -08003078
3079 // here, if url is null, we want to reset the title
Leon Scroggins1f005d32009-08-10 17:36:42 -04003080 setUrlTitle(url, title, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003081
3082 if (url == null ||
3083 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
3084 return;
3085 }
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003086 // See if we can find the current url in our history database and
3087 // add the new title to it.
The Android Open Source Project0c908882009-03-03 19:32:16 -08003088 if (url.startsWith("http://www.")) {
3089 url = url.substring(11);
3090 } else if (url.startsWith("http://")) {
3091 url = url.substring(4);
3092 }
3093 try {
3094 url = "%" + url;
3095 String [] selArgs = new String[] { url };
3096
3097 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
3098 + Browser.BookmarkColumns.BOOKMARK + " = 0";
3099 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
3100 Browser.HISTORY_PROJECTION, where, selArgs, null);
3101 if (c.moveToFirst()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003102 // Current implementation of database only has one entry per
3103 // url.
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003104 ContentValues map = new ContentValues();
3105 map.put(Browser.BookmarkColumns.TITLE, title);
3106 mResolver.update(Browser.BOOKMARKS_URI, map,
3107 "_id = " + c.getInt(0), null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003108 }
3109 c.close();
3110 } catch (IllegalStateException e) {
3111 Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3112 } catch (SQLiteException ex) {
3113 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3114 }
3115 }
3116
3117 @Override
3118 public void onReceivedIcon(WebView view, Bitmap icon) {
Patrick Scott3918d442009-08-04 13:22:29 -04003119 updateIcon(view, icon);
3120 }
3121
3122 @Override
3123 public void onReceivedTouchIconUrl(WebView view, String url) {
3124 final ContentResolver cr = getContentResolver();
3125 final Cursor c =
3126 BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04003127 view.getOriginalUrl(), view.getUrl(), true);
Patrick Scott3918d442009-08-04 13:22:29 -04003128 if (c != null) {
3129 if (c.getCount() > 0) {
3130 new DownloadTouchIcon(cr, c, view).execute(url);
3131 } else {
3132 c.close();
3133 }
3134 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003135 }
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003136
Andrei Popescuadc008d2009-06-26 14:11:30 +01003137 @Override
Andrei Popescuc9b55562009-07-07 10:51:15 +01003138 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01003139 if (mCustomView != null)
3140 return;
3141
3142 // Add the custom view to its container.
3143 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
3144 mCustomView = view;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003145 mCustomViewCallback = callback;
Andrei Popescuadc008d2009-06-26 14:11:30 +01003146 // Save the menu state and set it to empty while the custom
3147 // view is showing.
3148 mOldMenuState = mMenuState;
3149 mMenuState = EMPTY_MENU;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003150 // Hide the content view.
3151 mContentView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003152 // Finally show the custom view container.
Andrei Popescuc9b55562009-07-07 10:51:15 +01003153 mCustomViewContainer.setVisibility(View.VISIBLE);
3154 mCustomViewContainer.bringToFront();
Andrei Popescuadc008d2009-06-26 14:11:30 +01003155 }
3156
3157 @Override
3158 public void onHideCustomView() {
3159 if (mCustomView == null)
3160 return;
3161
Andrei Popescuc9b55562009-07-07 10:51:15 +01003162 // Hide the custom view.
3163 mCustomView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003164 // Remove the custom view from its container.
3165 mCustomViewContainer.removeView(mCustomView);
3166 mCustomView = null;
3167 // Reset the old menu state.
3168 mMenuState = mOldMenuState;
3169 mOldMenuState = EMPTY_MENU;
3170 mCustomViewContainer.setVisibility(View.GONE);
Andrei Popescuc9b55562009-07-07 10:51:15 +01003171 mCustomViewCallback.onCustomViewHidden();
3172 // Show the content view.
3173 mContentView.setVisibility(View.VISIBLE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003174 }
3175
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003176 /**
Andrei Popescu79e82b72009-07-27 12:01:59 +01003177 * The origin has exceeded its database quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003178 * @param url the URL that exceeded the quota
3179 * @param databaseIdentifier the identifier of the database on
3180 * which the transaction that caused the quota overflow was run
3181 * @param currentQuota the current quota for the origin.
Ben Murdoch25a15232009-08-25 19:38:07 +01003182 * @param estimatedSize the estimated size of the database.
Andrei Popescu79e82b72009-07-27 12:01:59 +01003183 * @param totalUsedQuota is the sum of all origins' quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003184 * @param quotaUpdater The callback to run when a decision to allow or
3185 * deny quota has been made. Don't forget to call this!
3186 */
3187 @Override
3188 public void onExceededDatabaseQuota(String url,
Ben Murdoch25a15232009-08-25 19:38:07 +01003189 String databaseIdentifier, long currentQuota, long estimatedSize,
3190 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
Andrei Popescu79e82b72009-07-27 12:01:59 +01003191 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
Ben Murdoch25a15232009-08-25 19:38:07 +01003192 url, databaseIdentifier, currentQuota, estimatedSize,
3193 totalUsedQuota, quotaUpdater);
Andrei Popescu79e82b72009-07-27 12:01:59 +01003194 }
3195
3196 /**
3197 * The Application Cache has exceeded its max size.
3198 * @param spaceNeeded is the amount of disk space that would be needed
3199 * in order for the last appcache operation to succeed.
3200 * @param totalUsedQuota is the sum of all origins' quota.
3201 * @param quotaUpdater A callback to inform the WebCore thread that a new
3202 * app cache size is available. This callback must always be executed at
3203 * some point to ensure that the sleeping WebCore thread is woken up.
3204 */
3205 @Override
3206 public void onReachedMaxAppCacheSize(long spaceNeeded,
3207 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3208 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3209 spaceNeeded, totalUsedQuota, quotaUpdater);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003210 }
Ben Murdoch7db26342009-06-03 18:21:19 +01003211
Steve Block2bc69912009-07-30 14:45:13 +01003212 /**
3213 * Instructs the browser to show a prompt to ask the user to set the
3214 * Geolocation permission state for the specified origin.
3215 * @param origin The origin for which Geolocation permissions are
3216 * requested.
3217 * @param callback The callback to call once the user has set the
3218 * Geolocation permission state.
3219 */
3220 @Override
3221 public void onGeolocationPermissionsShowPrompt(String origin,
3222 GeolocationPermissions.Callback callback) {
3223 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3224 origin, callback);
3225 }
3226
3227 /**
3228 * Instructs the browser to hide the Geolocation permissions prompt.
3229 */
3230 @Override
3231 public void onGeolocationPermissionsHidePrompt() {
3232 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3233 }
3234
Ben Murdoch7db26342009-06-03 18:21:19 +01003235 /* Adds a JavaScript error message to the system log.
3236 * @param message The error message to report.
3237 * @param lineNumber The line number of the error.
3238 * @param sourceID The name of the source file that caused the error.
3239 */
3240 @Override
3241 public void addMessageToConsole(String message, int lineNumber, String sourceID) {
Ben Murdochbff2d602009-07-01 20:19:05 +01003242 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3243 errorConsole.addErrorMessage(message, sourceID, lineNumber);
3244 if (mShouldShowErrorConsole &&
3245 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3246 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3247 }
3248 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
Ben Murdoch7db26342009-06-03 18:21:19 +01003249 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003250 };
3251
3252 /**
3253 * Notify the host application a download should be done, or that
3254 * the data should be streamed if a streaming viewer is available.
3255 * @param url The full url to the content that should be downloaded
3256 * @param contentDisposition Content-disposition http header, if
3257 * present.
3258 * @param mimetype The mimetype of the content reported by the server
3259 * @param contentLength The file size reported by the server
3260 */
3261 public void onDownloadStart(String url, String userAgent,
3262 String contentDisposition, String mimetype, long contentLength) {
3263 // if we're dealing wih A/V content that's not explicitly marked
3264 // for download, check if it's streamable.
3265 if (contentDisposition == null
Patrick Scotte1fb9662009-08-31 14:31:52 -04003266 || !contentDisposition.regionMatches(
3267 true, 0, "attachment", 0, 10)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003268 // query the package manager to see if there's a registered handler
3269 // that matches.
3270 Intent intent = new Intent(Intent.ACTION_VIEW);
3271 intent.setDataAndType(Uri.parse(url), mimetype);
Patrick Scotte1fb9662009-08-31 14:31:52 -04003272 ResolveInfo info = getPackageManager().resolveActivity(intent,
3273 PackageManager.MATCH_DEFAULT_ONLY);
3274 if (info != null) {
3275 ComponentName myName = getComponentName();
3276 // If we resolved to ourselves, we don't want to attempt to
3277 // load the url only to try and download it again.
3278 if (!myName.getPackageName().equals(
3279 info.activityInfo.packageName)
3280 || !myName.getClassName().equals(
3281 info.activityInfo.name)) {
3282 // someone (other than us) knows how to handle this mime
3283 // type with this scheme, don't download.
3284 try {
3285 startActivity(intent);
3286 return;
3287 } catch (ActivityNotFoundException ex) {
3288 if (LOGD_ENABLED) {
3289 Log.d(LOGTAG, "activity not found for " + mimetype
3290 + " over " + Uri.parse(url).getScheme(),
3291 ex);
3292 }
3293 // Best behavior is to fall back to a download in this
3294 // case
The Android Open Source Project0c908882009-03-03 19:32:16 -08003295 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003296 }
3297 }
3298 }
3299 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3300 }
3301
3302 /**
3303 * Notify the host application a download should be done, even if there
3304 * is a streaming viewer available for thise type.
3305 * @param url The full url to the content that should be downloaded
3306 * @param contentDisposition Content-disposition http header, if
3307 * present.
3308 * @param mimetype The mimetype of the content reported by the server
3309 * @param contentLength The file size reported by the server
3310 */
3311 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3312 String contentDisposition, String mimetype, long contentLength) {
3313
3314 String filename = URLUtil.guessFileName(url,
3315 contentDisposition, mimetype);
3316
3317 // Check to see if we have an SDCard
3318 String status = Environment.getExternalStorageState();
3319 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3320 int title;
3321 String msg;
3322
3323 // Check to see if the SDCard is busy, same as the music app
3324 if (status.equals(Environment.MEDIA_SHARED)) {
3325 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3326 title = R.string.download_sdcard_busy_dlg_title;
3327 } else {
3328 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3329 title = R.string.download_no_sdcard_dlg_title;
3330 }
3331
3332 new AlertDialog.Builder(this)
3333 .setTitle(title)
3334 .setIcon(android.R.drawable.ic_dialog_alert)
3335 .setMessage(msg)
3336 .setPositiveButton(R.string.ok, null)
3337 .show();
3338 return;
3339 }
3340
3341 // java.net.URI is a lot stricter than KURL so we have to undo
3342 // KURL's percent-encoding and redo the encoding using java.net.URI.
3343 URI uri = null;
3344 try {
3345 // Undo the percent-encoding that KURL may have done.
3346 String newUrl = new String(URLUtil.decode(url.getBytes()));
3347 // Parse the url into pieces
3348 WebAddress w = new WebAddress(newUrl);
3349 String frag = null;
3350 String query = null;
3351 String path = w.mPath;
3352 // Break the path into path, query, and fragment
3353 if (path.length() > 0) {
3354 // Strip the fragment
3355 int idx = path.lastIndexOf('#');
3356 if (idx != -1) {
3357 frag = path.substring(idx + 1);
3358 path = path.substring(0, idx);
3359 }
3360 idx = path.lastIndexOf('?');
3361 if (idx != -1) {
3362 query = path.substring(idx + 1);
3363 path = path.substring(0, idx);
3364 }
3365 }
3366 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3367 query, frag);
3368 } catch (Exception e) {
3369 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3370 return;
3371 }
3372
3373 // XXX: Have to use the old url since the cookies were stored using the
3374 // old percent-encoded url.
3375 String cookies = CookieManager.getInstance().getCookie(url);
3376
3377 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003378 values.put(Downloads.COLUMN_URI, uri.toString());
3379 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3380 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3381 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003382 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003383 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003384 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003385 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3386 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3387 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3388 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003389 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003390 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003391 }
3392 if (mimetype == null) {
3393 // We must have long pressed on a link or image to download it. We
3394 // are not sure of the mimetype in this case, so do a head request
3395 new FetchUrlMimeType(this).execute(values);
3396 } else {
3397 final Uri contentUri =
3398 getContentResolver().insert(Downloads.CONTENT_URI, values);
3399 viewDownloads(contentUri);
3400 }
3401
3402 }
3403
3404 /**
3405 * Resets the lock icon. This method is called when we start a new load and
3406 * know the url to be loaded.
3407 */
3408 private void resetLockIcon(String url) {
3409 // Save the lock-icon state (we revert to it if the load gets cancelled)
3410 saveLockIcon();
3411
3412 mLockIconType = LOCK_ICON_UNSECURE;
3413 if (URLUtil.isHttpsUrl(url)) {
3414 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003415 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003416 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3417 " reset lock icon to " + mLockIconType);
3418 }
3419 }
3420
3421 updateLockIconImage(LOCK_ICON_UNSECURE);
3422 }
3423
3424 /**
3425 * Resets the lock icon. This method is called when the icon needs to be
3426 * reset but we do not know whether we are loading a secure or not secure
3427 * page.
3428 */
3429 private void resetLockIcon() {
3430 // Save the lock-icon state (we revert to it if the load gets cancelled)
3431 saveLockIcon();
3432
3433 mLockIconType = LOCK_ICON_UNSECURE;
3434
Dave Bort31a6d1c2009-04-13 15:56:49 -07003435 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003436 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3437 " reset lock icon to " + mLockIconType);
3438 }
3439
3440 updateLockIconImage(LOCK_ICON_UNSECURE);
3441 }
3442
3443 /**
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04003444 * Update the lock icon to correspond to our latest state.
3445 */
3446 /* package */ void updateLockIconToLatest() {
3447 updateLockIconImage(mLockIconType);
3448 }
3449
3450 /**
The Android Open Source Project0c908882009-03-03 19:32:16 -08003451 * Updates the lock-icon image in the title-bar.
3452 */
3453 private void updateLockIconImage(int lockIconType) {
3454 Drawable d = null;
3455 if (lockIconType == LOCK_ICON_SECURE) {
3456 d = mSecLockIcon;
3457 } else if (lockIconType == LOCK_ICON_MIXED) {
3458 d = mMixLockIcon;
3459 }
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04003460 mTitleBar.setLock(d, getTopWindow());
3461 if (mFakeTitleBar != null) {
3462 mFakeTitleBar.setLock(d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003463 }
3464 }
3465
3466 /**
3467 * Displays a page-info dialog.
3468 * @param tab The tab to show info about
3469 * @param fromShowSSLCertificateOnError The flag that indicates whether
3470 * this dialog was opened from the SSL-certificate-on-error dialog or
3471 * not. This is important, since we need to know whether to return to
3472 * the parent dialog or simply dismiss.
3473 */
3474 private void showPageInfo(final TabControl.Tab tab,
3475 final boolean fromShowSSLCertificateOnError) {
3476 final LayoutInflater factory = LayoutInflater
3477 .from(this);
3478
3479 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3480
3481 final WebView view = tab.getWebView();
3482
3483 String url = null;
3484 String title = null;
3485
3486 if (view == null) {
3487 url = tab.getUrl();
3488 title = tab.getTitle();
3489 } else if (view == mTabControl.getCurrentWebView()) {
3490 // Use the cached title and url if this is the current WebView
3491 url = mUrl;
3492 title = mTitle;
3493 } else {
3494 url = view.getUrl();
3495 title = view.getTitle();
3496 }
3497
3498 if (url == null) {
3499 url = "";
3500 }
3501 if (title == null) {
3502 title = "";
3503 }
3504
3505 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3506 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3507
3508 mPageInfoView = tab;
3509 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3510
3511 AlertDialog.Builder alertDialogBuilder =
3512 new AlertDialog.Builder(this)
3513 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3514 .setView(pageInfoView)
3515 .setPositiveButton(
3516 R.string.ok,
3517 new DialogInterface.OnClickListener() {
3518 public void onClick(DialogInterface dialog,
3519 int whichButton) {
3520 mPageInfoDialog = null;
3521 mPageInfoView = null;
3522 mPageInfoFromShowSSLCertificateOnError = null;
3523
3524 // if we came here from the SSL error dialog
3525 if (fromShowSSLCertificateOnError) {
3526 // go back to the SSL error dialog
3527 showSSLCertificateOnError(
3528 mSSLCertificateOnErrorView,
3529 mSSLCertificateOnErrorHandler,
3530 mSSLCertificateOnErrorError);
3531 }
3532 }
3533 })
3534 .setOnCancelListener(
3535 new DialogInterface.OnCancelListener() {
3536 public void onCancel(DialogInterface dialog) {
3537 mPageInfoDialog = null;
3538 mPageInfoView = null;
3539 mPageInfoFromShowSSLCertificateOnError = null;
3540
3541 // if we came here from the SSL error dialog
3542 if (fromShowSSLCertificateOnError) {
3543 // go back to the SSL error dialog
3544 showSSLCertificateOnError(
3545 mSSLCertificateOnErrorView,
3546 mSSLCertificateOnErrorHandler,
3547 mSSLCertificateOnErrorError);
3548 }
3549 }
3550 });
3551
3552 // if we have a main top-level page SSL certificate set or a certificate
3553 // error
3554 if (fromShowSSLCertificateOnError ||
3555 (view != null && view.getCertificate() != null)) {
3556 // add a 'View Certificate' button
3557 alertDialogBuilder.setNeutralButton(
3558 R.string.view_certificate,
3559 new DialogInterface.OnClickListener() {
3560 public void onClick(DialogInterface dialog,
3561 int whichButton) {
3562 mPageInfoDialog = null;
3563 mPageInfoView = null;
3564 mPageInfoFromShowSSLCertificateOnError = null;
3565
3566 // if we came here from the SSL error dialog
3567 if (fromShowSSLCertificateOnError) {
3568 // go back to the SSL error dialog
3569 showSSLCertificateOnError(
3570 mSSLCertificateOnErrorView,
3571 mSSLCertificateOnErrorHandler,
3572 mSSLCertificateOnErrorError);
3573 } else {
3574 // otherwise, display the top-most certificate from
3575 // the chain
3576 if (view.getCertificate() != null) {
3577 showSSLCertificate(tab);
3578 }
3579 }
3580 }
3581 });
3582 }
3583
3584 mPageInfoDialog = alertDialogBuilder.show();
3585 }
3586
3587 /**
3588 * Displays the main top-level page SSL certificate dialog
3589 * (accessible from the Page-Info dialog).
3590 * @param tab The tab to show certificate for.
3591 */
3592 private void showSSLCertificate(final TabControl.Tab tab) {
3593 final View certificateView =
3594 inflateCertificateView(tab.getWebView().getCertificate());
3595 if (certificateView == null) {
3596 return;
3597 }
3598
3599 LayoutInflater factory = LayoutInflater.from(this);
3600
3601 final LinearLayout placeholder =
3602 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3603
3604 LinearLayout ll = (LinearLayout) factory.inflate(
3605 R.layout.ssl_success, placeholder);
3606 ((TextView)ll.findViewById(R.id.success))
3607 .setText(R.string.ssl_certificate_is_valid);
3608
3609 mSSLCertificateView = tab;
3610 mSSLCertificateDialog =
3611 new AlertDialog.Builder(this)
3612 .setTitle(R.string.ssl_certificate).setIcon(
3613 R.drawable.ic_dialog_browser_certificate_secure)
3614 .setView(certificateView)
3615 .setPositiveButton(R.string.ok,
3616 new DialogInterface.OnClickListener() {
3617 public void onClick(DialogInterface dialog,
3618 int whichButton) {
3619 mSSLCertificateDialog = null;
3620 mSSLCertificateView = null;
3621
3622 showPageInfo(tab, false);
3623 }
3624 })
3625 .setOnCancelListener(
3626 new DialogInterface.OnCancelListener() {
3627 public void onCancel(DialogInterface dialog) {
3628 mSSLCertificateDialog = null;
3629 mSSLCertificateView = null;
3630
3631 showPageInfo(tab, false);
3632 }
3633 })
3634 .show();
3635 }
3636
3637 /**
3638 * Displays the SSL error certificate dialog.
3639 * @param view The target web-view.
3640 * @param handler The SSL error handler responsible for cancelling the
3641 * connection that resulted in an SSL error or proceeding per user request.
3642 * @param error The SSL error object.
3643 */
3644 private void showSSLCertificateOnError(
3645 final WebView view, final SslErrorHandler handler, final SslError error) {
3646
3647 final View certificateView =
3648 inflateCertificateView(error.getCertificate());
3649 if (certificateView == null) {
3650 return;
3651 }
3652
3653 LayoutInflater factory = LayoutInflater.from(this);
3654
3655 final LinearLayout placeholder =
3656 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3657
3658 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3659 LinearLayout ll = (LinearLayout)factory
3660 .inflate(R.layout.ssl_warning, placeholder);
3661 ((TextView)ll.findViewById(R.id.warning))
3662 .setText(R.string.ssl_untrusted);
3663 }
3664
3665 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3666 LinearLayout ll = (LinearLayout)factory
3667 .inflate(R.layout.ssl_warning, placeholder);
3668 ((TextView)ll.findViewById(R.id.warning))
3669 .setText(R.string.ssl_mismatch);
3670 }
3671
3672 if (error.hasError(SslError.SSL_EXPIRED)) {
3673 LinearLayout ll = (LinearLayout)factory
3674 .inflate(R.layout.ssl_warning, placeholder);
3675 ((TextView)ll.findViewById(R.id.warning))
3676 .setText(R.string.ssl_expired);
3677 }
3678
3679 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3680 LinearLayout ll = (LinearLayout)factory
3681 .inflate(R.layout.ssl_warning, placeholder);
3682 ((TextView)ll.findViewById(R.id.warning))
3683 .setText(R.string.ssl_not_yet_valid);
3684 }
3685
3686 mSSLCertificateOnErrorHandler = handler;
3687 mSSLCertificateOnErrorView = view;
3688 mSSLCertificateOnErrorError = error;
3689 mSSLCertificateOnErrorDialog =
3690 new AlertDialog.Builder(this)
3691 .setTitle(R.string.ssl_certificate).setIcon(
3692 R.drawable.ic_dialog_browser_certificate_partially_secure)
3693 .setView(certificateView)
3694 .setPositiveButton(R.string.ok,
3695 new DialogInterface.OnClickListener() {
3696 public void onClick(DialogInterface dialog,
3697 int whichButton) {
3698 mSSLCertificateOnErrorDialog = null;
3699 mSSLCertificateOnErrorView = null;
3700 mSSLCertificateOnErrorHandler = null;
3701 mSSLCertificateOnErrorError = null;
3702
3703 mWebViewClient.onReceivedSslError(
3704 view, handler, error);
3705 }
3706 })
3707 .setNeutralButton(R.string.page_info_view,
3708 new DialogInterface.OnClickListener() {
3709 public void onClick(DialogInterface dialog,
3710 int whichButton) {
3711 mSSLCertificateOnErrorDialog = null;
3712
3713 // do not clear the dialog state: we will
3714 // need to show the dialog again once the
3715 // user is done exploring the page-info details
3716
3717 showPageInfo(mTabControl.getTabFromView(view),
3718 true);
3719 }
3720 })
3721 .setOnCancelListener(
3722 new DialogInterface.OnCancelListener() {
3723 public void onCancel(DialogInterface dialog) {
3724 mSSLCertificateOnErrorDialog = null;
3725 mSSLCertificateOnErrorView = null;
3726 mSSLCertificateOnErrorHandler = null;
3727 mSSLCertificateOnErrorError = null;
3728
3729 mWebViewClient.onReceivedSslError(
3730 view, handler, error);
3731 }
3732 })
3733 .show();
3734 }
3735
3736 /**
3737 * Inflates the SSL certificate view (helper method).
3738 * @param certificate The SSL certificate.
3739 * @return The resultant certificate view with issued-to, issued-by,
3740 * issued-on, expires-on, and possibly other fields set.
3741 * If the input certificate is null, returns null.
3742 */
3743 private View inflateCertificateView(SslCertificate certificate) {
3744 if (certificate == null) {
3745 return null;
3746 }
3747
3748 LayoutInflater factory = LayoutInflater.from(this);
3749
3750 View certificateView = factory.inflate(
3751 R.layout.ssl_certificate, null);
3752
3753 // issued to:
3754 SslCertificate.DName issuedTo = certificate.getIssuedTo();
3755 if (issuedTo != null) {
3756 ((TextView) certificateView.findViewById(R.id.to_common))
3757 .setText(issuedTo.getCName());
3758 ((TextView) certificateView.findViewById(R.id.to_org))
3759 .setText(issuedTo.getOName());
3760 ((TextView) certificateView.findViewById(R.id.to_org_unit))
3761 .setText(issuedTo.getUName());
3762 }
3763
3764 // issued by:
3765 SslCertificate.DName issuedBy = certificate.getIssuedBy();
3766 if (issuedBy != null) {
3767 ((TextView) certificateView.findViewById(R.id.by_common))
3768 .setText(issuedBy.getCName());
3769 ((TextView) certificateView.findViewById(R.id.by_org))
3770 .setText(issuedBy.getOName());
3771 ((TextView) certificateView.findViewById(R.id.by_org_unit))
3772 .setText(issuedBy.getUName());
3773 }
3774
3775 // issued on:
3776 String issuedOn = reformatCertificateDate(
3777 certificate.getValidNotBefore());
3778 ((TextView) certificateView.findViewById(R.id.issued_on))
3779 .setText(issuedOn);
3780
3781 // expires on:
3782 String expiresOn = reformatCertificateDate(
3783 certificate.getValidNotAfter());
3784 ((TextView) certificateView.findViewById(R.id.expires_on))
3785 .setText(expiresOn);
3786
3787 return certificateView;
3788 }
3789
3790 /**
3791 * Re-formats the certificate date (Date.toString()) string to
3792 * a properly localized date string.
3793 * @return Properly localized version of the certificate date string and
3794 * the original certificate date string if fails to localize.
3795 * If the original string is null, returns an empty string "".
3796 */
3797 private String reformatCertificateDate(String certificateDate) {
3798 String reformattedDate = null;
3799
3800 if (certificateDate != null) {
3801 Date date = null;
3802 try {
3803 date = java.text.DateFormat.getInstance().parse(certificateDate);
3804 } catch (ParseException e) {
3805 date = null;
3806 }
3807
3808 if (date != null) {
3809 reformattedDate =
3810 DateFormat.getDateFormat(this).format(date);
3811 }
3812 }
3813
3814 return reformattedDate != null ? reformattedDate :
3815 (certificateDate != null ? certificateDate : "");
3816 }
3817
3818 /**
3819 * Displays an http-authentication dialog.
3820 */
3821 private void showHttpAuthentication(final HttpAuthHandler handler,
3822 final String host, final String realm, final String title,
3823 final String name, final String password, int focusId) {
3824 LayoutInflater factory = LayoutInflater.from(this);
3825 final View v = factory
3826 .inflate(R.layout.http_authentication, null);
3827 if (name != null) {
3828 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3829 }
3830 if (password != null) {
3831 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3832 }
3833
3834 String titleText = title;
3835 if (titleText == null) {
3836 titleText = getText(R.string.sign_in_to).toString().replace(
3837 "%s1", host).replace("%s2", realm);
3838 }
3839
3840 mHttpAuthHandler = handler;
3841 AlertDialog dialog = new AlertDialog.Builder(this)
3842 .setTitle(titleText)
3843 .setIcon(android.R.drawable.ic_dialog_alert)
3844 .setView(v)
3845 .setPositiveButton(R.string.action,
3846 new DialogInterface.OnClickListener() {
3847 public void onClick(DialogInterface dialog,
3848 int whichButton) {
3849 String nm = ((EditText) v
3850 .findViewById(R.id.username_edit))
3851 .getText().toString();
3852 String pw = ((EditText) v
3853 .findViewById(R.id.password_edit))
3854 .getText().toString();
3855 BrowserActivity.this.setHttpAuthUsernamePassword
3856 (host, realm, nm, pw);
3857 handler.proceed(nm, pw);
3858 mHttpAuthenticationDialog = null;
3859 mHttpAuthHandler = null;
3860 }})
3861 .setNegativeButton(R.string.cancel,
3862 new DialogInterface.OnClickListener() {
3863 public void onClick(DialogInterface dialog,
3864 int whichButton) {
3865 handler.cancel();
3866 BrowserActivity.this.resetTitleAndRevertLockIcon();
3867 mHttpAuthenticationDialog = null;
3868 mHttpAuthHandler = null;
3869 }})
3870 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3871 public void onCancel(DialogInterface dialog) {
3872 handler.cancel();
3873 BrowserActivity.this.resetTitleAndRevertLockIcon();
3874 mHttpAuthenticationDialog = null;
3875 mHttpAuthHandler = null;
3876 }})
3877 .create();
3878 // Make the IME appear when the dialog is displayed if applicable.
3879 dialog.getWindow().setSoftInputMode(
3880 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3881 dialog.show();
3882 if (focusId != 0) {
3883 dialog.findViewById(focusId).requestFocus();
3884 } else {
3885 v.findViewById(R.id.username_edit).requestFocus();
3886 }
3887 mHttpAuthenticationDialog = dialog;
3888 }
3889
3890 public int getProgress() {
3891 WebView w = mTabControl.getCurrentWebView();
3892 if (w != null) {
3893 return w.getProgress();
3894 } else {
3895 return 100;
3896 }
3897 }
3898
3899 /**
3900 * Set HTTP authentication password.
3901 *
3902 * @param host The host for the password
3903 * @param realm The realm for the password
3904 * @param username The username for the password. If it is null, it means
3905 * password can't be saved.
3906 * @param password The password
3907 */
3908 public void setHttpAuthUsernamePassword(String host, String realm,
3909 String username,
3910 String password) {
3911 WebView w = mTabControl.getCurrentWebView();
3912 if (w != null) {
3913 w.setHttpAuthUsernamePassword(host, realm, username, password);
3914 }
3915 }
3916
3917 /**
3918 * connectivity manager says net has come or gone... inform the user
3919 * @param up true if net has come up, false if net has gone down
3920 */
3921 public void onNetworkToggle(boolean up) {
3922 if (up == mIsNetworkUp) {
3923 return;
3924 } else if (up) {
3925 mIsNetworkUp = true;
3926 if (mAlertDialog != null) {
3927 mAlertDialog.cancel();
3928 mAlertDialog = null;
3929 }
3930 } else {
3931 mIsNetworkUp = false;
3932 if (mInLoad && mAlertDialog == null) {
3933 mAlertDialog = new AlertDialog.Builder(this)
3934 .setTitle(R.string.loadSuspendedTitle)
3935 .setMessage(R.string.loadSuspended)
3936 .setPositiveButton(R.string.ok, null)
3937 .show();
3938 }
3939 }
3940 WebView w = mTabControl.getCurrentWebView();
3941 if (w != null) {
3942 w.setNetworkAvailable(up);
3943 }
3944 }
3945
3946 @Override
3947 protected void onActivityResult(int requestCode, int resultCode,
3948 Intent intent) {
3949 switch (requestCode) {
3950 case COMBO_PAGE:
3951 if (resultCode == RESULT_OK && intent != null) {
3952 String data = intent.getAction();
3953 Bundle extras = intent.getExtras();
3954 if (extras != null && extras.getBoolean("new_window", false)) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003955 final TabControl.Tab newTab = openTab(data);
3956 if (mSettings.openInBackground() &&
Leon Scroggins1f005d32009-08-10 17:36:42 -04003957 newTab != null) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003958 mTabControl.populatePickerData(newTab);
3959 mTabControl.setCurrentTab(newTab);
Leon Scroggins1f005d32009-08-10 17:36:42 -04003960 int newIndex = mTabControl.getCurrentIndex();
Leon Scroggins3bbb6ca2009-09-09 12:51:10 -04003961 mTitleBar.setCurrentTab(newIndex);
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003962 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003963 } else {
3964 final TabControl.Tab currentTab =
3965 mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04003966 dismissSubWindow(currentTab);
3967 if (data != null && data.length() != 0) {
3968 getTopWindow().loadUrl(data);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003969 }
3970 }
3971 }
3972 break;
3973 default:
3974 break;
3975 }
Leon Scroggins30444232009-09-04 18:36:20 -04003976 getTopWindow().requestFocus();
The Android Open Source Project0c908882009-03-03 19:32:16 -08003977 }
3978
3979 /*
3980 * This method is called as a result of the user selecting the options
3981 * menu to see the download window, or when a download changes state. It
3982 * shows the download window ontop of the current window.
3983 */
3984 /* package */ void viewDownloads(Uri downloadRecord) {
3985 Intent intent = new Intent(this,
3986 BrowserDownloadPage.class);
3987 intent.setData(downloadRecord);
3988 startActivityForResult(intent, this.DOWNLOAD_PAGE);
3989
3990 }
3991
Leon Scroggins160a7e72009-08-14 18:28:01 -04003992 /**
3993 * Open the Go page.
3994 * @param startWithHistory If true, open starting on the history tab.
3995 * Otherwise, start with the bookmarks tab.
Leon Scroggins160a7e72009-08-14 18:28:01 -04003996 */
Leon Scroggins30444232009-09-04 18:36:20 -04003997 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003998 WebView current = mTabControl.getCurrentWebView();
3999 if (current == null) {
4000 return;
4001 }
4002 Intent intent = new Intent(this,
4003 CombinedBookmarkHistoryActivity.class);
4004 String title = current.getTitle();
4005 String url = current.getUrl();
4006 // Just in case the user opens bookmarks before a page finishes loading
4007 // so the current history item, and therefore the page, is null.
4008 if (null == url) {
4009 url = mLastEnteredUrl;
4010 // This can happen.
4011 if (null == url) {
4012 url = mSettings.getHomePage();
4013 }
4014 }
4015 // In case the web page has not yet received its associated title.
4016 if (title == null) {
4017 title = url;
4018 }
4019 intent.putExtra("title", title);
4020 intent.putExtra("url", url);
Leon Scroggins30444232009-09-04 18:36:20 -04004021 // Disable opening in a new window if we have maxed out the windows
4022 intent.putExtra("disable_new_window", mTabControl.getTabCount()
4023 >= TabControl.MAX_TABS);
Patrick Scott3918d442009-08-04 13:22:29 -04004024 intent.putExtra("touch_icon_url", current.getTouchIconUrl());
The Android Open Source Project0c908882009-03-03 19:32:16 -08004025 if (startWithHistory) {
4026 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
4027 CombinedBookmarkHistoryActivity.HISTORY_TAB);
4028 }
4029 startActivityForResult(intent, COMBO_PAGE);
4030 }
4031
4032 // Called when loading from context menu or LOAD_URL message
4033 private void loadURL(WebView view, String url) {
4034 // In case the user enters nothing.
4035 if (url != null && url.length() != 0 && view != null) {
4036 url = smartUrlFilter(url);
4037 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
4038 view.loadUrl(url);
4039 }
4040 }
4041 }
4042
The Android Open Source Project0c908882009-03-03 19:32:16 -08004043 private String smartUrlFilter(Uri inUri) {
4044 if (inUri != null) {
4045 return smartUrlFilter(inUri.toString());
4046 }
4047 return null;
4048 }
4049
4050
4051 // get window count
4052
4053 int getWindowCount(){
4054 if(mTabControl != null){
4055 return mTabControl.getTabCount();
4056 }
4057 return 0;
4058 }
4059
Feng Qianb34f87a2009-03-24 21:27:26 -07004060 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08004061 "(?i)" + // switch on case insensitive matching
4062 "(" + // begin group for schema
4063 "(?:http|https|file):\\/\\/" +
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004064 "|(?:inline|data|about|content|javascript):" +
The Android Open Source Project0c908882009-03-03 19:32:16 -08004065 ")" +
4066 "(.*)" );
4067
4068 /**
4069 * Attempts to determine whether user input is a URL or search
4070 * terms. Anything with a space is passed to search.
4071 *
4072 * Converts to lowercase any mistakenly uppercased schema (i.e.,
4073 * "Http://" converts to "http://"
4074 *
4075 * @return Original or modified URL
4076 *
4077 */
4078 String smartUrlFilter(String url) {
4079
4080 String inUrl = url.trim();
4081 boolean hasSpace = inUrl.indexOf(' ') != -1;
4082
4083 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4084 if (matcher.matches()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004085 // force scheme to lowercase
4086 String scheme = matcher.group(1);
4087 String lcScheme = scheme.toLowerCase();
4088 if (!lcScheme.equals(scheme)) {
Mitsuru Oshima123ecfb2009-05-18 19:11:14 -07004089 inUrl = lcScheme + matcher.group(2);
4090 }
4091 if (hasSpace) {
4092 inUrl = inUrl.replace(" ", "%20");
The Android Open Source Project0c908882009-03-03 19:32:16 -08004093 }
4094 return inUrl;
4095 }
4096 if (hasSpace) {
Satish Sampath565505b2009-05-29 15:37:27 +01004097 // FIXME: Is this the correct place to add to searches?
4098 // what if someone else calls this function?
4099 int shortcut = parseUrlShortcut(inUrl);
4100 if (shortcut != SHORTCUT_INVALID) {
4101 Browser.addSearchUrl(mResolver, inUrl);
4102 String query = inUrl.substring(2);
4103 switch (shortcut) {
4104 case SHORTCUT_GOOGLE_SEARCH:
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004105 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
Satish Sampath565505b2009-05-29 15:37:27 +01004106 case SHORTCUT_WIKIPEDIA_SEARCH:
4107 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4108 case SHORTCUT_DICTIONARY_SEARCH:
4109 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4110 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
The Android Open Source Project0c908882009-03-03 19:32:16 -08004111 // FIXME: we need location in this case
Satish Sampath565505b2009-05-29 15:37:27 +01004112 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004113 }
4114 }
4115 } else {
4116 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4117 return URLUtil.guessUrl(inUrl);
4118 }
4119 }
4120
4121 Browser.addSearchUrl(mResolver, inUrl);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004122 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004123 }
4124
Ben Murdochbff2d602009-07-01 20:19:05 +01004125 /* package */ void setShouldShowErrorConsole(boolean flag) {
4126 if (flag == mShouldShowErrorConsole) {
4127 // Nothing to do.
4128 return;
4129 }
4130
4131 mShouldShowErrorConsole = flag;
4132
4133 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4134
4135 if (flag) {
4136 // Setting the show state of the console will cause it's the layout to be inflated.
4137 if (errorConsole.numberOfErrors() > 0) {
4138 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4139 } else {
4140 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4141 }
4142
4143 // Now we can add it to the main view.
4144 mErrorConsoleContainer.addView(errorConsole,
4145 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4146 ViewGroup.LayoutParams.WRAP_CONTENT));
4147 } else {
4148 mErrorConsoleContainer.removeView(errorConsole);
4149 }
4150
4151 }
4152
The Android Open Source Project0c908882009-03-03 19:32:16 -08004153 private final static int LOCK_ICON_UNSECURE = 0;
4154 private final static int LOCK_ICON_SECURE = 1;
4155 private final static int LOCK_ICON_MIXED = 2;
4156
4157 private int mLockIconType = LOCK_ICON_UNSECURE;
4158 private int mPrevLockType = LOCK_ICON_UNSECURE;
4159
4160 private BrowserSettings mSettings;
4161 private TabControl mTabControl;
4162 private ContentResolver mResolver;
4163 private FrameLayout mContentView;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004164 private View mCustomView;
4165 private FrameLayout mCustomViewContainer;
Andrei Popescuc9b55562009-07-07 10:51:15 +01004166 private WebChromeClient.CustomViewCallback mCustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004167
4168 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4169 // view, we should rewrite this.
4170 private int mCurrentMenuState = 0;
4171 private int mMenuState = R.id.MAIN_MENU;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004172 private int mOldMenuState = EMPTY_MENU;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004173 private static final int EMPTY_MENU = -1;
4174 private Menu mMenu;
4175
4176 private FindDialog mFindDialog;
4177 // Used to prevent chording to result in firing two shortcuts immediately
4178 // one after another. Fixes bug 1211714.
4179 boolean mCanChord;
4180
4181 private boolean mInLoad;
4182 private boolean mIsNetworkUp;
4183
4184 private boolean mPageStarted;
4185 private boolean mActivityInPause = true;
4186
4187 private boolean mMenuIsDown;
4188
4189 private final KeyTracker mKeyTracker = new KeyTracker(this);
4190
4191 // As trackball doesn't send repeat down, we have to track it ourselves
4192 private boolean mTrackTrackball;
4193
4194 private static boolean mInTrace;
4195
4196 // Performance probe
4197 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4198 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4199 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4200 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4201 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4202 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4203 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4204 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4205 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4206 };
4207
4208 private long mStart;
4209 private long mProcessStart;
4210 private long mUserStart;
4211 private long mSystemStart;
4212 private long mIdleStart;
4213 private long mIrqStart;
4214
4215 private long mUiStart;
4216
4217 private Drawable mMixLockIcon;
4218 private Drawable mSecLockIcon;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004219
4220 /* hold a ref so we can auto-cancel if necessary */
4221 private AlertDialog mAlertDialog;
4222
4223 // Wait for credentials before loading google.com
4224 private ProgressDialog mCredsDlg;
4225
4226 // The up-to-date URL and title (these can be different from those stored
4227 // in WebView, since it takes some time for the information in WebView to
4228 // get updated)
4229 private String mUrl;
4230 private String mTitle;
4231
4232 // As PageInfo has different style for landscape / portrait, we have
4233 // to re-open it when configuration changed
4234 private AlertDialog mPageInfoDialog;
4235 private TabControl.Tab mPageInfoView;
4236 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4237 // dialog, we should not just dismiss it, but should get back to the
4238 // SSL-certificate-on-error dialog. This flag is used to store this state
4239 private Boolean mPageInfoFromShowSSLCertificateOnError;
4240
4241 // as SSLCertificateOnError has different style for landscape / portrait,
4242 // we have to re-open it when configuration changed
4243 private AlertDialog mSSLCertificateOnErrorDialog;
4244 private WebView mSSLCertificateOnErrorView;
4245 private SslErrorHandler mSSLCertificateOnErrorHandler;
4246 private SslError mSSLCertificateOnErrorError;
4247
4248 // as SSLCertificate has different style for landscape / portrait, we
4249 // have to re-open it when configuration changed
4250 private AlertDialog mSSLCertificateDialog;
4251 private TabControl.Tab mSSLCertificateView;
4252
4253 // as HttpAuthentication has different style for landscape / portrait, we
4254 // have to re-open it when configuration changed
4255 private AlertDialog mHttpAuthenticationDialog;
4256 private HttpAuthHandler mHttpAuthHandler;
4257
4258 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4259 new FrameLayout.LayoutParams(
4260 ViewGroup.LayoutParams.FILL_PARENT,
4261 ViewGroup.LayoutParams.FILL_PARENT);
Andrei Popescuadc008d2009-06-26 14:11:30 +01004262 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4263 new FrameLayout.LayoutParams(
4264 ViewGroup.LayoutParams.FILL_PARENT,
4265 ViewGroup.LayoutParams.FILL_PARENT,
4266 Gravity.CENTER);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004267 // Google search
4268 final static String QuickSearch_G = "http://www.google.com/m?q=%s";
The Android Open Source Project0c908882009-03-03 19:32:16 -08004269 // Wikipedia search
4270 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4271 // Dictionary search
4272 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4273 // Google Mobile Local search
4274 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4275
4276 final static String QUERY_PLACE_HOLDER = "%s";
4277
4278 // "source" parameter for Google search through search key
4279 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4280 // "source" parameter for Google search through goto menu
4281 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4282 // "source" parameter for Google search through simplily type
4283 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4284 // "source" parameter for Google search suggested by the browser
4285 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4286 // "source" parameter for Google search from unknown source
4287 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4288
4289 private final static String LOGTAG = "browser";
4290
The Android Open Source Project0c908882009-03-03 19:32:16 -08004291 private String mLastEnteredUrl;
4292
4293 private PowerManager.WakeLock mWakeLock;
4294 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4295
4296 private Toast mStopToast;
4297
Leon Scroggins1f005d32009-08-10 17:36:42 -04004298 private TitleBarSet mTitleBar;
Leon Scroggins81db3662009-06-04 17:45:11 -04004299
Ben Murdochbff2d602009-07-01 20:19:05 +01004300 private LinearLayout mErrorConsoleContainer = null;
4301 private boolean mShouldShowErrorConsole = false;
4302
The Android Open Source Project0c908882009-03-03 19:32:16 -08004303 // As the ids are dynamically created, we can't guarantee that they will
4304 // be in sequence, so this static array maps ids to a window number.
4305 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4306 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4307 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4308 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4309
4310 // monitor platform changes
4311 private IntentFilter mNetworkStateChangedFilter;
4312 private BroadcastReceiver mNetworkStateIntentReceiver;
4313
Grace Klobab4da0ad2009-05-14 14:45:40 -07004314 private BroadcastReceiver mPackageInstallationReceiver;
4315
The Android Open Source Project0c908882009-03-03 19:32:16 -08004316 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004317 final static int COMBO_PAGE = 1;
4318 final static int DOWNLOAD_PAGE = 2;
4319 final static int PREFERENCES_PAGE = 3;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004320
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004321 /**
4322 * A UrlData class to abstract how the content will be set to WebView.
4323 * This base class uses loadUrl to show the content.
4324 */
4325 private static class UrlData {
4326 String mUrl;
Grace Kloba60e095c2009-06-16 11:50:55 -07004327 byte[] mPostData;
4328
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004329 UrlData(String url) {
4330 this.mUrl = url;
4331 }
Grace Kloba60e095c2009-06-16 11:50:55 -07004332
4333 void setPostData(byte[] postData) {
4334 mPostData = postData;
4335 }
4336
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004337 boolean isEmpty() {
4338 return mUrl == null || mUrl.length() == 0;
4339 }
4340
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004341 public void loadIn(WebView webView) {
Grace Kloba60e095c2009-06-16 11:50:55 -07004342 if (mPostData != null) {
4343 webView.postUrl(mUrl, mPostData);
4344 } else {
4345 webView.loadUrl(mUrl);
4346 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004347 }
4348 };
4349
4350 /**
4351 * A subclass of UrlData class that can display inlined content using
4352 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4353 */
4354 private static class InlinedUrlData extends UrlData {
4355 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4356 super(failUrl);
4357 mInlined = inlined;
4358 mMimeType = mimeType;
4359 mEncoding = encoding;
4360 }
4361 String mMimeType;
4362 String mInlined;
4363 String mEncoding;
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004364 @Override
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004365 boolean isEmpty() {
Ben Murdochbff2d602009-07-01 20:19:05 +01004366 return mInlined == null || mInlined.length() == 0 || super.isEmpty();
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004367 }
4368
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004369 @Override
4370 public void loadIn(WebView webView) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004371 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4372 }
4373 }
4374
Leon Scroggins1f005d32009-08-10 17:36:42 -04004375 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004376}