blob: c24577cf7de23d1f0bc14b794965ef53867afce1 [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;
Ramanan Rajeswarandd4f4292009-03-24 20:41:19 -070021import com.google.android.providers.GoogleSettings.Partner;
The Android Open Source Project0c908882009-03-03 19:32:16 -080022
23import android.app.Activity;
24import android.app.ActivityManager;
25import android.app.AlertDialog;
26import android.app.ProgressDialog;
27import android.app.SearchManager;
28import android.content.ActivityNotFoundException;
29import android.content.BroadcastReceiver;
30import android.content.ComponentName;
31import android.content.ContentResolver;
32import android.content.ContentValues;
33import android.content.Context;
34import android.content.DialogInterface;
35import android.content.Intent;
36import android.content.IntentFilter;
37import android.content.ServiceConnection;
38import android.content.DialogInterface.OnCancelListener;
39import android.content.pm.PackageManager;
40import android.content.pm.ResolveInfo;
41import android.content.res.AssetManager;
42import android.content.res.Configuration;
43import android.content.res.Resources;
44import android.database.Cursor;
45import android.database.sqlite.SQLiteDatabase;
46import android.database.sqlite.SQLiteException;
47import android.graphics.Bitmap;
48import android.graphics.Canvas;
49import android.graphics.Color;
50import android.graphics.DrawFilter;
51import android.graphics.Paint;
52import android.graphics.PaintFlagsDrawFilter;
53import android.graphics.Picture;
54import android.graphics.drawable.BitmapDrawable;
55import android.graphics.drawable.Drawable;
56import android.graphics.drawable.LayerDrawable;
57import android.graphics.drawable.PaintDrawable;
58import android.hardware.SensorListener;
59import android.hardware.SensorManager;
60import android.net.ConnectivityManager;
61import android.net.Uri;
62import android.net.WebAddress;
63import android.net.http.EventHandler;
64import android.net.http.SslCertificate;
65import android.net.http.SslError;
66import android.os.AsyncTask;
67import android.os.Bundle;
68import android.os.Debug;
69import android.os.Environment;
70import android.os.Handler;
71import android.os.IBinder;
72import android.os.Message;
73import android.os.PowerManager;
74import android.os.Process;
75import android.os.RemoteException;
76import android.os.ServiceManager;
77import android.os.SystemClock;
78import android.os.SystemProperties;
79import android.preference.PreferenceManager;
80import android.provider.Browser;
81import android.provider.Contacts;
82import android.provider.Downloads;
83import android.provider.MediaStore;
84import android.provider.Contacts.Intents.Insert;
85import android.text.IClipboard;
86import android.text.TextUtils;
87import android.text.format.DateFormat;
88import android.text.util.Regex;
The Android Open Source Project0c908882009-03-03 19:32:16 -080089import android.util.Log;
90import android.view.ContextMenu;
91import android.view.Gravity;
92import android.view.KeyEvent;
93import android.view.LayoutInflater;
94import android.view.Menu;
95import android.view.MenuInflater;
96import android.view.MenuItem;
97import android.view.View;
98import android.view.ViewGroup;
99import android.view.Window;
100import android.view.WindowManager;
101import android.view.ContextMenu.ContextMenuInfo;
102import android.view.MenuItem.OnMenuItemClickListener;
103import android.view.animation.AlphaAnimation;
104import android.view.animation.Animation;
105import android.view.animation.AnimationSet;
106import android.view.animation.DecelerateInterpolator;
107import android.view.animation.ScaleAnimation;
108import android.view.animation.TranslateAnimation;
109import android.webkit.CookieManager;
110import android.webkit.CookieSyncManager;
111import android.webkit.DownloadListener;
112import android.webkit.HttpAuthHandler;
113import android.webkit.SslErrorHandler;
114import android.webkit.URLUtil;
115import android.webkit.WebChromeClient;
116import android.webkit.WebHistoryItem;
117import android.webkit.WebIconDatabase;
Ben Murdoch092dd5d2009-04-22 12:34:12 +0100118import android.webkit.WebStorage;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800119import android.webkit.WebView;
120import android.webkit.WebViewClient;
121import android.widget.EditText;
122import android.widget.FrameLayout;
123import android.widget.LinearLayout;
124import android.widget.TextView;
125import android.widget.Toast;
126
127import java.io.BufferedOutputStream;
128import 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;
135import java.net.URL;
136import java.net.URLEncoder;
137import java.text.ParseException;
138import java.util.Date;
139import java.util.Enumeration;
140import java.util.HashMap;
Patrick Scott37911c72009-03-24 18:02:58 -0700141import java.util.LinkedList;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800142import java.util.Locale;
143import 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
Nicolas Roard78a98e42009-05-11 13:34:17 +0100166 private WebStorage.QuotaUpdater mWebStorageQuotaUpdater = null;
167
The Android Open Source Project0c908882009-03-03 19:32:16 -0800168 /* Whitelisted webpages
169 private static HashSet<String> sWhiteList;
170
171 static {
172 sWhiteList = new HashSet<String>();
173 sWhiteList.add("cnn.com/");
174 sWhiteList.add("espn.go.com/");
175 sWhiteList.add("nytimes.com/");
176 sWhiteList.add("engadget.com/");
177 sWhiteList.add("yahoo.com/");
178 sWhiteList.add("msn.com/");
179 sWhiteList.add("amazon.com/");
180 sWhiteList.add("consumerist.com/");
181 sWhiteList.add("google.com/m/news");
182 }
183 */
184
185 private void setupHomePage() {
186 final Runnable getAccount = new Runnable() {
187 public void run() {
188 // Lower priority
189 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
190 // get the default home page
191 String homepage = mSettings.getHomePage();
192
193 try {
194 if (mGls == null) return;
195
196 String hostedUser = mGls.getAccount(GoogleLoginServiceConstants.PREFER_HOSTED);
197 String googleUser = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE);
198
199 // three cases:
200 //
201 // hostedUser == googleUser
202 // The device has only a google account
203 //
204 // hostedUser != googleUser
205 // The device has a hosted account and a google account
206 //
207 // hostedUser != null, googleUser == null
208 // The device has only a hosted account (so far)
209
210 // developers might have no accounts at all
211 if (hostedUser == null) return;
212
213 if (googleUser == null || !hostedUser.equals(googleUser)) {
214 String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1);
215 homepage = "http://www.google.com/m/a/" + domain + "?client=ms-" +
Ramanan Rajeswarandd4f4292009-03-24 20:41:19 -0700216 Partner.getString(BrowserActivity.this.getContentResolver(), Partner.CLIENT_ID);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800217 }
218 } catch (RemoteException ignore) {
219 // Login service died; carry on
220 } catch (RuntimeException ignore) {
221 // Login service died; carry on
222 } finally {
223 finish(homepage);
224 }
225 }
226
227 private void finish(final String homepage) {
228 mHandler.post(new Runnable() {
229 public void run() {
230 mSettings.setHomePage(BrowserActivity.this, homepage);
231 resumeAfterCredentials();
232
233 // as this is running in a separate thread,
234 // BrowserActivity's onDestroy() may have been called,
235 // which also calls unbindService().
236 if (mGlsConnection != null) {
237 // we no longer need to keep GLS open
238 unbindService(mGlsConnection);
239 mGlsConnection = null;
240 }
241 } });
242 } };
243
244 final boolean[] done = { false };
245
246 // Open a connection to the Google Login Service. The first
247 // time the connection is established, set up the homepage depending on
248 // the account in a background thread.
249 mGlsConnection = new ServiceConnection() {
250 public void onServiceConnected(ComponentName className, IBinder service) {
251 mGls = IGoogleLoginService.Stub.asInterface(service);
252 if (done[0] == false) {
253 done[0] = true;
254 Thread account = new Thread(getAccount);
255 account.setName("GLSAccount");
256 account.start();
257 }
258 }
259 public void onServiceDisconnected(ComponentName className) {
260 mGls = null;
261 }
262 };
263
264 bindService(GoogleLoginServiceConstants.SERVICE_INTENT,
265 mGlsConnection, Context.BIND_AUTO_CREATE);
266 }
267
268 /**
269 * This class is in charge of installing pre-packaged plugins
270 * from the Browser assets directory to the user's data partition.
271 * Plugins are loaded from the "plugins" directory in the assets;
272 * Anything that is in this directory will be copied over to the
273 * user data partition in app_plugins.
274 */
275 private class CopyPlugins implements Runnable {
276 final static String TAG = "PluginsInstaller";
277 final static String ZIP_FILTER = "assets/plugins/";
278 final static String APK_PATH = "/system/app/Browser.apk";
279 final static String PLUGIN_EXTENSION = ".so";
280 final static String TEMPORARY_EXTENSION = "_temp";
281 final static String BUILD_INFOS_FILE = "build.prop";
282 final static String SYSTEM_BUILD_INFOS_FILE = "/system/"
283 + BUILD_INFOS_FILE;
284 final int BUFSIZE = 4096;
285 boolean mDoOverwrite = false;
286 String pluginsPath;
287 Context mContext;
288 File pluginsDir;
289 AssetManager manager;
290
291 public CopyPlugins (boolean overwrite, Context context) {
292 mDoOverwrite = overwrite;
293 mContext = context;
294 }
295
296 /**
297 * Returned a filtered list of ZipEntry.
298 * We list all the files contained in the zip and
299 * only returns the ones starting with the ZIP_FILTER
300 * path.
301 *
302 * @param zip the zip file used.
303 */
304 public Vector<ZipEntry> pluginsFilesFromZip(ZipFile zip) {
305 Vector<ZipEntry> list = new Vector<ZipEntry>();
306 Enumeration entries = zip.entries();
307 while (entries.hasMoreElements()) {
308 ZipEntry entry = (ZipEntry) entries.nextElement();
309 if (entry.getName().startsWith(ZIP_FILTER)) {
310 list.add(entry);
311 }
312 }
313 return list;
314 }
315
316 /**
317 * Utility method to copy the content from an inputstream
318 * to a file output stream.
319 */
320 public void copyStreams(InputStream is, FileOutputStream fos) {
321 BufferedOutputStream os = null;
322 try {
323 byte data[] = new byte[BUFSIZE];
324 int count;
325 os = new BufferedOutputStream(fos, BUFSIZE);
326 while ((count = is.read(data, 0, BUFSIZE)) != -1) {
327 os.write(data, 0, count);
328 }
329 os.flush();
330 } catch (IOException e) {
331 Log.e(TAG, "Exception while copying: " + e);
332 } finally {
333 try {
334 if (os != null) {
335 os.close();
336 }
337 } catch (IOException e2) {
338 Log.e(TAG, "Exception while closing the stream: " + e2);
339 }
340 }
341 }
342
343 /**
344 * Returns a string containing the contents of a file
345 *
346 * @param file the target file
347 */
348 private String contentsOfFile(File file) {
349 String ret = null;
350 FileInputStream is = null;
351 try {
352 byte[] buffer = new byte[BUFSIZE];
353 int count;
354 is = new FileInputStream(file);
355 StringBuffer out = new StringBuffer();
356
357 while ((count = is.read(buffer, 0, BUFSIZE)) != -1) {
358 out.append(new String(buffer, 0, count));
359 }
360 ret = out.toString();
361 } catch (IOException e) {
362 Log.e(TAG, "Exception getting contents of file " + e);
363 } finally {
364 if (is != null) {
365 try {
366 is.close();
367 } catch (IOException e2) {
368 Log.e(TAG, "Exception while closing the file: " + e2);
369 }
370 }
371 }
372 return ret;
373 }
374
375 /**
376 * Utility method to initialize the user data plugins path.
377 */
378 public void initPluginsPath() {
379 BrowserSettings s = BrowserSettings.getInstance();
380 pluginsPath = s.getPluginsPath();
381 if (pluginsPath == null) {
382 s.loadFromDb(mContext);
383 pluginsPath = s.getPluginsPath();
384 }
Dave Bort31a6d1c2009-04-13 15:56:49 -0700385 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800386 Log.v(TAG, "Plugin path: " + pluginsPath);
387 }
388 }
389
390 /**
391 * Utility method to delete a file or a directory
392 *
393 * @param file the File to delete
394 */
395 public void deleteFile(File file) {
396 File[] files = file.listFiles();
397 if ((files != null) && files.length > 0) {
398 for (int i=0; i< files.length; i++) {
399 deleteFile(files[i]);
400 }
401 }
402 if (!file.delete()) {
403 Log.e(TAG, file.getPath() + " could not get deleted");
404 }
405 }
406
407 /**
408 * Clean the content of the plugins directory.
409 * We delete the directory, then recreate it.
410 */
411 public void cleanPluginsDirectory() {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700412 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800413 Log.v(TAG, "delete plugins directory: " + pluginsPath);
414 }
415 File pluginsDirectory = new File(pluginsPath);
416 deleteFile(pluginsDirectory);
417 pluginsDirectory.mkdir();
418 }
419
420
421 /**
422 * Copy the SYSTEM_BUILD_INFOS_FILE file containing the
423 * informations about the system build to the
424 * BUILD_INFOS_FILE in the plugins directory.
425 */
426 public void copyBuildInfos() {
427 try {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700428 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800429 Log.v(TAG, "Copy build infos to the plugins directory");
430 }
431 File buildInfoFile = new File(SYSTEM_BUILD_INFOS_FILE);
432 File buildInfoPlugins = new File(pluginsPath, BUILD_INFOS_FILE);
433 copyStreams(new FileInputStream(buildInfoFile),
434 new FileOutputStream(buildInfoPlugins));
435 } catch (IOException e) {
436 Log.e(TAG, "Exception while copying the build infos: " + e);
437 }
438 }
439
440 /**
441 * Returns true if the current system is newer than the
442 * system that installed the plugins.
443 * We determinate this by checking the build number of the system.
444 *
445 * At the end of the plugins copy operation, we copy the
446 * SYSTEM_BUILD_INFOS_FILE to the BUILD_INFOS_FILE.
447 * We then just have to load both and compare them -- if they
448 * are different the current system is newer.
449 *
450 * Loading and comparing the strings should be faster than
451 * creating a hash, the files being rather small. Extracting the
452 * version number would require some parsing which may be more
453 * brittle.
454 */
455 public boolean newSystemImage() {
456 try {
457 File buildInfoFile = new File(SYSTEM_BUILD_INFOS_FILE);
458 File buildInfoPlugins = new File(pluginsPath, BUILD_INFOS_FILE);
459 if (!buildInfoPlugins.exists()) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700460 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800461 Log.v(TAG, "build.prop in plugins directory " + pluginsPath
462 + " does not exist, therefore it's a new system image");
463 }
464 return true;
465 } else {
466 String buildInfo = contentsOfFile(buildInfoFile);
467 String buildInfoPlugin = contentsOfFile(buildInfoPlugins);
468 if (buildInfo == null || buildInfoPlugin == null
469 || buildInfo.compareTo(buildInfoPlugin) != 0) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700470 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800471 Log.v(TAG, "build.prop are different, "
472 + " therefore it's a new system image");
473 }
474 return true;
475 }
476 }
477 } catch (Exception e) {
478 Log.e(TAG, "Exc in newSystemImage(): " + e);
479 }
480 return false;
481 }
482
483 /**
484 * Check if the version of the plugins contained in the
485 * Browser assets is the same as the version of the plugins
486 * in the plugins directory.
487 * We simply iterate on every file in the assets/plugins
488 * and return false if a file listed in the assets does
489 * not exist in the plugins directory.
490 */
491 private boolean checkIsDifferentVersions() {
492 try {
493 ZipFile zip = new ZipFile(APK_PATH);
494 Vector<ZipEntry> files = pluginsFilesFromZip(zip);
495 int zipFilterLength = ZIP_FILTER.length();
496
497 Enumeration entries = files.elements();
498 while (entries.hasMoreElements()) {
499 ZipEntry entry = (ZipEntry) entries.nextElement();
500 String path = entry.getName().substring(zipFilterLength);
501 File outputFile = new File(pluginsPath, path);
502 if (!outputFile.exists()) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700503 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800504 Log.v(TAG, "checkIsDifferentVersions(): extracted file "
505 + path + " does not exist, we have a different version");
506 }
507 return true;
508 }
509 }
510 } catch (IOException e) {
511 Log.e(TAG, "Exception in checkDifferentVersions(): " + e);
512 }
513 return false;
514 }
515
516 /**
517 * Copy every files from the assets/plugins directory
518 * to the app_plugins directory in the data partition.
519 * Once copied, we copy over the SYSTEM_BUILD_INFOS file
520 * in the plugins directory.
521 *
522 * NOTE: we directly access the content from the Browser
523 * package (it's a zip file) and do not use AssetManager
524 * as there is a limit of 1Mb (see Asset.h)
525 */
526 public void run() {
527 // Lower the priority
528 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
529 try {
530 if (pluginsPath == null) {
531 Log.e(TAG, "No plugins path found!");
532 return;
533 }
534
535 ZipFile zip = new ZipFile(APK_PATH);
536 Vector<ZipEntry> files = pluginsFilesFromZip(zip);
537 Vector<File> plugins = new Vector<File>();
538 int zipFilterLength = ZIP_FILTER.length();
539
540 Enumeration entries = files.elements();
541 while (entries.hasMoreElements()) {
542 ZipEntry entry = (ZipEntry) entries.nextElement();
543 String path = entry.getName().substring(zipFilterLength);
544 File outputFile = new File(pluginsPath, path);
545 outputFile.getParentFile().mkdirs();
546
547 if (outputFile.exists() && !mDoOverwrite) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700548 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800549 Log.v(TAG, path + " already extracted.");
550 }
551 } else {
552 if (path.endsWith(PLUGIN_EXTENSION)) {
553 // We rename plugins to be sure a half-copied
554 // plugin is not loaded by the browser.
555 plugins.add(outputFile);
556 outputFile = new File(pluginsPath,
557 path + TEMPORARY_EXTENSION);
558 }
559 FileOutputStream fos = new FileOutputStream(outputFile);
Dave Bort31a6d1c2009-04-13 15:56:49 -0700560 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800561 Log.v(TAG, "copy " + entry + " to "
562 + pluginsPath + "/" + path);
563 }
564 copyStreams(zip.getInputStream(entry), fos);
565 }
566 }
567
568 // We now rename the .so we copied, once all their resources
569 // are safely copied over to the user data partition.
570 Enumeration elems = plugins.elements();
571 while (elems.hasMoreElements()) {
572 File renamedFile = (File) elems.nextElement();
573 File sourceFile = new File(renamedFile.getPath()
574 + TEMPORARY_EXTENSION);
Dave Bort31a6d1c2009-04-13 15:56:49 -0700575 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800576 Log.v(TAG, "rename " + sourceFile.getPath()
577 + " to " + renamedFile.getPath());
578 }
579 sourceFile.renameTo(renamedFile);
580 }
581
582 copyBuildInfos();
583
584 // Refresh the plugin list.
585 if (mTabControl.getCurrentWebView() != null) {
586 mTabControl.getCurrentWebView().refreshPlugins(false);
587 }
588 } catch (IOException e) {
589 Log.e(TAG, "IO Exception: " + e);
590 }
591 }
592 };
593
594 /**
595 * Copy the content of assets/plugins/ to the app_plugins directory
596 * in the data partition.
597 *
598 * This function is called every time the browser is started.
599 * We first check if the system image is newer than the one that
600 * copied the plugins (if there's plugins in the data partition).
601 * If this is the case, we then check if the versions are different.
602 * If they are different, we clean the plugins directory in the
603 * data partition, then start a thread to copy the plugins while
604 * the browser continue to load.
605 *
606 * @param overwrite if true overwrite the files even if they are
607 * already present (to let the user "reset" the plugins if needed).
608 */
609 private void copyPlugins(boolean overwrite) {
610 CopyPlugins copyPluginsFromAssets = new CopyPlugins(overwrite, this);
611 copyPluginsFromAssets.initPluginsPath();
612 if (copyPluginsFromAssets.newSystemImage()) {
613 if (copyPluginsFromAssets.checkIsDifferentVersions()) {
614 copyPluginsFromAssets.cleanPluginsDirectory();
615 Thread copyplugins = new Thread(copyPluginsFromAssets);
616 copyplugins.setName("CopyPlugins");
617 copyplugins.start();
618 }
619 }
620 }
621
622 private class ClearThumbnails extends AsyncTask<File, Void, Void> {
623 @Override
624 public Void doInBackground(File... files) {
625 if (files != null) {
626 for (File f : files) {
627 f.delete();
628 }
629 }
630 return null;
631 }
632 }
633
634 @Override public void onCreate(Bundle icicle) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700635 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800636 Log.v(LOGTAG, this + " onStart");
637 }
638 super.onCreate(icicle);
639 this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
640 this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
641 this.requestWindowFeature(Window.FEATURE_PROGRESS);
642 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
643
644 // test the browser in OpenGL
645 // requestWindowFeature(Window.FEATURE_OPENGL);
646
647 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
648
649 mResolver = getContentResolver();
650
651 setBaseSearchUrl(PreferenceManager.getDefaultSharedPreferences(this)
652 .getString("search_url", ""));
653
654 //
655 // start MASF proxy service
656 //
657 //Intent proxyServiceIntent = new Intent();
658 //proxyServiceIntent.setComponent
659 // (new ComponentName(
660 // "com.android.masfproxyservice",
661 // "com.android.masfproxyservice.MasfProxyService"));
662 //startService(proxyServiceIntent, null);
663
664 mSecLockIcon = Resources.getSystem().getDrawable(
665 android.R.drawable.ic_secure);
666 mMixLockIcon = Resources.getSystem().getDrawable(
667 android.R.drawable.ic_partial_secure);
668 mGenericFavicon = getResources().getDrawable(
669 R.drawable.app_web_browser_sm);
670
671 mContentView = (FrameLayout) getWindow().getDecorView().findViewById(
672 com.android.internal.R.id.content);
673
674 // Create the tab control and our initial tab
675 mTabControl = new TabControl(this);
676
677 // Open the icon database and retain all the bookmark urls for favicons
678 retainIconsOnStartup();
679
680 // Keep a settings instance handy.
681 mSettings = BrowserSettings.getInstance();
682 mSettings.setTabControl(mTabControl);
683 mSettings.loadFromDb(this);
684
685 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
686 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
687
688 if (!mTabControl.restoreState(icicle)) {
689 // clear up the thumbnail directory if we can't restore the state as
690 // none of the files in the directory are referenced any more.
691 new ClearThumbnails().execute(
692 mTabControl.getThumbnailDir().listFiles());
693 final Intent intent = getIntent();
694 final Bundle extra = intent.getExtras();
695 // Create an initial tab.
696 // If the intent is ACTION_VIEW and data is not null, the Browser is
697 // invoked to view the content by another application. In this case,
698 // the tab will be close when exit.
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700699 String url = getUrlFromIntent(intent);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800700 final TabControl.Tab t = mTabControl.createNewTab(
701 Intent.ACTION_VIEW.equals(intent.getAction()) &&
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700702 intent.getData() != null,
703 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800704 mTabControl.setCurrentTab(t);
705 // This is one of the only places we call attachTabToContentView
706 // without animating from the tab picker.
707 attachTabToContentView(t);
708 WebView webView = t.getWebView();
709 if (extra != null) {
710 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
711 if (scale > 0 && scale <= 1000) {
712 webView.setInitialScale(scale);
713 }
714 }
715 // If we are not restoring from an icicle, then there is a high
716 // likely hood this is the first run. So, check to see if the
717 // homepage needs to be configured and copy any plugins from our
718 // asset directory to the data partition.
719 if ((extra == null || !extra.getBoolean("testing"))
720 && !mSettings.isLoginInitialized()) {
721 setupHomePage();
722 }
723 copyPlugins(true);
724
The Android Open Source Project0c908882009-03-03 19:32:16 -0800725 if (url == null || url.length() == 0) {
726 if (mSettings.isLoginInitialized()) {
727 webView.loadUrl(mSettings.getHomePage());
728 } else {
729 waitForCredentials();
730 }
731 } else {
732 webView.loadUrl(url);
733 }
734 } else {
735 // TabControl.restoreState() will create a new tab even if
736 // restoring the state fails. Attach it to the view here since we
737 // are not animating from the tab picker.
738 attachTabToContentView(mTabControl.getCurrentTab());
739 }
740
741 /* enables registration for changes in network status from
742 http stack */
743 mNetworkStateChangedFilter = new IntentFilter();
744 mNetworkStateChangedFilter.addAction(
745 ConnectivityManager.CONNECTIVITY_ACTION);
746 mNetworkStateIntentReceiver = new BroadcastReceiver() {
747 @Override
748 public void onReceive(Context context, Intent intent) {
749 if (intent.getAction().equals(
750 ConnectivityManager.CONNECTIVITY_ACTION)) {
751 boolean down = intent.getBooleanExtra(
752 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
753 onNetworkToggle(!down);
754 }
755 }
756 };
757 }
758
759 @Override
760 protected void onNewIntent(Intent intent) {
761 TabControl.Tab current = mTabControl.getCurrentTab();
762 // When a tab is closed on exit, the current tab index is set to -1.
763 // Reset before proceed as Browser requires the current tab to be set.
764 if (current == null) {
765 // Try to reset the tab in case the index was incorrect.
766 current = mTabControl.getTab(0);
767 if (current == null) {
768 // No tabs at all so just ignore this intent.
769 return;
770 }
771 mTabControl.setCurrentTab(current);
772 attachTabToContentView(current);
773 resetTitleAndIcon(current.getWebView());
774 }
775 final String action = intent.getAction();
776 final int flags = intent.getFlags();
777 if (Intent.ACTION_MAIN.equals(action) ||
778 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
779 // just resume the browser
780 return;
781 }
782 if (Intent.ACTION_VIEW.equals(action)
783 || Intent.ACTION_SEARCH.equals(action)
784 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
785 || Intent.ACTION_WEB_SEARCH.equals(action)) {
786 String url = getUrlFromIntent(intent);
787 if (url == null || url.length() == 0) {
788 url = mSettings.getHomePage();
789 }
790 if (Intent.ACTION_VIEW.equals(action) &&
791 (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700792 final String appId =
793 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID);
794 final TabControl.Tab appTab = mTabControl.getTabFromId(appId);
795 if (appTab != null) {
796 Log.i(LOGTAG, "Reusing tab for " + appId);
797 // Dismiss the subwindow if applicable.
798 dismissSubWindow(appTab);
799 // Since we might kill the WebView, remove it from the
800 // content view first.
801 removeTabFromContentView(appTab);
802 // Recreate the main WebView after destroying the old one.
803 // If the WebView has the same original url and is on that
804 // page, it can be reused.
805 boolean needsLoad =
806 mTabControl.recreateWebView(appTab, url);
807 if (current != appTab) {
808 showTab(appTab, needsLoad ? url : null);
809 } else {
810 if (mTabOverview != null && mAnimationCount == 0) {
811 sendAnimateFromOverview(appTab, false,
812 needsLoad ? url : null, TAB_OVERVIEW_DELAY,
813 null);
814 } else {
815 // If the tab was the current tab, we have to attach
816 // it to the view system again.
817 attachTabToContentView(appTab);
818 if (needsLoad) {
819 appTab.getWebView().loadUrl(url);
820 }
821 }
822 }
823 return;
824 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800825 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url will be
826 // opened in a new tab unless we have reached MAX_TABS. Then the
827 // url will be opened in the current tab. If a new tab is
828 // created, it will have "true" for exit on close.
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700829 openTabAndShow(url, null, true, appId);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800830 } else {
831 if ("about:debug".equals(url)) {
832 mSettings.toggleDebugSettings();
833 return;
834 }
835 // If the Window overview is up and we are not in the midst of
836 // an animation, animate away from the Window overview.
837 if (mTabOverview != null && mAnimationCount == 0) {
838 sendAnimateFromOverview(current, false, url,
839 TAB_OVERVIEW_DELAY, null);
840 } else {
841 // Get rid of the subwindow if it exists
842 dismissSubWindow(current);
843 current.getWebView().loadUrl(url);
844 }
845 }
846 }
847 }
848
849 private String getUrlFromIntent(Intent intent) {
850 String url = null;
851 if (intent != null) {
852 final String action = intent.getAction();
853 if (Intent.ACTION_VIEW.equals(action)) {
854 url = smartUrlFilter(intent.getData());
855 if (url != null && url.startsWith("content:")) {
856 /* Append mimetype so webview knows how to display */
857 String mimeType = intent.resolveType(getContentResolver());
858 if (mimeType != null) {
859 url += "?" + mimeType;
860 }
861 }
862 } else if (Intent.ACTION_SEARCH.equals(action)
863 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
864 || Intent.ACTION_WEB_SEARCH.equals(action)) {
865 url = intent.getStringExtra(SearchManager.QUERY);
866 if (url != null) {
867 mLastEnteredUrl = url;
868 // Don't add Urls, just search terms.
869 // Urls will get added when the page is loaded.
870 if (!Regex.WEB_URL_PATTERN.matcher(url).matches()) {
871 Browser.updateVisitedHistory(mResolver, url, false);
872 }
873 // In general, we shouldn't modify URL from Intent.
874 // But currently, we get the user-typed URL from search box as well.
875 url = fixUrl(url);
876 url = smartUrlFilter(url);
877 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
878 if (url.contains(searchSource)) {
879 String source = null;
880 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
881 if (appData != null) {
882 source = appData.getString(SearchManager.SOURCE);
883 }
884 if (TextUtils.isEmpty(source)) {
885 source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
886 }
887 url = url.replace(searchSource, "&source=android-"+source+"&");
888 }
889 }
890 }
891 }
892 return url;
893 }
894
895 /* package */ static String fixUrl(String inUrl) {
896 if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
897 return inUrl;
898 if (inUrl.startsWith("http:") ||
899 inUrl.startsWith("https:")) {
900 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
901 inUrl = inUrl.replaceFirst("/", "//");
902 } else inUrl = inUrl.replaceFirst(":", "://");
903 }
904 return inUrl;
905 }
906
907 /**
908 * Looking for the pattern like this
909 *
910 * *
911 * * *
912 * *** * *******
913 * * *
914 * * *
915 * *
916 */
917 private final SensorListener mSensorListener = new SensorListener() {
918 private long mLastGestureTime;
919 private float[] mPrev = new float[3];
920 private float[] mPrevDiff = new float[3];
921 private float[] mDiff = new float[3];
922 private float[] mRevertDiff = new float[3];
923
924 public void onSensorChanged(int sensor, float[] values) {
925 boolean show = false;
926 float[] diff = new float[3];
927
928 for (int i = 0; i < 3; i++) {
929 diff[i] = values[i] - mPrev[i];
930 if (Math.abs(diff[i]) > 1) {
931 show = true;
932 }
933 if ((diff[i] > 1.0 && mDiff[i] < 0.2)
934 || (diff[i] < -1.0 && mDiff[i] > -0.2)) {
935 // start track when there is a big move, or revert
936 mRevertDiff[i] = mDiff[i];
937 mDiff[i] = 0;
938 } else if (diff[i] > -0.2 && diff[i] < 0.2) {
939 // reset when it is flat
940 mDiff[i] = mRevertDiff[i] = 0;
941 }
942 mDiff[i] += diff[i];
943 mPrevDiff[i] = diff[i];
944 mPrev[i] = values[i];
945 }
946
947 if (false) {
948 // only shows if we think the delta is big enough, in an attempt
949 // to detect "serious" moves left/right or up/down
950 Log.d("BrowserSensorHack", "sensorChanged " + sensor + " ("
951 + values[0] + ", " + values[1] + ", " + values[2] + ")"
952 + " diff(" + diff[0] + " " + diff[1] + " " + diff[2]
953 + ")");
954 Log.d("BrowserSensorHack", " mDiff(" + mDiff[0] + " "
955 + mDiff[1] + " " + mDiff[2] + ")" + " mRevertDiff("
956 + mRevertDiff[0] + " " + mRevertDiff[1] + " "
957 + mRevertDiff[2] + ")");
958 }
959
960 long now = android.os.SystemClock.uptimeMillis();
961 if (now - mLastGestureTime > 1000) {
962 mLastGestureTime = 0;
963
964 float y = mDiff[1];
965 float z = mDiff[2];
966 float ay = Math.abs(y);
967 float az = Math.abs(z);
968 float ry = mRevertDiff[1];
969 float rz = mRevertDiff[2];
970 float ary = Math.abs(ry);
971 float arz = Math.abs(rz);
972 boolean gestY = ay > 2.5f && ary > 1.0f && ay > ary;
973 boolean gestZ = az > 3.5f && arz > 1.0f && az > arz;
974
975 if ((gestY || gestZ) && !(gestY && gestZ)) {
976 WebView view = mTabControl.getCurrentWebView();
977
978 if (view != null) {
979 if (gestZ) {
980 if (z < 0) {
981 view.zoomOut();
982 } else {
983 view.zoomIn();
984 }
985 } else {
986 view.flingScroll(0, Math.round(y * 100));
987 }
988 }
989 mLastGestureTime = now;
990 }
991 }
992 }
993
994 public void onAccuracyChanged(int sensor, int accuracy) {
995 // TODO Auto-generated method stub
996
997 }
998 };
999
1000 @Override protected void onResume() {
1001 super.onResume();
Dave Bort31a6d1c2009-04-13 15:56:49 -07001002 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001003 Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
1004 }
1005
1006 if (!mActivityInPause) {
1007 Log.e(LOGTAG, "BrowserActivity is already resumed.");
1008 return;
1009 }
1010
1011 mActivityInPause = false;
1012 resumeWebView();
1013
1014 if (mWakeLock.isHeld()) {
1015 mHandler.removeMessages(RELEASE_WAKELOCK);
1016 mWakeLock.release();
1017 }
1018
1019 if (mCredsDlg != null) {
1020 if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
1021 // In case credential request never comes back
1022 mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
1023 }
1024 }
1025
1026 registerReceiver(mNetworkStateIntentReceiver,
1027 mNetworkStateChangedFilter);
1028 WebView.enablePlatformNotifications();
1029
1030 if (mSettings.doFlick()) {
1031 if (mSensorManager == null) {
1032 mSensorManager = (SensorManager) getSystemService(
1033 Context.SENSOR_SERVICE);
1034 }
1035 mSensorManager.registerListener(mSensorListener,
1036 SensorManager.SENSOR_ACCELEROMETER,
1037 SensorManager.SENSOR_DELAY_FASTEST);
1038 } else {
1039 mSensorManager = null;
1040 }
1041 }
1042
1043 /**
1044 * onSaveInstanceState(Bundle map)
1045 * onSaveInstanceState is called right before onStop(). The map contains
1046 * the saved state.
1047 */
1048 @Override protected void onSaveInstanceState(Bundle outState) {
Dave Bort31a6d1c2009-04-13 15:56:49 -07001049 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001050 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
1051 }
1052 // the default implementation requires each view to have an id. As the
1053 // browser handles the state itself and it doesn't use id for the views,
1054 // don't call the default implementation. Otherwise it will trigger the
1055 // warning like this, "couldn't save which view has focus because the
1056 // focused view XXX has no id".
1057
1058 // Save all the tabs
1059 mTabControl.saveState(outState);
1060 }
1061
1062 @Override protected void onPause() {
1063 super.onPause();
1064
1065 if (mActivityInPause) {
1066 Log.e(LOGTAG, "BrowserActivity is already paused.");
1067 return;
1068 }
1069
1070 mActivityInPause = true;
1071 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebView()) {
1072 mWakeLock.acquire();
1073 mHandler.sendMessageDelayed(mHandler
1074 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
1075 }
1076
1077 // Clear the credentials toast if it is up
1078 if (mCredsDlg != null && mCredsDlg.isShowing()) {
1079 mCredsDlg.dismiss();
1080 }
1081 mCredsDlg = null;
1082
1083 cancelStopToast();
1084
1085 // unregister network state listener
1086 unregisterReceiver(mNetworkStateIntentReceiver);
1087 WebView.disablePlatformNotifications();
1088
1089 if (mSensorManager != null) {
1090 mSensorManager.unregisterListener(mSensorListener);
1091 }
1092 }
1093
1094 @Override protected void onDestroy() {
Dave Bort31a6d1c2009-04-13 15:56:49 -07001095 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001096 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
1097 }
1098 super.onDestroy();
1099 // Remove the current tab and sub window
1100 TabControl.Tab t = mTabControl.getCurrentTab();
Patrick Scottfb5e77f2009-04-08 19:17:37 -07001101 if (t != null) {
1102 dismissSubWindow(t);
1103 removeTabFromContentView(t);
1104 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001105 // Destroy all the tabs
1106 mTabControl.destroy();
1107 WebIconDatabase.getInstance().close();
1108 if (mGlsConnection != null) {
1109 unbindService(mGlsConnection);
1110 mGlsConnection = null;
1111 }
1112
1113 //
1114 // stop MASF proxy service
1115 //
1116 //Intent proxyServiceIntent = new Intent();
1117 //proxyServiceIntent.setComponent
1118 // (new ComponentName(
1119 // "com.android.masfproxyservice",
1120 // "com.android.masfproxyservice.MasfProxyService"));
1121 //stopService(proxyServiceIntent);
1122 }
1123
1124 @Override
1125 public void onConfigurationChanged(Configuration newConfig) {
1126 super.onConfigurationChanged(newConfig);
1127
1128 if (mPageInfoDialog != null) {
1129 mPageInfoDialog.dismiss();
1130 showPageInfo(
1131 mPageInfoView,
1132 mPageInfoFromShowSSLCertificateOnError.booleanValue());
1133 }
1134 if (mSSLCertificateDialog != null) {
1135 mSSLCertificateDialog.dismiss();
1136 showSSLCertificate(
1137 mSSLCertificateView);
1138 }
1139 if (mSSLCertificateOnErrorDialog != null) {
1140 mSSLCertificateOnErrorDialog.dismiss();
1141 showSSLCertificateOnError(
1142 mSSLCertificateOnErrorView,
1143 mSSLCertificateOnErrorHandler,
1144 mSSLCertificateOnErrorError);
1145 }
1146 if (mHttpAuthenticationDialog != null) {
1147 String title = ((TextView) mHttpAuthenticationDialog
1148 .findViewById(com.android.internal.R.id.alertTitle)).getText()
1149 .toString();
1150 String name = ((TextView) mHttpAuthenticationDialog
1151 .findViewById(R.id.username_edit)).getText().toString();
1152 String password = ((TextView) mHttpAuthenticationDialog
1153 .findViewById(R.id.password_edit)).getText().toString();
1154 int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1155 .getId();
1156 mHttpAuthenticationDialog.dismiss();
1157 showHttpAuthentication(mHttpAuthHandler, null, null, title,
1158 name, password, focusId);
1159 }
1160 if (mFindDialog != null && mFindDialog.isShowing()) {
1161 mFindDialog.onConfigurationChanged(newConfig);
1162 }
1163 }
1164
1165 @Override public void onLowMemory() {
1166 super.onLowMemory();
1167 mTabControl.freeMemory();
1168 }
1169
1170 private boolean resumeWebView() {
1171 if ((!mActivityInPause && !mPageStarted) ||
1172 (mActivityInPause && mPageStarted)) {
1173 CookieSyncManager.getInstance().startSync();
1174 WebView w = mTabControl.getCurrentWebView();
1175 if (w != null) {
1176 w.resumeTimers();
1177 }
1178 return true;
1179 } else {
1180 return false;
1181 }
1182 }
1183
1184 private boolean pauseWebView() {
1185 if (mActivityInPause && !mPageStarted) {
1186 CookieSyncManager.getInstance().stopSync();
1187 WebView w = mTabControl.getCurrentWebView();
1188 if (w != null) {
1189 w.pauseTimers();
1190 }
1191 return true;
1192 } else {
1193 return false;
1194 }
1195 }
1196
1197 /*
1198 * This function is called when we are launching for the first time. We
1199 * are waiting for the login credentials before loading Google home
1200 * pages. This way the user will be logged in straight away.
1201 */
1202 private void waitForCredentials() {
1203 // Show a toast
1204 mCredsDlg = new ProgressDialog(this);
1205 mCredsDlg.setIndeterminate(true);
1206 mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1207 // If the user cancels the operation, then cancel the Google
1208 // Credentials request.
1209 mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1210 mCredsDlg.show();
1211
1212 // We set a timeout for the retrieval of credentials in onResume()
1213 // as that is when we have freed up some CPU time to get
1214 // the login credentials.
1215 }
1216
1217 /*
1218 * If we have received the credentials or we have timed out and we are
1219 * showing the credentials dialog, then it is time to move on.
1220 */
1221 private void resumeAfterCredentials() {
1222 if (mCredsDlg == null) {
1223 return;
1224 }
1225
1226 // Clear the toast
1227 if (mCredsDlg.isShowing()) {
1228 mCredsDlg.dismiss();
1229 }
1230 mCredsDlg = null;
1231
1232 // Clear any pending timeout
1233 mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1234
1235 // Load the page
1236 WebView w = mTabControl.getCurrentWebView();
1237 if (w != null) {
1238 w.loadUrl(mSettings.getHomePage());
1239 }
1240
1241 // Update the settings, need to do this last as it can take a moment
1242 // to persist the settings. In the mean time we could be loading
1243 // content.
1244 mSettings.setLoginInitialized(this);
1245 }
1246
1247 // Open the icon database and retain all the icons for visited sites.
1248 private void retainIconsOnStartup() {
1249 final WebIconDatabase db = WebIconDatabase.getInstance();
1250 db.open(getDir("icons", 0).getPath());
1251 try {
1252 Cursor c = Browser.getAllBookmarks(mResolver);
1253 if (!c.moveToFirst()) {
1254 c.deactivate();
1255 return;
1256 }
1257 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1258 do {
1259 String url = c.getString(urlIndex);
1260 db.retainIconForPageUrl(url);
1261 } while (c.moveToNext());
1262 c.deactivate();
1263 } catch (IllegalStateException e) {
1264 Log.e(LOGTAG, "retainIconsOnStartup", e);
1265 }
1266 }
1267
1268 // Helper method for getting the top window.
1269 WebView getTopWindow() {
1270 return mTabControl.getCurrentTopWebView();
1271 }
1272
1273 @Override
1274 public boolean onCreateOptionsMenu(Menu menu) {
1275 super.onCreateOptionsMenu(menu);
1276
1277 MenuInflater inflater = getMenuInflater();
1278 inflater.inflate(R.menu.browser, menu);
1279 mMenu = menu;
1280 updateInLoadMenuItems();
1281 return true;
1282 }
1283
1284 /**
1285 * As the menu can be open when loading state changes
1286 * we must manually update the state of the stop/reload menu
1287 * item
1288 */
1289 private void updateInLoadMenuItems() {
1290 if (mMenu == null) {
1291 return;
1292 }
1293 MenuItem src = mInLoad ?
1294 mMenu.findItem(R.id.stop_menu_id):
1295 mMenu.findItem(R.id.reload_menu_id);
1296 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1297 dest.setIcon(src.getIcon());
1298 dest.setTitle(src.getTitle());
1299 }
1300
1301 @Override
1302 public boolean onContextItemSelected(MenuItem item) {
1303 // chording is not an issue with context menus, but we use the same
1304 // options selector, so set mCanChord to true so we can access them.
1305 mCanChord = true;
1306 int id = item.getItemId();
1307 final WebView webView = getTopWindow();
1308 final HashMap hrefMap = new HashMap();
1309 hrefMap.put("webview", webView);
1310 final Message msg = mHandler.obtainMessage(
1311 FOCUS_NODE_HREF, id, 0, hrefMap);
1312 switch (id) {
1313 // -- Browser context menu
1314 case R.id.open_context_menu_id:
1315 case R.id.open_newtab_context_menu_id:
1316 case R.id.bookmark_context_menu_id:
1317 case R.id.save_link_context_menu_id:
1318 case R.id.share_link_context_menu_id:
1319 case R.id.copy_link_context_menu_id:
1320 webView.requestFocusNodeHref(msg);
1321 break;
1322
1323 default:
1324 // For other context menus
1325 return onOptionsItemSelected(item);
1326 }
1327 mCanChord = false;
1328 return true;
1329 }
1330
1331 private Bundle createGoogleSearchSourceBundle(String source) {
1332 Bundle bundle = new Bundle();
1333 bundle.putString(SearchManager.SOURCE, source);
1334 return bundle;
1335 }
1336
1337 /**
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001338 * Overriding this to insert a local information bundle
The Android Open Source Project0c908882009-03-03 19:32:16 -08001339 */
1340 @Override
1341 public boolean onSearchRequested() {
1342 startSearch(null, false,
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001343 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001344 return true;
1345 }
1346
1347 @Override
1348 public void startSearch(String initialQuery, boolean selectInitialQuery,
1349 Bundle appSearchData, boolean globalSearch) {
1350 if (appSearchData == null) {
1351 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1352 }
1353 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1354 }
1355
1356 @Override
1357 public boolean onOptionsItemSelected(MenuItem item) {
1358 if (!mCanChord) {
1359 // The user has already fired a shortcut with this hold down of the
1360 // menu key.
1361 return false;
1362 }
1363 switch (item.getItemId()) {
1364 // -- Main menu
1365 case R.id.goto_menu_id: {
1366 String url = getTopWindow().getUrl();
1367 startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
1368 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_GOTO), false);
1369 }
1370 break;
1371
1372 case R.id.bookmarks_menu_id:
1373 bookmarksOrHistoryPicker(false);
1374 break;
1375
1376 case R.id.windows_menu_id:
1377 if (mTabControl.getTabCount() == 1) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001378 openTabAndShow(mSettings.getHomePage(), null, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001379 } else {
1380 tabPicker(true, mTabControl.getCurrentIndex(), false);
1381 }
1382 break;
1383
1384 case R.id.stop_reload_menu_id:
1385 if (mInLoad) {
1386 stopLoading();
1387 } else {
1388 getTopWindow().reload();
1389 }
1390 break;
1391
1392 case R.id.back_menu_id:
1393 getTopWindow().goBack();
1394 break;
1395
1396 case R.id.forward_menu_id:
1397 getTopWindow().goForward();
1398 break;
1399
1400 case R.id.close_menu_id:
1401 // Close the subwindow if it exists.
1402 if (mTabControl.getCurrentSubWindow() != null) {
1403 dismissSubWindow(mTabControl.getCurrentTab());
1404 break;
1405 }
1406 final int currentIndex = mTabControl.getCurrentIndex();
1407 final TabControl.Tab parent =
1408 mTabControl.getCurrentTab().getParentTab();
1409 int indexToShow = -1;
1410 if (parent != null) {
1411 indexToShow = mTabControl.getTabIndex(parent);
1412 } else {
1413 // Get the last tab in the list. If it is the current tab,
1414 // subtract 1 more.
1415 indexToShow = mTabControl.getTabCount() - 1;
1416 if (currentIndex == indexToShow) {
1417 indexToShow--;
1418 }
1419 }
1420 switchTabs(currentIndex, indexToShow, true);
1421 break;
1422
1423 case R.id.homepage_menu_id:
1424 TabControl.Tab current = mTabControl.getCurrentTab();
1425 if (current != null) {
1426 dismissSubWindow(current);
1427 current.getWebView().loadUrl(mSettings.getHomePage());
1428 }
1429 break;
1430
1431 case R.id.preferences_menu_id:
1432 Intent intent = new Intent(this,
1433 BrowserPreferencesPage.class);
1434 startActivityForResult(intent, PREFERENCES_PAGE);
1435 break;
1436
1437 case R.id.find_menu_id:
1438 if (null == mFindDialog) {
1439 mFindDialog = new FindDialog(this);
1440 }
1441 mFindDialog.setWebView(getTopWindow());
1442 mFindDialog.show();
1443 mMenuState = EMPTY_MENU;
1444 break;
1445
1446 case R.id.select_text_id:
1447 getTopWindow().emulateShiftHeld();
1448 break;
1449 case R.id.page_info_menu_id:
1450 showPageInfo(mTabControl.getCurrentTab(), false);
1451 break;
1452
1453 case R.id.classic_history_menu_id:
1454 bookmarksOrHistoryPicker(true);
1455 break;
1456
1457 case R.id.share_page_menu_id:
1458 Browser.sendString(this, getTopWindow().getUrl());
1459 break;
1460
1461 case R.id.dump_nav_menu_id:
1462 getTopWindow().debugDump();
1463 break;
1464
1465 case R.id.zoom_in_menu_id:
1466 getTopWindow().zoomIn();
1467 break;
1468
1469 case R.id.zoom_out_menu_id:
1470 getTopWindow().zoomOut();
1471 break;
1472
1473 case R.id.view_downloads_menu_id:
1474 viewDownloads(null);
1475 break;
1476
1477 // -- Tab menu
1478 case R.id.view_tab_menu_id:
1479 if (mTabListener != null && mTabOverview != null) {
1480 int pos = mTabOverview.getContextMenuPosition(item);
1481 mTabOverview.setCurrentIndex(pos);
1482 mTabListener.onClick(pos);
1483 }
1484 break;
1485
1486 case R.id.remove_tab_menu_id:
1487 if (mTabListener != null && mTabOverview != null) {
1488 int pos = mTabOverview.getContextMenuPosition(item);
1489 mTabListener.remove(pos);
1490 }
1491 break;
1492
1493 case R.id.new_tab_menu_id:
1494 // No need to check for mTabOverview here since we are not
1495 // dependent on it for a position.
1496 if (mTabListener != null) {
1497 // If the overview happens to be non-null, make the "New
1498 // Tab" cell visible.
1499 if (mTabOverview != null) {
1500 mTabOverview.setCurrentIndex(ImageGrid.NEW_TAB);
1501 }
1502 mTabListener.onClick(ImageGrid.NEW_TAB);
1503 }
1504 break;
1505
1506 case R.id.bookmark_tab_menu_id:
1507 if (mTabListener != null && mTabOverview != null) {
1508 int pos = mTabOverview.getContextMenuPosition(item);
1509 TabControl.Tab t = mTabControl.getTab(pos);
1510 // Since we called populatePickerData for all of the
1511 // tabs, getTitle and getUrl will return appropriate
1512 // values.
1513 Browser.saveBookmark(BrowserActivity.this, t.getTitle(),
1514 t.getUrl());
1515 }
1516 break;
1517
1518 case R.id.history_tab_menu_id:
1519 bookmarksOrHistoryPicker(true);
1520 break;
1521
1522 case R.id.bookmarks_tab_menu_id:
1523 bookmarksOrHistoryPicker(false);
1524 break;
1525
1526 case R.id.properties_tab_menu_id:
1527 if (mTabListener != null && mTabOverview != null) {
1528 int pos = mTabOverview.getContextMenuPosition(item);
1529 showPageInfo(mTabControl.getTab(pos), false);
1530 }
1531 break;
1532
1533 case R.id.window_one_menu_id:
1534 case R.id.window_two_menu_id:
1535 case R.id.window_three_menu_id:
1536 case R.id.window_four_menu_id:
1537 case R.id.window_five_menu_id:
1538 case R.id.window_six_menu_id:
1539 case R.id.window_seven_menu_id:
1540 case R.id.window_eight_menu_id:
1541 {
1542 int menuid = item.getItemId();
1543 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1544 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1545 TabControl.Tab desiredTab = mTabControl.getTab(id);
1546 if (desiredTab != null &&
1547 desiredTab != mTabControl.getCurrentTab()) {
1548 switchTabs(mTabControl.getCurrentIndex(), id, false);
1549 }
1550 break;
1551 }
1552 }
1553 }
1554 break;
1555
1556 default:
1557 if (!super.onOptionsItemSelected(item)) {
1558 return false;
1559 }
1560 // Otherwise fall through.
1561 }
1562 mCanChord = false;
1563 return true;
1564 }
1565
1566 public void closeFind() {
1567 mMenuState = R.id.MAIN_MENU;
1568 }
1569
1570 @Override public boolean onPrepareOptionsMenu(Menu menu)
1571 {
1572 // This happens when the user begins to hold down the menu key, so
1573 // allow them to chord to get a shortcut.
1574 mCanChord = true;
1575 // Note: setVisible will decide whether an item is visible; while
1576 // setEnabled() will decide whether an item is enabled, which also means
1577 // whether the matching shortcut key will function.
1578 super.onPrepareOptionsMenu(menu);
1579 switch (mMenuState) {
1580 case R.id.TAB_MENU:
1581 if (mCurrentMenuState != mMenuState) {
1582 menu.setGroupVisible(R.id.MAIN_MENU, false);
1583 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1584 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1585 menu.setGroupVisible(R.id.TAB_MENU, true);
1586 menu.setGroupEnabled(R.id.TAB_MENU, true);
1587 }
1588 boolean newT = mTabControl.getTabCount() < TabControl.MAX_TABS;
1589 final MenuItem tab = menu.findItem(R.id.new_tab_menu_id);
1590 tab.setVisible(newT);
1591 tab.setEnabled(newT);
1592 break;
1593 case EMPTY_MENU:
1594 if (mCurrentMenuState != mMenuState) {
1595 menu.setGroupVisible(R.id.MAIN_MENU, false);
1596 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1597 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1598 menu.setGroupVisible(R.id.TAB_MENU, false);
1599 menu.setGroupEnabled(R.id.TAB_MENU, false);
1600 }
1601 break;
1602 default:
1603 if (mCurrentMenuState != mMenuState) {
1604 menu.setGroupVisible(R.id.MAIN_MENU, true);
1605 menu.setGroupEnabled(R.id.MAIN_MENU, true);
1606 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
1607 menu.setGroupVisible(R.id.TAB_MENU, false);
1608 menu.setGroupEnabled(R.id.TAB_MENU, false);
1609 }
1610 final WebView w = getTopWindow();
1611 boolean canGoBack = false;
1612 boolean canGoForward = false;
1613 boolean isHome = false;
1614 if (w != null) {
1615 canGoBack = w.canGoBack();
1616 canGoForward = w.canGoForward();
1617 isHome = mSettings.getHomePage().equals(w.getUrl());
1618 }
1619 final MenuItem back = menu.findItem(R.id.back_menu_id);
1620 back.setEnabled(canGoBack);
1621
1622 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1623 home.setEnabled(!isHome);
1624
1625 menu.findItem(R.id.forward_menu_id)
1626 .setEnabled(canGoForward);
1627
1628 // decide whether to show the share link option
1629 PackageManager pm = getPackageManager();
1630 Intent send = new Intent(Intent.ACTION_SEND);
1631 send.setType("text/plain");
1632 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1633 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1634
1635 // If there is only 1 window, the text will be "New window"
1636 final MenuItem windows = menu.findItem(R.id.windows_menu_id);
1637 windows.setTitleCondensed(mTabControl.getTabCount() > 1 ?
1638 getString(R.string.view_tabs_condensed) :
1639 getString(R.string.tab_picker_new_tab));
1640
1641 boolean isNavDump = mSettings.isNavDump();
1642 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1643 nav.setVisible(isNavDump);
1644 nav.setEnabled(isNavDump);
1645 break;
1646 }
1647 mCurrentMenuState = mMenuState;
1648 return true;
1649 }
1650
1651 @Override
1652 public void onCreateContextMenu(ContextMenu menu, View v,
1653 ContextMenuInfo menuInfo) {
1654 WebView webview = (WebView) v;
1655 WebView.HitTestResult result = webview.getHitTestResult();
1656 if (result == null) {
1657 return;
1658 }
1659
1660 int type = result.getType();
1661 if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1662 Log.w(LOGTAG,
1663 "We should not show context menu when nothing is touched");
1664 return;
1665 }
1666 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1667 // let TextView handles context menu
1668 return;
1669 }
1670
1671 // Note, http://b/issue?id=1106666 is requesting that
1672 // an inflated menu can be used again. This is not available
1673 // yet, so inflate each time (yuk!)
1674 MenuInflater inflater = getMenuInflater();
1675 inflater.inflate(R.menu.browsercontext, menu);
1676
1677 // Show the correct menu group
1678 String extra = result.getExtra();
1679 menu.setGroupVisible(R.id.PHONE_MENU,
1680 type == WebView.HitTestResult.PHONE_TYPE);
1681 menu.setGroupVisible(R.id.EMAIL_MENU,
1682 type == WebView.HitTestResult.EMAIL_TYPE);
1683 menu.setGroupVisible(R.id.GEO_MENU,
1684 type == WebView.HitTestResult.GEO_TYPE);
1685 menu.setGroupVisible(R.id.IMAGE_MENU,
1686 type == WebView.HitTestResult.IMAGE_TYPE
1687 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1688 menu.setGroupVisible(R.id.ANCHOR_MENU,
1689 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1690 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1691
1692 // Setup custom handling depending on the type
1693 switch (type) {
1694 case WebView.HitTestResult.PHONE_TYPE:
1695 menu.setHeaderTitle(Uri.decode(extra));
1696 menu.findItem(R.id.dial_context_menu_id).setIntent(
1697 new Intent(Intent.ACTION_VIEW, Uri
1698 .parse(WebView.SCHEME_TEL + extra)));
1699 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1700 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1701 addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1702 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1703 addIntent);
1704 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1705 new Copy(extra));
1706 break;
1707
1708 case WebView.HitTestResult.EMAIL_TYPE:
1709 menu.setHeaderTitle(extra);
1710 menu.findItem(R.id.email_context_menu_id).setIntent(
1711 new Intent(Intent.ACTION_VIEW, Uri
1712 .parse(WebView.SCHEME_MAILTO + extra)));
1713 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1714 new Copy(extra));
1715 break;
1716
1717 case WebView.HitTestResult.GEO_TYPE:
1718 menu.setHeaderTitle(extra);
1719 menu.findItem(R.id.map_context_menu_id).setIntent(
1720 new Intent(Intent.ACTION_VIEW, Uri
1721 .parse(WebView.SCHEME_GEO
1722 + URLEncoder.encode(extra))));
1723 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1724 new Copy(extra));
1725 break;
1726
1727 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1728 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1729 TextView titleView = (TextView) LayoutInflater.from(this)
1730 .inflate(android.R.layout.browser_link_context_header,
1731 null);
1732 titleView.setText(extra);
1733 menu.setHeaderView(titleView);
1734 // decide whether to show the open link in new tab option
1735 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1736 mTabControl.getTabCount() < TabControl.MAX_TABS);
1737 PackageManager pm = getPackageManager();
1738 Intent send = new Intent(Intent.ACTION_SEND);
1739 send.setType("text/plain");
1740 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1741 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1742 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1743 break;
1744 }
1745 // otherwise fall through to handle image part
1746 case WebView.HitTestResult.IMAGE_TYPE:
1747 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1748 menu.setHeaderTitle(extra);
1749 }
1750 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1751 new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1752 menu.findItem(R.id.download_context_menu_id).
1753 setOnMenuItemClickListener(new Download(extra));
1754 break;
1755
1756 default:
1757 Log.w(LOGTAG, "We should not get here.");
1758 break;
1759 }
1760 }
1761
The Android Open Source Project0c908882009-03-03 19:32:16 -08001762 // Attach the given tab to the content view.
1763 private void attachTabToContentView(TabControl.Tab t) {
1764 final WebView main = t.getWebView();
1765 // Attach the main WebView.
1766 mContentView.addView(main, COVER_SCREEN_PARAMS);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001767 // Attach the sub window if necessary
1768 attachSubWindow(t);
1769 // Request focus on the top window.
1770 t.getTopWindow().requestFocus();
1771 }
1772
1773 // Attach a sub window to the main WebView of the given tab.
1774 private void attachSubWindow(TabControl.Tab t) {
1775 // If a sub window exists, attach it to the content view.
1776 final WebView subView = t.getSubWebView();
1777 if (subView != null) {
1778 final View container = t.getSubWebViewContainer();
1779 mContentView.addView(container, COVER_SCREEN_PARAMS);
1780 subView.requestFocus();
1781 }
1782 }
1783
1784 // Remove the given tab from the content view.
1785 private void removeTabFromContentView(TabControl.Tab t) {
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07001786 // Remove the main WebView.
The Android Open Source Project0c908882009-03-03 19:32:16 -08001787 mContentView.removeView(t.getWebView());
1788 // Remove the sub window if it exists.
1789 if (t.getSubWebView() != null) {
1790 mContentView.removeView(t.getSubWebViewContainer());
1791 }
1792 }
1793
1794 // Remove the sub window if it exists. Also called by TabControl when the
1795 // user clicks the 'X' to dismiss a sub window.
1796 /* package */ void dismissSubWindow(TabControl.Tab t) {
1797 final WebView mainView = t.getWebView();
1798 if (t.getSubWebView() != null) {
1799 // Remove the container view and request focus on the main WebView.
1800 mContentView.removeView(t.getSubWebViewContainer());
1801 mainView.requestFocus();
1802 // Tell the TabControl to dismiss the subwindow. This will destroy
1803 // the WebView.
1804 mTabControl.dismissSubWindow(t);
1805 }
1806 }
1807
1808 // Send the ANIMTE_FROM_OVERVIEW message after changing the current tab.
1809 private void sendAnimateFromOverview(final TabControl.Tab tab,
1810 final boolean newTab, final String url, final int delay,
1811 final Message msg) {
1812 // Set the current tab.
1813 mTabControl.setCurrentTab(tab);
1814 // Attach the WebView so it will layout.
1815 attachTabToContentView(tab);
1816 // Set the view to invisibile for now.
1817 tab.getWebView().setVisibility(View.INVISIBLE);
1818 // If there is a sub window, make it invisible too.
1819 if (tab.getSubWebView() != null) {
1820 tab.getSubWebViewContainer().setVisibility(View.INVISIBLE);
1821 }
1822 // Create our fake animating view.
1823 final AnimatingView view = new AnimatingView(this, tab);
1824 // Attach it to the view system and make in invisible so it will
1825 // layout but not flash white on the screen.
1826 mContentView.addView(view, COVER_SCREEN_PARAMS);
1827 view.setVisibility(View.INVISIBLE);
1828 // Send the animate message.
1829 final HashMap map = new HashMap();
1830 map.put("view", view);
1831 // Load the url after the AnimatingView has captured the picture. This
1832 // prevents any bad layout or bad scale from being used during
1833 // animation.
1834 if (url != null) {
1835 dismissSubWindow(tab);
1836 tab.getWebView().loadUrl(url);
1837 }
1838 map.put("msg", msg);
1839 mHandler.sendMessageDelayed(mHandler.obtainMessage(
1840 ANIMATE_FROM_OVERVIEW, newTab ? 1 : 0, 0, map), delay);
1841 // Increment the count to indicate that we are in an animation.
1842 mAnimationCount++;
1843 // Remove the listener so we don't get any more tab changes.
1844 mTabOverview.setListener(null);
1845 mTabListener = null;
1846 // Make the menu empty until the animation completes.
1847 mMenuState = EMPTY_MENU;
1848
1849 }
1850
1851 // 500ms animation with 800ms delay
1852 private static final int TAB_ANIMATION_DURATION = 500;
1853 private static final int TAB_OVERVIEW_DELAY = 800;
1854
1855 // Called by TabControl when a tab is requesting focus
1856 /* package */ void showTab(TabControl.Tab t) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001857 showTab(t, null);
1858 }
1859
1860 private void showTab(TabControl.Tab t, String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001861 // Disallow focus change during a tab animation.
1862 if (mAnimationCount > 0) {
1863 return;
1864 }
1865 int delay = 0;
1866 if (mTabOverview == null) {
1867 // Add a delay so the tab overview can be shown before the second
1868 // animation begins.
1869 delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
1870 tabPicker(false, mTabControl.getTabIndex(t), false);
1871 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001872 sendAnimateFromOverview(t, false, url, delay, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001873 }
1874
1875 // This method does a ton of stuff. It will attempt to create a new tab
1876 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
1877 // url isn't null, it will load the given url. If the tab overview is not
1878 // showing, it will animate to the tab overview, create a new tab and
1879 // animate away from it. After the animation completes, it will dispatch
1880 // the given Message. If the tab overview is already showing (i.e. this
1881 // method is called from TabListener.onClick(), the method will animate
1882 // away from the tab overview.
Grace Klobac9181842009-04-14 08:53:22 -07001883 private TabControl.Tab openTabAndShow(String url, final Message msg,
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001884 boolean closeOnExit, String appId) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001885 final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1886 final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1887 if (newTab) {
1888 int delay = 0;
1889 // If the tab overview is up and there are animations, just load
1890 // the url.
1891 if (mTabOverview != null && mAnimationCount > 0) {
1892 if (url != null) {
1893 // We should not have a msg here since onCreateWindow
1894 // checks the animation count and every other caller passes
1895 // null.
1896 assert msg == null;
1897 // just dismiss the subwindow and load the given url.
1898 dismissSubWindow(currentTab);
1899 currentTab.getWebView().loadUrl(url);
1900 }
1901 } else {
1902 // show mTabOverview if it is not there.
1903 if (mTabOverview == null) {
1904 // We have to delay the animation from the tab picker by the
1905 // length of the tab animation. Add a delay so the tab
1906 // overview can be shown before the second animation begins.
1907 delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
1908 tabPicker(false, ImageGrid.NEW_TAB, false);
1909 }
1910 // Animate from the Tab overview after any animations have
1911 // finished.
Grace Klobac9181842009-04-14 08:53:22 -07001912 final TabControl.Tab tab = mTabControl.createNewTab(
1913 closeOnExit, appId, url);
1914 sendAnimateFromOverview(tab, true, url, delay, msg);
1915 return tab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001916 }
1917 } else if (url != null) {
1918 // We should not have a msg here.
1919 assert msg == null;
1920 if (mTabOverview != null && mAnimationCount == 0) {
1921 sendAnimateFromOverview(currentTab, false, url,
1922 TAB_OVERVIEW_DELAY, null);
1923 } else {
1924 // Get rid of the subwindow if it exists
1925 dismissSubWindow(currentTab);
1926 // Load the given url.
1927 currentTab.getWebView().loadUrl(url);
1928 }
1929 }
Grace Klobac9181842009-04-14 08:53:22 -07001930 return currentTab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001931 }
1932
1933 private Animation createTabAnimation(final AnimatingView view,
1934 final View cell, boolean scaleDown) {
1935 final AnimationSet set = new AnimationSet(true);
1936 final float scaleX = (float) cell.getWidth() / view.getWidth();
1937 final float scaleY = (float) cell.getHeight() / view.getHeight();
1938 if (scaleDown) {
1939 set.addAnimation(new ScaleAnimation(1.0f, scaleX, 1.0f, scaleY));
1940 set.addAnimation(new TranslateAnimation(0, cell.getLeft(), 0,
1941 cell.getTop()));
1942 } else {
1943 set.addAnimation(new ScaleAnimation(scaleX, 1.0f, scaleY, 1.0f));
1944 set.addAnimation(new TranslateAnimation(cell.getLeft(), 0,
1945 cell.getTop(), 0));
1946 }
1947 set.setDuration(TAB_ANIMATION_DURATION);
1948 set.setInterpolator(new DecelerateInterpolator());
1949 return set;
1950 }
1951
1952 // Animate to the tab overview. currentIndex tells us which position to
1953 // animate to and newIndex is the position that should be selected after
1954 // the animation completes.
1955 // If remove is true, after the animation stops, a confirmation dialog will
1956 // be displayed to the user.
1957 private void animateToTabOverview(final int newIndex, final boolean remove,
1958 final AnimatingView view) {
1959 // Find the view in the ImageGrid allowing for the "New Tab" cell.
1960 int position = mTabControl.getTabIndex(view.mTab);
1961 if (!((ImageAdapter) mTabOverview.getAdapter()).maxedOut()) {
1962 position++;
1963 }
1964
1965 // Offset the tab position with the first visible position to get a
1966 // number between 0 and 3.
1967 position -= mTabOverview.getFirstVisiblePosition();
1968
1969 // Grab the view that we are going to animate to.
1970 final View v = mTabOverview.getChildAt(position);
1971
1972 final Animation.AnimationListener l =
1973 new Animation.AnimationListener() {
1974 public void onAnimationStart(Animation a) {
1975 mTabOverview.requestFocus();
1976 // Clear the listener so we don't trigger a tab
1977 // selection.
1978 mTabOverview.setListener(null);
1979 }
1980 public void onAnimationRepeat(Animation a) {}
1981 public void onAnimationEnd(Animation a) {
1982 // We are no longer animating so decrement the count.
1983 mAnimationCount--;
1984 // Make the view GONE so that it will not draw between
1985 // now and when the Runnable is handled.
1986 view.setVisibility(View.GONE);
1987 // Post a runnable since we can't modify the view
1988 // hierarchy during this callback.
1989 mHandler.post(new Runnable() {
1990 public void run() {
1991 // Remove the AnimatingView.
1992 mContentView.removeView(view);
1993 if (mTabOverview != null) {
1994 // Make newIndex visible.
1995 mTabOverview.setCurrentIndex(newIndex);
1996 // Restore the listener.
1997 mTabOverview.setListener(mTabListener);
1998 // Change the menu to TAB_MENU if the
1999 // ImageGrid is interactive.
2000 if (mTabOverview.isLive()) {
2001 mMenuState = R.id.TAB_MENU;
2002 mTabOverview.requestFocus();
2003 }
2004 }
2005 // If a remove was requested, remove the tab.
2006 if (remove) {
2007 // During a remove, the current tab has
2008 // already changed. Remember the current one
2009 // here.
2010 final TabControl.Tab currentTab =
2011 mTabControl.getCurrentTab();
2012 // Remove the tab at newIndex from
2013 // TabControl and the tab overview.
2014 final TabControl.Tab tab =
2015 mTabControl.getTab(newIndex);
2016 mTabControl.removeTab(tab);
2017 // Restore the current tab.
2018 if (currentTab != tab) {
2019 mTabControl.setCurrentTab(currentTab);
2020 }
2021 if (mTabOverview != null) {
2022 mTabOverview.remove(newIndex);
2023 // Make the current tab visible.
2024 mTabOverview.setCurrentIndex(
2025 mTabControl.getCurrentIndex());
2026 }
2027 }
2028 }
2029 });
2030 }
2031 };
2032
2033 // Do an animation if there is a view to animate to.
2034 if (v != null) {
2035 // Create our animation
2036 final Animation anim = createTabAnimation(view, v, true);
2037 anim.setAnimationListener(l);
2038 // Start animating
2039 view.startAnimation(anim);
2040 } else {
2041 // If something goes wrong and we didn't find a view to animate to,
2042 // just do everything here.
2043 l.onAnimationStart(null);
2044 l.onAnimationEnd(null);
2045 }
2046 }
2047
2048 // Animate from the tab picker. The index supplied is the index to animate
2049 // from.
2050 private void animateFromTabOverview(final AnimatingView view,
2051 final boolean newTab, final Message msg) {
2052 // firstVisible is the first visible tab on the screen. This helps
2053 // to know which corner of the screen the selected tab is.
2054 int firstVisible = mTabOverview.getFirstVisiblePosition();
2055 // tabPosition is the 0-based index of of the tab being opened
2056 int tabPosition = mTabControl.getTabIndex(view.mTab);
2057 if (!((ImageAdapter) mTabOverview.getAdapter()).maxedOut()) {
2058 // Add one to make room for the "New Tab" cell.
2059 tabPosition++;
2060 }
2061 // If this is a new tab, animate from the "New Tab" cell.
2062 if (newTab) {
2063 tabPosition = 0;
2064 }
2065 // Location corresponds to the four corners of the screen.
2066 // A new tab or 0 is upper left, 0 for an old tab is upper
2067 // right, 1 is lower left, and 2 is lower right
2068 int location = tabPosition - firstVisible;
2069
2070 // Find the view at this location.
2071 final View v = mTabOverview.getChildAt(location);
2072
2073 // Wait until the animation completes to replace the AnimatingView.
2074 final Animation.AnimationListener l =
2075 new Animation.AnimationListener() {
2076 public void onAnimationStart(Animation a) {}
2077 public void onAnimationRepeat(Animation a) {}
2078 public void onAnimationEnd(Animation a) {
2079 mHandler.post(new Runnable() {
2080 public void run() {
2081 mContentView.removeView(view);
2082 // Dismiss the tab overview. If the cell at the
2083 // given location is null, set the fade
2084 // parameter to true.
2085 dismissTabOverview(v == null);
2086 TabControl.Tab t =
2087 mTabControl.getCurrentTab();
2088 mMenuState = R.id.MAIN_MENU;
2089 // Resume regular updates.
2090 t.getWebView().resumeTimers();
2091 // Dispatch the message after the animation
2092 // completes.
2093 if (msg != null) {
2094 msg.sendToTarget();
2095 }
2096 // The animation is done and the tab overview is
2097 // gone so allow key events and other animations
2098 // to begin.
2099 mAnimationCount--;
2100 // Reset all the title bar info.
2101 resetTitle();
2102 }
2103 });
2104 }
2105 };
2106
2107 if (v != null) {
2108 final Animation anim = createTabAnimation(view, v, false);
2109 // Set the listener and start animating
2110 anim.setAnimationListener(l);
2111 view.startAnimation(anim);
2112 // Make the view VISIBLE during the animation.
2113 view.setVisibility(View.VISIBLE);
2114 } else {
2115 // Go ahead and do all the cleanup.
2116 l.onAnimationEnd(null);
2117 }
2118 }
2119
2120 // Dismiss the tab overview applying a fade if needed.
2121 private void dismissTabOverview(final boolean fade) {
2122 if (fade) {
2123 AlphaAnimation anim = new AlphaAnimation(1.0f, 0.0f);
2124 anim.setDuration(500);
2125 anim.startNow();
2126 mTabOverview.startAnimation(anim);
2127 }
2128 // Just in case there was a problem with animating away from the tab
2129 // overview
2130 WebView current = mTabControl.getCurrentWebView();
2131 if (current != null) {
2132 current.setVisibility(View.VISIBLE);
2133 } else {
2134 Log.e(LOGTAG, "No current WebView in dismissTabOverview");
2135 }
2136 // Make the sub window container visible.
2137 if (mTabControl.getCurrentSubWindow() != null) {
2138 mTabControl.getCurrentTab().getSubWebViewContainer()
2139 .setVisibility(View.VISIBLE);
2140 }
2141 mContentView.removeView(mTabOverview);
Patrick Scott2ed6edb2009-04-22 10:07:45 -04002142 // Clear all the data for tab picker so next time it will be
2143 // recreated.
2144 mTabControl.wipeAllPickerData();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002145 mTabOverview.clear();
2146 mTabOverview = null;
2147 mTabListener = null;
2148 }
2149
Grace Klobac9181842009-04-14 08:53:22 -07002150 private TabControl.Tab openTab(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002151 if (mSettings.openInBackground()) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07002152 TabControl.Tab t = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002153 if (t != null) {
2154 t.getWebView().loadUrl(url);
2155 }
Grace Klobac9181842009-04-14 08:53:22 -07002156 return t;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002157 } else {
Grace Klobac9181842009-04-14 08:53:22 -07002158 return openTabAndShow(url, null, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002159 }
2160 }
2161
2162 private class Copy implements OnMenuItemClickListener {
2163 private CharSequence mText;
2164
2165 public boolean onMenuItemClick(MenuItem item) {
2166 copy(mText);
2167 return true;
2168 }
2169
2170 public Copy(CharSequence toCopy) {
2171 mText = toCopy;
2172 }
2173 }
2174
2175 private class Download implements OnMenuItemClickListener {
2176 private String mText;
2177
2178 public boolean onMenuItemClick(MenuItem item) {
2179 onDownloadStartNoStream(mText, null, null, null, -1);
2180 return true;
2181 }
2182
2183 public Download(String toDownload) {
2184 mText = toDownload;
2185 }
2186 }
2187
2188 private void copy(CharSequence text) {
2189 try {
2190 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
2191 if (clip != null) {
2192 clip.setClipboardText(text);
2193 }
2194 } catch (android.os.RemoteException e) {
2195 Log.e(LOGTAG, "Copy failed", e);
2196 }
2197 }
2198
2199 /**
2200 * Resets the browser title-view to whatever it must be (for example, if we
2201 * load a page from history).
2202 */
2203 private void resetTitle() {
2204 resetLockIcon();
2205 resetTitleIconAndProgress();
2206 }
2207
2208 /**
2209 * Resets the browser title-view to whatever it must be
2210 * (for example, if we had a loading error)
2211 * When we have a new page, we call resetTitle, when we
2212 * have to reset the titlebar to whatever it used to be
2213 * (for example, if the user chose to stop loading), we
2214 * call resetTitleAndRevertLockIcon.
2215 */
2216 /* package */ void resetTitleAndRevertLockIcon() {
2217 revertLockIcon();
2218 resetTitleIconAndProgress();
2219 }
2220
2221 /**
2222 * Reset the title, favicon, and progress.
2223 */
2224 private void resetTitleIconAndProgress() {
2225 WebView current = mTabControl.getCurrentWebView();
2226 if (current == null) {
2227 return;
2228 }
2229 resetTitleAndIcon(current);
2230 int progress = current.getProgress();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002231 mWebChromeClient.onProgressChanged(current, progress);
2232 }
2233
2234 // Reset the title and the icon based on the given item.
2235 private void resetTitleAndIcon(WebView view) {
2236 WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
2237 if (item != null) {
2238 setUrlTitle(item.getUrl(), item.getTitle());
2239 setFavicon(item.getFavicon());
2240 } else {
2241 setUrlTitle(null, null);
2242 setFavicon(null);
2243 }
2244 }
2245
2246 /**
2247 * Sets a title composed of the URL and the title string.
2248 * @param url The URL of the site being loaded.
2249 * @param title The title of the site being loaded.
2250 */
2251 private void setUrlTitle(String url, String title) {
2252 mUrl = url;
2253 mTitle = title;
2254
2255 // While the tab overview is animating or being shown, block changes
2256 // to the title.
2257 if (mAnimationCount == 0 && mTabOverview == null) {
2258 setTitle(buildUrlTitle(url, title));
2259 }
2260 }
2261
2262 /**
2263 * Builds and returns the page title, which is some
2264 * combination of the page URL and title.
2265 * @param url The URL of the site being loaded.
2266 * @param title The title of the site being loaded.
2267 * @return The page title.
2268 */
2269 private String buildUrlTitle(String url, String title) {
2270 String urlTitle = "";
2271
2272 if (url != null) {
2273 String titleUrl = buildTitleUrl(url);
2274
2275 if (title != null && 0 < title.length()) {
2276 if (titleUrl != null && 0 < titleUrl.length()) {
2277 urlTitle = titleUrl + ": " + title;
2278 } else {
2279 urlTitle = title;
2280 }
2281 } else {
2282 if (titleUrl != null) {
2283 urlTitle = titleUrl;
2284 }
2285 }
2286 }
2287
2288 return urlTitle;
2289 }
2290
2291 /**
2292 * @param url The URL to build a title version of the URL from.
2293 * @return The title version of the URL or null if fails.
2294 * The title version of the URL can be either the URL hostname,
2295 * or the hostname with an "https://" prefix (for secure URLs),
2296 * or an empty string if, for example, the URL in question is a
2297 * file:// URL with no hostname.
2298 */
2299 private static String buildTitleUrl(String url) {
2300 String titleUrl = null;
2301
2302 if (url != null) {
2303 try {
2304 // parse the url string
2305 URL urlObj = new URL(url);
2306 if (urlObj != null) {
2307 titleUrl = "";
2308
2309 String protocol = urlObj.getProtocol();
2310 String host = urlObj.getHost();
2311
2312 if (host != null && 0 < host.length()) {
2313 titleUrl = host;
2314 if (protocol != null) {
2315 // if a secure site, add an "https://" prefix!
2316 if (protocol.equalsIgnoreCase("https")) {
2317 titleUrl = protocol + "://" + host;
2318 }
2319 }
2320 }
2321 }
2322 } catch (MalformedURLException e) {}
2323 }
2324
2325 return titleUrl;
2326 }
2327
2328 // Set the favicon in the title bar.
2329 private void setFavicon(Bitmap icon) {
2330 // While the tab overview is animating or being shown, block changes to
2331 // the favicon.
2332 if (mAnimationCount > 0 || mTabOverview != null) {
2333 return;
2334 }
2335 Drawable[] array = new Drawable[2];
2336 PaintDrawable p = new PaintDrawable(Color.WHITE);
2337 p.setCornerRadius(3f);
2338 array[0] = p;
2339 if (icon == null) {
2340 array[1] = mGenericFavicon;
2341 } else {
2342 array[1] = new BitmapDrawable(icon);
2343 }
2344 LayerDrawable d = new LayerDrawable(array);
2345 d.setLayerInset(1, 2, 2, 2, 2);
2346 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
2347 }
2348
2349 /**
2350 * Saves the current lock-icon state before resetting
2351 * the lock icon. If we have an error, we may need to
2352 * roll back to the previous state.
2353 */
2354 private void saveLockIcon() {
2355 mPrevLockType = mLockIconType;
2356 }
2357
2358 /**
2359 * Reverts the lock-icon state to the last saved state,
2360 * for example, if we had an error, and need to cancel
2361 * the load.
2362 */
2363 private void revertLockIcon() {
2364 mLockIconType = mPrevLockType;
2365
Dave Bort31a6d1c2009-04-13 15:56:49 -07002366 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002367 Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
2368 " revert lock icon to " + mLockIconType);
2369 }
2370
2371 updateLockIconImage(mLockIconType);
2372 }
2373
2374 private void switchTabs(int indexFrom, int indexToShow, boolean remove) {
2375 int delay = TAB_ANIMATION_DURATION + TAB_OVERVIEW_DELAY;
2376 // Animate to the tab picker, remove the current tab, then
2377 // animate away from the tab picker to the parent WebView.
2378 tabPicker(false, indexFrom, remove);
2379 // Change to the parent tab
2380 final TabControl.Tab tab = mTabControl.getTab(indexToShow);
2381 if (tab != null) {
2382 sendAnimateFromOverview(tab, false, null, delay, null);
2383 } else {
2384 // Increment this here so that no other animations can happen in
2385 // between the end of the tab picker transition and the beginning
2386 // of openTabAndShow. This has a matching decrement in the handler
2387 // of OPEN_TAB_AND_SHOW.
2388 mAnimationCount++;
2389 // Send a message to open a new tab.
2390 mHandler.sendMessageDelayed(
2391 mHandler.obtainMessage(OPEN_TAB_AND_SHOW,
2392 mSettings.getHomePage()), delay);
2393 }
2394 }
2395
2396 private void goBackOnePageOrQuit() {
2397 TabControl.Tab current = mTabControl.getCurrentTab();
2398 if (current == null) {
2399 /*
2400 * Instead of finishing the activity, simply push this to the back
2401 * of the stack and let ActivityManager to choose the foreground
2402 * activity. As BrowserActivity is singleTask, it will be always the
2403 * root of the task. So we can use either true or false for
2404 * moveTaskToBack().
2405 */
2406 moveTaskToBack(true);
2407 }
2408 WebView w = current.getWebView();
2409 if (w.canGoBack()) {
2410 w.goBack();
2411 } else {
2412 // Check to see if we are closing a window that was created by
2413 // another window. If so, we switch back to that window.
2414 TabControl.Tab parent = current.getParentTab();
2415 if (parent != null) {
2416 switchTabs(mTabControl.getCurrentIndex(),
2417 mTabControl.getTabIndex(parent), true);
2418 } else {
2419 if (current.closeOnExit()) {
2420 if (mTabControl.getTabCount() == 1) {
2421 finish();
2422 return;
2423 }
2424 // call pauseWebView() now, we won't be able to call it in
2425 // onPause() as the WebView won't be valid.
2426 pauseWebView();
2427 removeTabFromContentView(current);
2428 mTabControl.removeTab(current);
2429 }
2430 /*
2431 * Instead of finishing the activity, simply push this to the back
2432 * of the stack and let ActivityManager to choose the foreground
2433 * activity. As BrowserActivity is singleTask, it will be always the
2434 * root of the task. So we can use either true or false for
2435 * moveTaskToBack().
2436 */
2437 moveTaskToBack(true);
2438 }
2439 }
2440 }
2441
2442 public KeyTracker.State onKeyTracker(int keyCode,
2443 KeyEvent event,
2444 KeyTracker.Stage stage,
2445 int duration) {
2446 // if onKeyTracker() is called after activity onStop()
2447 // because of accumulated key events,
2448 // we should ignore it as browser is not active any more.
2449 WebView topWindow = getTopWindow();
2450 if (topWindow == null)
2451 return KeyTracker.State.NOT_TRACKING;
2452
2453 if (keyCode == KeyEvent.KEYCODE_BACK) {
2454 // During animations, block the back key so that other animations
2455 // are not triggered and so that we don't end up destroying all the
2456 // WebViews before finishing the animation.
2457 if (mAnimationCount > 0) {
2458 return KeyTracker.State.DONE_TRACKING;
2459 }
2460 if (stage == KeyTracker.Stage.LONG_REPEAT) {
2461 bookmarksOrHistoryPicker(true);
2462 return KeyTracker.State.DONE_TRACKING;
2463 } else if (stage == KeyTracker.Stage.UP) {
2464 // FIXME: Currently, we do not have a notion of the
2465 // history picker for the subwindow, but maybe we
2466 // should?
2467 WebView subwindow = mTabControl.getCurrentSubWindow();
2468 if (subwindow != null) {
2469 if (subwindow.canGoBack()) {
2470 subwindow.goBack();
2471 } else {
2472 dismissSubWindow(mTabControl.getCurrentTab());
2473 }
2474 } else {
2475 goBackOnePageOrQuit();
2476 }
2477 return KeyTracker.State.DONE_TRACKING;
2478 }
2479 return KeyTracker.State.KEEP_TRACKING;
2480 }
2481 return KeyTracker.State.NOT_TRACKING;
2482 }
2483
2484 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2485 if (keyCode == KeyEvent.KEYCODE_MENU) {
2486 mMenuIsDown = true;
2487 }
2488 boolean handled = mKeyTracker.doKeyDown(keyCode, event);
2489 if (!handled) {
2490 switch (keyCode) {
2491 case KeyEvent.KEYCODE_SPACE:
2492 if (event.isShiftPressed()) {
2493 getTopWindow().pageUp(false);
2494 } else {
2495 getTopWindow().pageDown(false);
2496 }
2497 handled = true;
2498 break;
2499
2500 default:
2501 break;
2502 }
2503 }
2504 return handled || super.onKeyDown(keyCode, event);
2505 }
2506
2507 @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2508 if (keyCode == KeyEvent.KEYCODE_MENU) {
2509 mMenuIsDown = false;
2510 }
2511 return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2512 }
2513
2514 private void stopLoading() {
2515 resetTitleAndRevertLockIcon();
2516 WebView w = getTopWindow();
2517 w.stopLoading();
2518 mWebViewClient.onPageFinished(w, w.getUrl());
2519
2520 cancelStopToast();
2521 mStopToast = Toast
2522 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2523 mStopToast.show();
2524 }
2525
2526 private void cancelStopToast() {
2527 if (mStopToast != null) {
2528 mStopToast.cancel();
2529 mStopToast = null;
2530 }
2531 }
2532
2533 // called by a non-UI thread to post the message
2534 public void postMessage(int what, int arg1, int arg2, Object obj) {
2535 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2536 }
2537
2538 // public message ids
2539 public final static int LOAD_URL = 1001;
2540 public final static int STOP_LOAD = 1002;
2541
2542 // Message Ids
2543 private static final int FOCUS_NODE_HREF = 102;
2544 private static final int CANCEL_CREDS_REQUEST = 103;
2545 private static final int ANIMATE_FROM_OVERVIEW = 104;
2546 private static final int ANIMATE_TO_OVERVIEW = 105;
2547 private static final int OPEN_TAB_AND_SHOW = 106;
2548 private static final int CHECK_MEMORY = 107;
2549 private static final int RELEASE_WAKELOCK = 108;
2550
2551 // Private handler for handling javascript and saving passwords
2552 private Handler mHandler = new Handler() {
2553
2554 public void handleMessage(Message msg) {
2555 switch (msg.what) {
2556 case ANIMATE_FROM_OVERVIEW:
2557 final HashMap map = (HashMap) msg.obj;
2558 animateFromTabOverview((AnimatingView) map.get("view"),
2559 msg.arg1 == 1, (Message) map.get("msg"));
2560 break;
2561
2562 case ANIMATE_TO_OVERVIEW:
2563 animateToTabOverview(msg.arg1, msg.arg2 == 1,
2564 (AnimatingView) msg.obj);
2565 break;
2566
2567 case OPEN_TAB_AND_SHOW:
2568 // Decrement mAnimationCount before openTabAndShow because
2569 // the method relies on the value being 0 to start the next
2570 // animation.
2571 mAnimationCount--;
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07002572 openTabAndShow((String) msg.obj, null, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002573 break;
2574
2575 case FOCUS_NODE_HREF:
2576 String url = (String) msg.getData().get("url");
2577 if (url == null || url.length() == 0) {
2578 break;
2579 }
2580 HashMap focusNodeMap = (HashMap) msg.obj;
2581 WebView view = (WebView) focusNodeMap.get("webview");
2582 // Only apply the action if the top window did not change.
2583 if (getTopWindow() != view) {
2584 break;
2585 }
2586 switch (msg.arg1) {
2587 case R.id.open_context_menu_id:
2588 case R.id.view_image_context_menu_id:
2589 loadURL(getTopWindow(), url);
2590 break;
2591 case R.id.open_newtab_context_menu_id:
Grace Klobac9181842009-04-14 08:53:22 -07002592 final TabControl.Tab parent = mTabControl
2593 .getCurrentTab();
2594 final TabControl.Tab newTab = openTab(url);
2595 if (newTab != parent) {
2596 parent.addChildTab(newTab);
2597 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002598 break;
2599 case R.id.bookmark_context_menu_id:
2600 Intent intent = new Intent(BrowserActivity.this,
2601 AddBookmarkPage.class);
2602 intent.putExtra("url", url);
2603 startActivity(intent);
2604 break;
2605 case R.id.share_link_context_menu_id:
2606 Browser.sendString(BrowserActivity.this, url);
2607 break;
2608 case R.id.copy_link_context_menu_id:
2609 copy(url);
2610 break;
2611 case R.id.save_link_context_menu_id:
2612 case R.id.download_context_menu_id:
2613 onDownloadStartNoStream(url, null, null, null, -1);
2614 break;
2615 }
2616 break;
2617
2618 case LOAD_URL:
2619 loadURL(getTopWindow(), (String) msg.obj);
2620 break;
2621
2622 case STOP_LOAD:
2623 stopLoading();
2624 break;
2625
2626 case CANCEL_CREDS_REQUEST:
2627 resumeAfterCredentials();
2628 break;
2629
2630 case CHECK_MEMORY:
2631 // reschedule to check memory condition
2632 mHandler.removeMessages(CHECK_MEMORY);
2633 mHandler.sendMessageDelayed(mHandler.obtainMessage
2634 (CHECK_MEMORY), CHECK_MEMORY_INTERVAL);
2635 checkMemory();
2636 break;
2637
2638 case RELEASE_WAKELOCK:
2639 if (mWakeLock.isHeld()) {
2640 mWakeLock.release();
2641 }
2642 break;
2643 }
2644 }
2645 };
2646
2647 // -------------------------------------------------------------------------
2648 // WebViewClient implementation.
2649 //-------------------------------------------------------------------------
2650
2651 // Use in overrideUrlLoading
2652 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2653 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2654 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2655 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2656
2657 /* package */ WebViewClient getWebViewClient() {
2658 return mWebViewClient;
2659 }
2660
2661 private void updateIcon(String url, Bitmap icon) {
2662 if (icon != null) {
2663 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
2664 url, icon);
2665 }
2666 setFavicon(icon);
2667 }
2668
2669 private final WebViewClient mWebViewClient = new WebViewClient() {
2670 @Override
2671 public void onPageStarted(WebView view, String url, Bitmap favicon) {
2672 resetLockIcon(url);
2673 setUrlTitle(url, null);
2674 // Call updateIcon instead of setFavicon so the bookmark
2675 // database can be updated.
2676 updateIcon(url, favicon);
2677
2678 if (mSettings.isTracing() == true) {
2679 // FIXME: we should save the trace file somewhere other than data.
2680 // I can't use "/tmp" as it competes for system memory.
2681 File file = getDir("browserTrace", 0);
2682 String baseDir = file.getPath();
2683 if (!baseDir.endsWith(File.separator)) baseDir += File.separator;
2684 String host;
2685 try {
2686 WebAddress uri = new WebAddress(url);
2687 host = uri.mHost;
2688 } catch (android.net.ParseException ex) {
2689 host = "unknown_host";
2690 }
2691 host = host.replace('.', '_');
2692 baseDir = baseDir + host;
2693 file = new File(baseDir+".data");
2694 if (file.exists() == true) {
2695 file.delete();
2696 }
2697 file = new File(baseDir+".key");
2698 if (file.exists() == true) {
2699 file.delete();
2700 }
2701 mInTrace = true;
2702 Debug.startMethodTracing(baseDir, 8 * 1024 * 1024);
2703 }
2704
2705 // Performance probe
2706 if (false) {
2707 mStart = SystemClock.uptimeMillis();
2708 mProcessStart = Process.getElapsedCpuTime();
2709 long[] sysCpu = new long[7];
2710 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2711 sysCpu, null)) {
2712 mUserStart = sysCpu[0] + sysCpu[1];
2713 mSystemStart = sysCpu[2];
2714 mIdleStart = sysCpu[3];
2715 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2716 }
2717 mUiStart = SystemClock.currentThreadTimeMillis();
2718 }
2719
2720 if (!mPageStarted) {
2721 mPageStarted = true;
2722 // if onResume() has been called, resumeWebView() does nothing.
2723 resumeWebView();
2724 }
2725
2726 // reset sync timer to avoid sync starts during loading a page
2727 CookieSyncManager.getInstance().resetSync();
2728
2729 mInLoad = true;
2730 updateInLoadMenuItems();
2731 if (!mIsNetworkUp) {
2732 if ( mAlertDialog == null) {
2733 mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
2734 .setTitle(R.string.loadSuspendedTitle)
2735 .setMessage(R.string.loadSuspended)
2736 .setPositiveButton(R.string.ok, null)
2737 .show();
2738 }
2739 if (view != null) {
2740 view.setNetworkAvailable(false);
2741 }
2742 }
2743
2744 // schedule to check memory condition
2745 mHandler.sendMessageDelayed(mHandler.obtainMessage(CHECK_MEMORY),
2746 CHECK_MEMORY_INTERVAL);
2747 }
2748
2749 @Override
2750 public void onPageFinished(WebView view, String url) {
2751 // Reset the title and icon in case we stopped a provisional
2752 // load.
2753 resetTitleAndIcon(view);
2754
2755 // Update the lock icon image only once we are done loading
2756 updateLockIconImage(mLockIconType);
2757
2758 // Performance probe
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002759 if (false) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002760 long[] sysCpu = new long[7];
2761 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2762 sysCpu, null)) {
2763 String uiInfo = "UI thread used "
2764 + (SystemClock.currentThreadTimeMillis() - mUiStart)
2765 + " ms";
Dave Bort31a6d1c2009-04-13 15:56:49 -07002766 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002767 Log.d(LOGTAG, uiInfo);
2768 }
2769 //The string that gets written to the log
2770 String performanceString = "It took total "
2771 + (SystemClock.uptimeMillis() - mStart)
2772 + " ms clock time to load the page."
2773 + "\nbrowser process used "
2774 + (Process.getElapsedCpuTime() - mProcessStart)
2775 + " ms, user processes used "
2776 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2777 + " ms, kernel used "
2778 + (sysCpu[2] - mSystemStart) * 10
2779 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2780 + " ms and irq took "
2781 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2782 * 10 + " ms, " + uiInfo;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002783 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002784 Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2785 }
2786 if (url != null) {
2787 // strip the url to maintain consistency
2788 String newUrl = new String(url);
2789 if (newUrl.startsWith("http://www.")) {
2790 newUrl = newUrl.substring(11);
2791 } else if (newUrl.startsWith("http://")) {
2792 newUrl = newUrl.substring(7);
2793 } else if (newUrl.startsWith("https://www.")) {
2794 newUrl = newUrl.substring(12);
2795 } else if (newUrl.startsWith("https://")) {
2796 newUrl = newUrl.substring(8);
2797 }
Dave Bort31a6d1c2009-04-13 15:56:49 -07002798 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002799 Log.d(LOGTAG, newUrl + " loaded");
2800 }
2801 /*
2802 if (sWhiteList.contains(newUrl)) {
2803 // The string that gets pushed to the statistcs
2804 // service
2805 performanceString = performanceString
2806 + "\nWebpage: "
2807 + newUrl
2808 + "\nCarrier: "
2809 + android.os.SystemProperties
2810 .get("gsm.sim.operator.alpha");
2811 if (mWebView != null
2812 && mWebView.getContext() != null
2813 && mWebView.getContext().getSystemService(
2814 Context.CONNECTIVITY_SERVICE) != null) {
2815 ConnectivityManager cManager =
2816 (ConnectivityManager) mWebView
2817 .getContext().getSystemService(
2818 Context.CONNECTIVITY_SERVICE);
2819 NetworkInfo nInfo = cManager
2820 .getActiveNetworkInfo();
2821 if (nInfo != null) {
2822 performanceString = performanceString
2823 + "\nNetwork Type: "
2824 + nInfo.getType().toString();
2825 }
2826 }
2827 Checkin.logEvent(mResolver,
2828 Checkin.Events.Tag.WEBPAGE_LOAD,
2829 performanceString);
2830 Log.w(LOGTAG, "pushed to the statistics service");
2831 }
2832 */
2833 }
2834 }
2835 }
2836
2837 if (mInTrace) {
2838 mInTrace = false;
2839 Debug.stopMethodTracing();
2840 }
2841
2842 if (mPageStarted) {
2843 mPageStarted = false;
2844 // pauseWebView() will do nothing and return false if onPause()
2845 // is not called yet.
2846 if (pauseWebView()) {
2847 if (mWakeLock.isHeld()) {
2848 mHandler.removeMessages(RELEASE_WAKELOCK);
2849 mWakeLock.release();
2850 }
2851 }
2852 }
2853
The Android Open Source Project0c908882009-03-03 19:32:16 -08002854 mHandler.removeMessages(CHECK_MEMORY);
2855 checkMemory();
2856 }
2857
2858 // return true if want to hijack the url to let another app to handle it
2859 @Override
2860 public boolean shouldOverrideUrlLoading(WebView view, String url) {
2861 if (url.startsWith(SCHEME_WTAI)) {
2862 // wtai://wp/mc;number
2863 // number=string(phone-number)
2864 if (url.startsWith(SCHEME_WTAI_MC)) {
2865 Intent intent = new Intent(Intent.ACTION_VIEW,
2866 Uri.parse(WebView.SCHEME_TEL +
2867 url.substring(SCHEME_WTAI_MC.length())));
2868 startActivity(intent);
2869 return true;
2870 }
2871 // wtai://wp/sd;dtmf
2872 // dtmf=string(dialstring)
2873 if (url.startsWith(SCHEME_WTAI_SD)) {
2874 // TODO
2875 // only send when there is active voice connection
2876 return false;
2877 }
2878 // wtai://wp/ap;number;name
2879 // number=string(phone-number)
2880 // name=string
2881 if (url.startsWith(SCHEME_WTAI_AP)) {
2882 // TODO
2883 return false;
2884 }
2885 }
2886
2887 Uri uri;
2888 try {
2889 uri = Uri.parse(url);
2890 } catch (IllegalArgumentException ex) {
2891 return false;
2892 }
2893
2894 // check whether other activities want to handle this url
2895 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
2896 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2897 try {
2898 if (startActivityIfNeeded(intent, -1)) {
2899 return true;
2900 }
2901 } catch (ActivityNotFoundException ex) {
2902 // ignore the error. If no application can handle the URL,
2903 // eg about:blank, assume the browser can handle it.
2904 }
2905
2906 if (mMenuIsDown) {
2907 openTab(url);
2908 closeOptionsMenu();
2909 return true;
2910 }
2911
2912 return false;
2913 }
2914
2915 /**
2916 * Updates the lock icon. This method is called when we discover another
2917 * resource to be loaded for this page (for example, javascript). While
2918 * we update the icon type, we do not update the lock icon itself until
2919 * we are done loading, it is slightly more secure this way.
2920 */
2921 @Override
2922 public void onLoadResource(WebView view, String url) {
2923 if (url != null && url.length() > 0) {
2924 // It is only if the page claims to be secure
2925 // that we may have to update the lock:
2926 if (mLockIconType == LOCK_ICON_SECURE) {
2927 // If NOT a 'safe' url, change the lock to mixed content!
2928 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2929 mLockIconType = LOCK_ICON_MIXED;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002930 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002931 Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2932 " updated lock icon to " + mLockIconType + " due to " + url);
2933 }
2934 }
2935 }
2936 }
2937 }
2938
2939 /**
2940 * Show the dialog, asking the user if they would like to continue after
2941 * an excessive number of HTTP redirects.
2942 */
2943 @Override
2944 public void onTooManyRedirects(WebView view, final Message cancelMsg,
2945 final Message continueMsg) {
2946 new AlertDialog.Builder(BrowserActivity.this)
2947 .setTitle(R.string.browserFrameRedirect)
2948 .setMessage(R.string.browserFrame307Post)
2949 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2950 public void onClick(DialogInterface dialog, int which) {
2951 continueMsg.sendToTarget();
2952 }})
2953 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2954 public void onClick(DialogInterface dialog, int which) {
2955 cancelMsg.sendToTarget();
2956 }})
2957 .setOnCancelListener(new OnCancelListener() {
2958 public void onCancel(DialogInterface dialog) {
2959 cancelMsg.sendToTarget();
2960 }})
2961 .show();
2962 }
2963
Patrick Scott37911c72009-03-24 18:02:58 -07002964 // Container class for the next error dialog that needs to be
2965 // displayed.
2966 class ErrorDialog {
2967 public final int mTitle;
2968 public final String mDescription;
2969 public final int mError;
2970 ErrorDialog(int title, String desc, int error) {
2971 mTitle = title;
2972 mDescription = desc;
2973 mError = error;
2974 }
2975 };
2976
2977 private void processNextError() {
2978 if (mQueuedErrors == null) {
2979 return;
2980 }
2981 // The first one is currently displayed so just remove it.
2982 mQueuedErrors.removeFirst();
2983 if (mQueuedErrors.size() == 0) {
2984 mQueuedErrors = null;
2985 return;
2986 }
2987 showError(mQueuedErrors.getFirst());
2988 }
2989
2990 private DialogInterface.OnDismissListener mDialogListener =
2991 new DialogInterface.OnDismissListener() {
2992 public void onDismiss(DialogInterface d) {
2993 processNextError();
2994 }
2995 };
2996 private LinkedList<ErrorDialog> mQueuedErrors;
2997
2998 private void queueError(int err, String desc) {
2999 if (mQueuedErrors == null) {
3000 mQueuedErrors = new LinkedList<ErrorDialog>();
3001 }
3002 for (ErrorDialog d : mQueuedErrors) {
3003 if (d.mError == err) {
3004 // Already saw a similar error, ignore the new one.
3005 return;
3006 }
3007 }
3008 ErrorDialog errDialog = new ErrorDialog(
3009 err == EventHandler.FILE_NOT_FOUND_ERROR ?
3010 R.string.browserFrameFileErrorLabel :
3011 R.string.browserFrameNetworkErrorLabel,
3012 desc, err);
3013 mQueuedErrors.addLast(errDialog);
3014
3015 // Show the dialog now if the queue was empty.
3016 if (mQueuedErrors.size() == 1) {
3017 showError(errDialog);
3018 }
3019 }
3020
3021 private void showError(ErrorDialog errDialog) {
3022 AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
3023 .setTitle(errDialog.mTitle)
3024 .setMessage(errDialog.mDescription)
3025 .setPositiveButton(R.string.ok, null)
3026 .create();
3027 d.setOnDismissListener(mDialogListener);
3028 d.show();
3029 }
3030
The Android Open Source Project0c908882009-03-03 19:32:16 -08003031 /**
3032 * Show a dialog informing the user of the network error reported by
3033 * WebCore.
3034 */
3035 @Override
3036 public void onReceivedError(WebView view, int errorCode,
3037 String description, String failingUrl) {
3038 if (errorCode != EventHandler.ERROR_LOOKUP &&
3039 errorCode != EventHandler.ERROR_CONNECT &&
3040 errorCode != EventHandler.ERROR_BAD_URL &&
3041 errorCode != EventHandler.ERROR_UNSUPPORTED_SCHEME &&
3042 errorCode != EventHandler.FILE_ERROR) {
Patrick Scott37911c72009-03-24 18:02:58 -07003043 queueError(errorCode, description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003044 }
Patrick Scott37911c72009-03-24 18:02:58 -07003045 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
3046 + " " + description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003047
3048 // We need to reset the title after an error.
3049 resetTitleAndRevertLockIcon();
3050 }
3051
3052 /**
3053 * Check with the user if it is ok to resend POST data as the page they
3054 * are trying to navigate to is the result of a POST.
3055 */
3056 @Override
3057 public void onFormResubmission(WebView view, final Message dontResend,
3058 final Message resend) {
3059 new AlertDialog.Builder(BrowserActivity.this)
3060 .setTitle(R.string.browserFrameFormResubmitLabel)
3061 .setMessage(R.string.browserFrameFormResubmitMessage)
3062 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
3063 public void onClick(DialogInterface dialog, int which) {
3064 resend.sendToTarget();
3065 }})
3066 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
3067 public void onClick(DialogInterface dialog, int which) {
3068 dontResend.sendToTarget();
3069 }})
3070 .setOnCancelListener(new OnCancelListener() {
3071 public void onCancel(DialogInterface dialog) {
3072 dontResend.sendToTarget();
3073 }})
3074 .show();
3075 }
3076
3077 /**
3078 * Insert the url into the visited history database.
3079 * @param url The url to be inserted.
3080 * @param isReload True if this url is being reloaded.
3081 * FIXME: Not sure what to do when reloading the page.
3082 */
3083 @Override
3084 public void doUpdateVisitedHistory(WebView view, String url,
3085 boolean isReload) {
3086 if (url.regionMatches(true, 0, "about:", 0, 6)) {
3087 return;
3088 }
3089 Browser.updateVisitedHistory(mResolver, url, true);
3090 WebIconDatabase.getInstance().retainIconForPageUrl(url);
3091 }
3092
3093 /**
3094 * Displays SSL error(s) dialog to the user.
3095 */
3096 @Override
3097 public void onReceivedSslError(
3098 final WebView view, final SslErrorHandler handler, final SslError error) {
3099
3100 if (mSettings.showSecurityWarnings()) {
3101 final LayoutInflater factory =
3102 LayoutInflater.from(BrowserActivity.this);
3103 final View warningsView =
3104 factory.inflate(R.layout.ssl_warnings, null);
3105 final LinearLayout placeholder =
3106 (LinearLayout)warningsView.findViewById(R.id.placeholder);
3107
3108 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3109 LinearLayout ll = (LinearLayout)factory
3110 .inflate(R.layout.ssl_warning, null);
3111 ((TextView)ll.findViewById(R.id.warning))
3112 .setText(R.string.ssl_untrusted);
3113 placeholder.addView(ll);
3114 }
3115
3116 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3117 LinearLayout ll = (LinearLayout)factory
3118 .inflate(R.layout.ssl_warning, null);
3119 ((TextView)ll.findViewById(R.id.warning))
3120 .setText(R.string.ssl_mismatch);
3121 placeholder.addView(ll);
3122 }
3123
3124 if (error.hasError(SslError.SSL_EXPIRED)) {
3125 LinearLayout ll = (LinearLayout)factory
3126 .inflate(R.layout.ssl_warning, null);
3127 ((TextView)ll.findViewById(R.id.warning))
3128 .setText(R.string.ssl_expired);
3129 placeholder.addView(ll);
3130 }
3131
3132 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3133 LinearLayout ll = (LinearLayout)factory
3134 .inflate(R.layout.ssl_warning, null);
3135 ((TextView)ll.findViewById(R.id.warning))
3136 .setText(R.string.ssl_not_yet_valid);
3137 placeholder.addView(ll);
3138 }
3139
3140 new AlertDialog.Builder(BrowserActivity.this)
3141 .setTitle(R.string.security_warning)
3142 .setIcon(android.R.drawable.ic_dialog_alert)
3143 .setView(warningsView)
3144 .setPositiveButton(R.string.ssl_continue,
3145 new DialogInterface.OnClickListener() {
3146 public void onClick(DialogInterface dialog, int whichButton) {
3147 handler.proceed();
3148 }
3149 })
3150 .setNeutralButton(R.string.view_certificate,
3151 new DialogInterface.OnClickListener() {
3152 public void onClick(DialogInterface dialog, int whichButton) {
3153 showSSLCertificateOnError(view, handler, error);
3154 }
3155 })
3156 .setNegativeButton(R.string.cancel,
3157 new DialogInterface.OnClickListener() {
3158 public void onClick(DialogInterface dialog, int whichButton) {
3159 handler.cancel();
3160 BrowserActivity.this.resetTitleAndRevertLockIcon();
3161 }
3162 })
3163 .setOnCancelListener(
3164 new DialogInterface.OnCancelListener() {
3165 public void onCancel(DialogInterface dialog) {
3166 handler.cancel();
3167 BrowserActivity.this.resetTitleAndRevertLockIcon();
3168 }
3169 })
3170 .show();
3171 } else {
3172 handler.proceed();
3173 }
3174 }
3175
3176 /**
3177 * Handles an HTTP authentication request.
3178 *
3179 * @param handler The authentication handler
3180 * @param host The host
3181 * @param realm The realm
3182 */
3183 @Override
3184 public void onReceivedHttpAuthRequest(WebView view,
3185 final HttpAuthHandler handler, final String host, final String realm) {
3186 String username = null;
3187 String password = null;
3188
3189 boolean reuseHttpAuthUsernamePassword =
3190 handler.useHttpAuthUsernamePassword();
3191
3192 if (reuseHttpAuthUsernamePassword &&
3193 (mTabControl.getCurrentWebView() != null)) {
3194 String[] credentials =
3195 mTabControl.getCurrentWebView()
3196 .getHttpAuthUsernamePassword(host, realm);
3197 if (credentials != null && credentials.length == 2) {
3198 username = credentials[0];
3199 password = credentials[1];
3200 }
3201 }
3202
3203 if (username != null && password != null) {
3204 handler.proceed(username, password);
3205 } else {
3206 showHttpAuthentication(handler, host, realm, null, null, null, 0);
3207 }
3208 }
3209
3210 @Override
3211 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
3212 if (mMenuIsDown) {
3213 // only check shortcut key when MENU is held
3214 return getWindow().isShortcutKey(event.getKeyCode(), event);
3215 } else {
3216 return false;
3217 }
3218 }
3219
3220 @Override
3221 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
3222 if (view != mTabControl.getCurrentTopWebView()) {
3223 return;
3224 }
3225 if (event.isDown()) {
3226 BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
3227 } else {
3228 BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
3229 }
3230 }
3231 };
3232
3233 //--------------------------------------------------------------------------
3234 // WebChromeClient implementation
3235 //--------------------------------------------------------------------------
3236
3237 /* package */ WebChromeClient getWebChromeClient() {
3238 return mWebChromeClient;
3239 }
3240
3241 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
3242 // Helper method to create a new tab or sub window.
3243 private void createWindow(final boolean dialog, final Message msg) {
3244 if (dialog) {
3245 mTabControl.createSubWindow();
3246 final TabControl.Tab t = mTabControl.getCurrentTab();
3247 attachSubWindow(t);
3248 WebView.WebViewTransport transport =
3249 (WebView.WebViewTransport) msg.obj;
3250 transport.setWebView(t.getSubWebView());
3251 msg.sendToTarget();
3252 } else {
3253 final TabControl.Tab parent = mTabControl.getCurrentTab();
3254 // openTabAndShow will dispatch the message after creating the
3255 // new WebView. This will prevent another request from coming
3256 // in during the animation.
Grace Klobac9181842009-04-14 08:53:22 -07003257 final TabControl.Tab newTab = openTabAndShow(null, msg, false,
3258 null);
3259 if (newTab != parent) {
3260 parent.addChildTab(newTab);
3261 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003262 WebView.WebViewTransport transport =
3263 (WebView.WebViewTransport) msg.obj;
3264 transport.setWebView(mTabControl.getCurrentWebView());
3265 }
3266 }
3267
3268 @Override
3269 public boolean onCreateWindow(WebView view, final boolean dialog,
3270 final boolean userGesture, final Message resultMsg) {
3271 // Ignore these requests during tab animations or if the tab
3272 // overview is showing.
3273 if (mAnimationCount > 0 || mTabOverview != null) {
3274 return false;
3275 }
3276 // Short-circuit if we can't create any more tabs or sub windows.
3277 if (dialog && mTabControl.getCurrentSubWindow() != null) {
3278 new AlertDialog.Builder(BrowserActivity.this)
3279 .setTitle(R.string.too_many_subwindows_dialog_title)
3280 .setIcon(android.R.drawable.ic_dialog_alert)
3281 .setMessage(R.string.too_many_subwindows_dialog_message)
3282 .setPositiveButton(R.string.ok, null)
3283 .show();
3284 return false;
3285 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
3286 new AlertDialog.Builder(BrowserActivity.this)
3287 .setTitle(R.string.too_many_windows_dialog_title)
3288 .setIcon(android.R.drawable.ic_dialog_alert)
3289 .setMessage(R.string.too_many_windows_dialog_message)
3290 .setPositiveButton(R.string.ok, null)
3291 .show();
3292 return false;
3293 }
3294
3295 // Short-circuit if this was a user gesture.
3296 if (userGesture) {
3297 // createWindow will call openTabAndShow for new Windows and
3298 // that will call tabPicker which will increment
3299 // mAnimationCount.
3300 createWindow(dialog, resultMsg);
3301 return true;
3302 }
3303
3304 // Allow the popup and create the appropriate window.
3305 final AlertDialog.OnClickListener allowListener =
3306 new AlertDialog.OnClickListener() {
3307 public void onClick(DialogInterface d,
3308 int which) {
3309 // Same comment as above for setting
3310 // mAnimationCount.
3311 createWindow(dialog, resultMsg);
3312 // Since we incremented mAnimationCount while the
3313 // dialog was up, we have to decrement it here.
3314 mAnimationCount--;
3315 }
3316 };
3317
3318 // Block the popup by returning a null WebView.
3319 final AlertDialog.OnClickListener blockListener =
3320 new AlertDialog.OnClickListener() {
3321 public void onClick(DialogInterface d, int which) {
3322 resultMsg.sendToTarget();
3323 // We are not going to trigger an animation so
3324 // unblock keys and animation requests.
3325 mAnimationCount--;
3326 }
3327 };
3328
3329 // Build a confirmation dialog to display to the user.
3330 final AlertDialog d =
3331 new AlertDialog.Builder(BrowserActivity.this)
3332 .setTitle(R.string.attention)
3333 .setIcon(android.R.drawable.ic_dialog_alert)
3334 .setMessage(R.string.popup_window_attempt)
3335 .setPositiveButton(R.string.allow, allowListener)
3336 .setNegativeButton(R.string.block, blockListener)
3337 .setCancelable(false)
3338 .create();
3339
3340 // Show the confirmation dialog.
3341 d.show();
3342 // We want to increment mAnimationCount here to prevent a
3343 // potential race condition. If the user allows a pop-up from a
3344 // site and that pop-up then triggers another pop-up, it is
3345 // possible to get the BACK key between here and when the dialog
3346 // appears.
3347 mAnimationCount++;
3348 return true;
3349 }
3350
3351 @Override
3352 public void onCloseWindow(WebView window) {
3353 final int currentIndex = mTabControl.getCurrentIndex();
3354 final TabControl.Tab parent =
3355 mTabControl.getCurrentTab().getParentTab();
3356 if (parent != null) {
3357 // JavaScript can only close popup window.
3358 switchTabs(currentIndex, mTabControl.getTabIndex(parent), true);
3359 }
3360 }
3361
3362 @Override
3363 public void onProgressChanged(WebView view, int newProgress) {
3364 // Block progress updates to the title bar while the tab overview
3365 // is animating or being displayed.
3366 if (mAnimationCount == 0 && mTabOverview == null) {
3367 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
3368 newProgress * 100);
3369 }
3370
3371 if (newProgress == 100) {
3372 // onProgressChanged() is called for sub-frame too while
3373 // onPageFinished() is only called for the main frame. sync
3374 // cookie and cache promptly here.
3375 CookieSyncManager.getInstance().sync();
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07003376 if (mInLoad) {
3377 mInLoad = false;
3378 updateInLoadMenuItems();
3379 }
3380 } else {
3381 // onPageFinished may have already been called but a subframe
3382 // is still loading and updating the progress. Reset mInLoad
3383 // and update the menu items.
3384 if (!mInLoad) {
3385 mInLoad = true;
3386 updateInLoadMenuItems();
3387 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003388 }
3389 }
3390
3391 @Override
3392 public void onReceivedTitle(WebView view, String title) {
3393 String url = view.getOriginalUrl();
3394
3395 // here, if url is null, we want to reset the title
3396 setUrlTitle(url, title);
3397
3398 if (url == null ||
3399 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
3400 return;
3401 }
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003402 // See if we can find the current url in our history database and
3403 // add the new title to it.
The Android Open Source Project0c908882009-03-03 19:32:16 -08003404 if (url.startsWith("http://www.")) {
3405 url = url.substring(11);
3406 } else if (url.startsWith("http://")) {
3407 url = url.substring(4);
3408 }
3409 try {
3410 url = "%" + url;
3411 String [] selArgs = new String[] { url };
3412
3413 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
3414 + Browser.BookmarkColumns.BOOKMARK + " = 0";
3415 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
3416 Browser.HISTORY_PROJECTION, where, selArgs, null);
3417 if (c.moveToFirst()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003418 // Current implementation of database only has one entry per
3419 // url.
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003420 ContentValues map = new ContentValues();
3421 map.put(Browser.BookmarkColumns.TITLE, title);
3422 mResolver.update(Browser.BOOKMARKS_URI, map,
3423 "_id = " + c.getInt(0), null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003424 }
3425 c.close();
3426 } catch (IllegalStateException e) {
3427 Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3428 } catch (SQLiteException ex) {
3429 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3430 }
3431 }
3432
3433 @Override
3434 public void onReceivedIcon(WebView view, Bitmap icon) {
3435 updateIcon(view.getUrl(), icon);
3436 }
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003437
3438 /**
3439 * The origin has exceeded it's database quota.
3440 * @param url the URL that exceeded the quota
3441 * @param databaseIdentifier the identifier of the database on
3442 * which the transaction that caused the quota overflow was run
3443 * @param currentQuota the current quota for the origin.
3444 * @param quotaUpdater The callback to run when a decision to allow or
3445 * deny quota has been made. Don't forget to call this!
3446 */
3447 @Override
3448 public void onExceededDatabaseQuota(String url,
3449 String databaseIdentifier, long currentQuota,
3450 WebStorage.QuotaUpdater quotaUpdater) {
3451 if(LOGV_ENABLED) {
3452 Log.v(LOGTAG,
3453 "BrowserActivity received onExceededDatabaseQuota for "
3454 + url +
3455 ":"
3456 + databaseIdentifier +
3457 "(current quota: "
3458 + currentQuota +
3459 ")");
3460 }
Nicolas Roard78a98e42009-05-11 13:34:17 +01003461 mWebStorageQuotaUpdater = quotaUpdater;
3462 String DIALOG_PACKAGE = "com.android.browser";
3463 String DIALOG_CLASS = DIALOG_PACKAGE + ".PermissionDialog";
3464 Intent intent = new Intent();
3465 intent.setClassName(DIALOG_PACKAGE, DIALOG_CLASS);
3466 intent.putExtra(PermissionDialog.PARAM_ORIGIN, url);
3467 intent.putExtra(PermissionDialog.PARAM_QUOTA, currentQuota);
3468 startActivityForResult(intent, WEBSTORAGE_QUOTA_DIALOG);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003469 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003470 };
3471
3472 /**
3473 * Notify the host application a download should be done, or that
3474 * the data should be streamed if a streaming viewer is available.
3475 * @param url The full url to the content that should be downloaded
3476 * @param contentDisposition Content-disposition http header, if
3477 * present.
3478 * @param mimetype The mimetype of the content reported by the server
3479 * @param contentLength The file size reported by the server
3480 */
3481 public void onDownloadStart(String url, String userAgent,
3482 String contentDisposition, String mimetype, long contentLength) {
3483 // if we're dealing wih A/V content that's not explicitly marked
3484 // for download, check if it's streamable.
3485 if (contentDisposition == null
3486 || !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) {
3487 // query the package manager to see if there's a registered handler
3488 // that matches.
3489 Intent intent = new Intent(Intent.ACTION_VIEW);
3490 intent.setDataAndType(Uri.parse(url), mimetype);
3491 if (getPackageManager().resolveActivity(intent,
3492 PackageManager.MATCH_DEFAULT_ONLY) != null) {
3493 // someone knows how to handle this mime type with this scheme, don't download.
3494 try {
3495 startActivity(intent);
3496 return;
3497 } catch (ActivityNotFoundException ex) {
Dave Bort31a6d1c2009-04-13 15:56:49 -07003498 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003499 Log.d(LOGTAG, "activity not found for " + mimetype
3500 + " over " + Uri.parse(url).getScheme(), ex);
3501 }
3502 // Best behavior is to fall back to a download in this case
3503 }
3504 }
3505 }
3506 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3507 }
3508
3509 /**
3510 * Notify the host application a download should be done, even if there
3511 * is a streaming viewer available for thise type.
3512 * @param url The full url to the content that should be downloaded
3513 * @param contentDisposition Content-disposition http header, if
3514 * present.
3515 * @param mimetype The mimetype of the content reported by the server
3516 * @param contentLength The file size reported by the server
3517 */
3518 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3519 String contentDisposition, String mimetype, long contentLength) {
3520
3521 String filename = URLUtil.guessFileName(url,
3522 contentDisposition, mimetype);
3523
3524 // Check to see if we have an SDCard
3525 String status = Environment.getExternalStorageState();
3526 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3527 int title;
3528 String msg;
3529
3530 // Check to see if the SDCard is busy, same as the music app
3531 if (status.equals(Environment.MEDIA_SHARED)) {
3532 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3533 title = R.string.download_sdcard_busy_dlg_title;
3534 } else {
3535 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3536 title = R.string.download_no_sdcard_dlg_title;
3537 }
3538
3539 new AlertDialog.Builder(this)
3540 .setTitle(title)
3541 .setIcon(android.R.drawable.ic_dialog_alert)
3542 .setMessage(msg)
3543 .setPositiveButton(R.string.ok, null)
3544 .show();
3545 return;
3546 }
3547
3548 // java.net.URI is a lot stricter than KURL so we have to undo
3549 // KURL's percent-encoding and redo the encoding using java.net.URI.
3550 URI uri = null;
3551 try {
3552 // Undo the percent-encoding that KURL may have done.
3553 String newUrl = new String(URLUtil.decode(url.getBytes()));
3554 // Parse the url into pieces
3555 WebAddress w = new WebAddress(newUrl);
3556 String frag = null;
3557 String query = null;
3558 String path = w.mPath;
3559 // Break the path into path, query, and fragment
3560 if (path.length() > 0) {
3561 // Strip the fragment
3562 int idx = path.lastIndexOf('#');
3563 if (idx != -1) {
3564 frag = path.substring(idx + 1);
3565 path = path.substring(0, idx);
3566 }
3567 idx = path.lastIndexOf('?');
3568 if (idx != -1) {
3569 query = path.substring(idx + 1);
3570 path = path.substring(0, idx);
3571 }
3572 }
3573 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3574 query, frag);
3575 } catch (Exception e) {
3576 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3577 return;
3578 }
3579
3580 // XXX: Have to use the old url since the cookies were stored using the
3581 // old percent-encoded url.
3582 String cookies = CookieManager.getInstance().getCookie(url);
3583
3584 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003585 values.put(Downloads.COLUMN_URI, uri.toString());
3586 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3587 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3588 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003589 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003590 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003591 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003592 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3593 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3594 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3595 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003596 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003597 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003598 }
3599 if (mimetype == null) {
3600 // We must have long pressed on a link or image to download it. We
3601 // are not sure of the mimetype in this case, so do a head request
3602 new FetchUrlMimeType(this).execute(values);
3603 } else {
3604 final Uri contentUri =
3605 getContentResolver().insert(Downloads.CONTENT_URI, values);
3606 viewDownloads(contentUri);
3607 }
3608
3609 }
3610
3611 /**
3612 * Resets the lock icon. This method is called when we start a new load and
3613 * know the url to be loaded.
3614 */
3615 private void resetLockIcon(String url) {
3616 // Save the lock-icon state (we revert to it if the load gets cancelled)
3617 saveLockIcon();
3618
3619 mLockIconType = LOCK_ICON_UNSECURE;
3620 if (URLUtil.isHttpsUrl(url)) {
3621 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003622 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003623 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3624 " reset lock icon to " + mLockIconType);
3625 }
3626 }
3627
3628 updateLockIconImage(LOCK_ICON_UNSECURE);
3629 }
3630
3631 /**
3632 * Resets the lock icon. This method is called when the icon needs to be
3633 * reset but we do not know whether we are loading a secure or not secure
3634 * page.
3635 */
3636 private void resetLockIcon() {
3637 // Save the lock-icon state (we revert to it if the load gets cancelled)
3638 saveLockIcon();
3639
3640 mLockIconType = LOCK_ICON_UNSECURE;
3641
Dave Bort31a6d1c2009-04-13 15:56:49 -07003642 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003643 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3644 " reset lock icon to " + mLockIconType);
3645 }
3646
3647 updateLockIconImage(LOCK_ICON_UNSECURE);
3648 }
3649
3650 /**
3651 * Updates the lock-icon image in the title-bar.
3652 */
3653 private void updateLockIconImage(int lockIconType) {
3654 Drawable d = null;
3655 if (lockIconType == LOCK_ICON_SECURE) {
3656 d = mSecLockIcon;
3657 } else if (lockIconType == LOCK_ICON_MIXED) {
3658 d = mMixLockIcon;
3659 }
3660 // If the tab overview is animating or being shown, do not update the
3661 // lock icon.
3662 if (mAnimationCount == 0 && mTabOverview == null) {
3663 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
3664 }
3665 }
3666
3667 /**
3668 * Displays a page-info dialog.
3669 * @param tab The tab to show info about
3670 * @param fromShowSSLCertificateOnError The flag that indicates whether
3671 * this dialog was opened from the SSL-certificate-on-error dialog or
3672 * not. This is important, since we need to know whether to return to
3673 * the parent dialog or simply dismiss.
3674 */
3675 private void showPageInfo(final TabControl.Tab tab,
3676 final boolean fromShowSSLCertificateOnError) {
3677 final LayoutInflater factory = LayoutInflater
3678 .from(this);
3679
3680 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3681
3682 final WebView view = tab.getWebView();
3683
3684 String url = null;
3685 String title = null;
3686
3687 if (view == null) {
3688 url = tab.getUrl();
3689 title = tab.getTitle();
3690 } else if (view == mTabControl.getCurrentWebView()) {
3691 // Use the cached title and url if this is the current WebView
3692 url = mUrl;
3693 title = mTitle;
3694 } else {
3695 url = view.getUrl();
3696 title = view.getTitle();
3697 }
3698
3699 if (url == null) {
3700 url = "";
3701 }
3702 if (title == null) {
3703 title = "";
3704 }
3705
3706 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3707 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3708
3709 mPageInfoView = tab;
3710 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3711
3712 AlertDialog.Builder alertDialogBuilder =
3713 new AlertDialog.Builder(this)
3714 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3715 .setView(pageInfoView)
3716 .setPositiveButton(
3717 R.string.ok,
3718 new DialogInterface.OnClickListener() {
3719 public void onClick(DialogInterface dialog,
3720 int whichButton) {
3721 mPageInfoDialog = null;
3722 mPageInfoView = null;
3723 mPageInfoFromShowSSLCertificateOnError = null;
3724
3725 // if we came here from the SSL error dialog
3726 if (fromShowSSLCertificateOnError) {
3727 // go back to the SSL error dialog
3728 showSSLCertificateOnError(
3729 mSSLCertificateOnErrorView,
3730 mSSLCertificateOnErrorHandler,
3731 mSSLCertificateOnErrorError);
3732 }
3733 }
3734 })
3735 .setOnCancelListener(
3736 new DialogInterface.OnCancelListener() {
3737 public void onCancel(DialogInterface dialog) {
3738 mPageInfoDialog = null;
3739 mPageInfoView = null;
3740 mPageInfoFromShowSSLCertificateOnError = null;
3741
3742 // if we came here from the SSL error dialog
3743 if (fromShowSSLCertificateOnError) {
3744 // go back to the SSL error dialog
3745 showSSLCertificateOnError(
3746 mSSLCertificateOnErrorView,
3747 mSSLCertificateOnErrorHandler,
3748 mSSLCertificateOnErrorError);
3749 }
3750 }
3751 });
3752
3753 // if we have a main top-level page SSL certificate set or a certificate
3754 // error
3755 if (fromShowSSLCertificateOnError ||
3756 (view != null && view.getCertificate() != null)) {
3757 // add a 'View Certificate' button
3758 alertDialogBuilder.setNeutralButton(
3759 R.string.view_certificate,
3760 new DialogInterface.OnClickListener() {
3761 public void onClick(DialogInterface dialog,
3762 int whichButton) {
3763 mPageInfoDialog = null;
3764 mPageInfoView = null;
3765 mPageInfoFromShowSSLCertificateOnError = null;
3766
3767 // if we came here from the SSL error dialog
3768 if (fromShowSSLCertificateOnError) {
3769 // go back to the SSL error dialog
3770 showSSLCertificateOnError(
3771 mSSLCertificateOnErrorView,
3772 mSSLCertificateOnErrorHandler,
3773 mSSLCertificateOnErrorError);
3774 } else {
3775 // otherwise, display the top-most certificate from
3776 // the chain
3777 if (view.getCertificate() != null) {
3778 showSSLCertificate(tab);
3779 }
3780 }
3781 }
3782 });
3783 }
3784
3785 mPageInfoDialog = alertDialogBuilder.show();
3786 }
3787
3788 /**
3789 * Displays the main top-level page SSL certificate dialog
3790 * (accessible from the Page-Info dialog).
3791 * @param tab The tab to show certificate for.
3792 */
3793 private void showSSLCertificate(final TabControl.Tab tab) {
3794 final View certificateView =
3795 inflateCertificateView(tab.getWebView().getCertificate());
3796 if (certificateView == null) {
3797 return;
3798 }
3799
3800 LayoutInflater factory = LayoutInflater.from(this);
3801
3802 final LinearLayout placeholder =
3803 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3804
3805 LinearLayout ll = (LinearLayout) factory.inflate(
3806 R.layout.ssl_success, placeholder);
3807 ((TextView)ll.findViewById(R.id.success))
3808 .setText(R.string.ssl_certificate_is_valid);
3809
3810 mSSLCertificateView = tab;
3811 mSSLCertificateDialog =
3812 new AlertDialog.Builder(this)
3813 .setTitle(R.string.ssl_certificate).setIcon(
3814 R.drawable.ic_dialog_browser_certificate_secure)
3815 .setView(certificateView)
3816 .setPositiveButton(R.string.ok,
3817 new DialogInterface.OnClickListener() {
3818 public void onClick(DialogInterface dialog,
3819 int whichButton) {
3820 mSSLCertificateDialog = null;
3821 mSSLCertificateView = null;
3822
3823 showPageInfo(tab, false);
3824 }
3825 })
3826 .setOnCancelListener(
3827 new DialogInterface.OnCancelListener() {
3828 public void onCancel(DialogInterface dialog) {
3829 mSSLCertificateDialog = null;
3830 mSSLCertificateView = null;
3831
3832 showPageInfo(tab, false);
3833 }
3834 })
3835 .show();
3836 }
3837
3838 /**
3839 * Displays the SSL error certificate dialog.
3840 * @param view The target web-view.
3841 * @param handler The SSL error handler responsible for cancelling the
3842 * connection that resulted in an SSL error or proceeding per user request.
3843 * @param error The SSL error object.
3844 */
3845 private void showSSLCertificateOnError(
3846 final WebView view, final SslErrorHandler handler, final SslError error) {
3847
3848 final View certificateView =
3849 inflateCertificateView(error.getCertificate());
3850 if (certificateView == null) {
3851 return;
3852 }
3853
3854 LayoutInflater factory = LayoutInflater.from(this);
3855
3856 final LinearLayout placeholder =
3857 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3858
3859 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3860 LinearLayout ll = (LinearLayout)factory
3861 .inflate(R.layout.ssl_warning, placeholder);
3862 ((TextView)ll.findViewById(R.id.warning))
3863 .setText(R.string.ssl_untrusted);
3864 }
3865
3866 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3867 LinearLayout ll = (LinearLayout)factory
3868 .inflate(R.layout.ssl_warning, placeholder);
3869 ((TextView)ll.findViewById(R.id.warning))
3870 .setText(R.string.ssl_mismatch);
3871 }
3872
3873 if (error.hasError(SslError.SSL_EXPIRED)) {
3874 LinearLayout ll = (LinearLayout)factory
3875 .inflate(R.layout.ssl_warning, placeholder);
3876 ((TextView)ll.findViewById(R.id.warning))
3877 .setText(R.string.ssl_expired);
3878 }
3879
3880 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3881 LinearLayout ll = (LinearLayout)factory
3882 .inflate(R.layout.ssl_warning, placeholder);
3883 ((TextView)ll.findViewById(R.id.warning))
3884 .setText(R.string.ssl_not_yet_valid);
3885 }
3886
3887 mSSLCertificateOnErrorHandler = handler;
3888 mSSLCertificateOnErrorView = view;
3889 mSSLCertificateOnErrorError = error;
3890 mSSLCertificateOnErrorDialog =
3891 new AlertDialog.Builder(this)
3892 .setTitle(R.string.ssl_certificate).setIcon(
3893 R.drawable.ic_dialog_browser_certificate_partially_secure)
3894 .setView(certificateView)
3895 .setPositiveButton(R.string.ok,
3896 new DialogInterface.OnClickListener() {
3897 public void onClick(DialogInterface dialog,
3898 int whichButton) {
3899 mSSLCertificateOnErrorDialog = null;
3900 mSSLCertificateOnErrorView = null;
3901 mSSLCertificateOnErrorHandler = null;
3902 mSSLCertificateOnErrorError = null;
3903
3904 mWebViewClient.onReceivedSslError(
3905 view, handler, error);
3906 }
3907 })
3908 .setNeutralButton(R.string.page_info_view,
3909 new DialogInterface.OnClickListener() {
3910 public void onClick(DialogInterface dialog,
3911 int whichButton) {
3912 mSSLCertificateOnErrorDialog = null;
3913
3914 // do not clear the dialog state: we will
3915 // need to show the dialog again once the
3916 // user is done exploring the page-info details
3917
3918 showPageInfo(mTabControl.getTabFromView(view),
3919 true);
3920 }
3921 })
3922 .setOnCancelListener(
3923 new DialogInterface.OnCancelListener() {
3924 public void onCancel(DialogInterface dialog) {
3925 mSSLCertificateOnErrorDialog = null;
3926 mSSLCertificateOnErrorView = null;
3927 mSSLCertificateOnErrorHandler = null;
3928 mSSLCertificateOnErrorError = null;
3929
3930 mWebViewClient.onReceivedSslError(
3931 view, handler, error);
3932 }
3933 })
3934 .show();
3935 }
3936
3937 /**
3938 * Inflates the SSL certificate view (helper method).
3939 * @param certificate The SSL certificate.
3940 * @return The resultant certificate view with issued-to, issued-by,
3941 * issued-on, expires-on, and possibly other fields set.
3942 * If the input certificate is null, returns null.
3943 */
3944 private View inflateCertificateView(SslCertificate certificate) {
3945 if (certificate == null) {
3946 return null;
3947 }
3948
3949 LayoutInflater factory = LayoutInflater.from(this);
3950
3951 View certificateView = factory.inflate(
3952 R.layout.ssl_certificate, null);
3953
3954 // issued to:
3955 SslCertificate.DName issuedTo = certificate.getIssuedTo();
3956 if (issuedTo != null) {
3957 ((TextView) certificateView.findViewById(R.id.to_common))
3958 .setText(issuedTo.getCName());
3959 ((TextView) certificateView.findViewById(R.id.to_org))
3960 .setText(issuedTo.getOName());
3961 ((TextView) certificateView.findViewById(R.id.to_org_unit))
3962 .setText(issuedTo.getUName());
3963 }
3964
3965 // issued by:
3966 SslCertificate.DName issuedBy = certificate.getIssuedBy();
3967 if (issuedBy != null) {
3968 ((TextView) certificateView.findViewById(R.id.by_common))
3969 .setText(issuedBy.getCName());
3970 ((TextView) certificateView.findViewById(R.id.by_org))
3971 .setText(issuedBy.getOName());
3972 ((TextView) certificateView.findViewById(R.id.by_org_unit))
3973 .setText(issuedBy.getUName());
3974 }
3975
3976 // issued on:
3977 String issuedOn = reformatCertificateDate(
3978 certificate.getValidNotBefore());
3979 ((TextView) certificateView.findViewById(R.id.issued_on))
3980 .setText(issuedOn);
3981
3982 // expires on:
3983 String expiresOn = reformatCertificateDate(
3984 certificate.getValidNotAfter());
3985 ((TextView) certificateView.findViewById(R.id.expires_on))
3986 .setText(expiresOn);
3987
3988 return certificateView;
3989 }
3990
3991 /**
3992 * Re-formats the certificate date (Date.toString()) string to
3993 * a properly localized date string.
3994 * @return Properly localized version of the certificate date string and
3995 * the original certificate date string if fails to localize.
3996 * If the original string is null, returns an empty string "".
3997 */
3998 private String reformatCertificateDate(String certificateDate) {
3999 String reformattedDate = null;
4000
4001 if (certificateDate != null) {
4002 Date date = null;
4003 try {
4004 date = java.text.DateFormat.getInstance().parse(certificateDate);
4005 } catch (ParseException e) {
4006 date = null;
4007 }
4008
4009 if (date != null) {
4010 reformattedDate =
4011 DateFormat.getDateFormat(this).format(date);
4012 }
4013 }
4014
4015 return reformattedDate != null ? reformattedDate :
4016 (certificateDate != null ? certificateDate : "");
4017 }
4018
4019 /**
4020 * Displays an http-authentication dialog.
4021 */
4022 private void showHttpAuthentication(final HttpAuthHandler handler,
4023 final String host, final String realm, final String title,
4024 final String name, final String password, int focusId) {
4025 LayoutInflater factory = LayoutInflater.from(this);
4026 final View v = factory
4027 .inflate(R.layout.http_authentication, null);
4028 if (name != null) {
4029 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
4030 }
4031 if (password != null) {
4032 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
4033 }
4034
4035 String titleText = title;
4036 if (titleText == null) {
4037 titleText = getText(R.string.sign_in_to).toString().replace(
4038 "%s1", host).replace("%s2", realm);
4039 }
4040
4041 mHttpAuthHandler = handler;
4042 AlertDialog dialog = new AlertDialog.Builder(this)
4043 .setTitle(titleText)
4044 .setIcon(android.R.drawable.ic_dialog_alert)
4045 .setView(v)
4046 .setPositiveButton(R.string.action,
4047 new DialogInterface.OnClickListener() {
4048 public void onClick(DialogInterface dialog,
4049 int whichButton) {
4050 String nm = ((EditText) v
4051 .findViewById(R.id.username_edit))
4052 .getText().toString();
4053 String pw = ((EditText) v
4054 .findViewById(R.id.password_edit))
4055 .getText().toString();
4056 BrowserActivity.this.setHttpAuthUsernamePassword
4057 (host, realm, nm, pw);
4058 handler.proceed(nm, pw);
4059 mHttpAuthenticationDialog = null;
4060 mHttpAuthHandler = null;
4061 }})
4062 .setNegativeButton(R.string.cancel,
4063 new DialogInterface.OnClickListener() {
4064 public void onClick(DialogInterface dialog,
4065 int whichButton) {
4066 handler.cancel();
4067 BrowserActivity.this.resetTitleAndRevertLockIcon();
4068 mHttpAuthenticationDialog = null;
4069 mHttpAuthHandler = null;
4070 }})
4071 .setOnCancelListener(new DialogInterface.OnCancelListener() {
4072 public void onCancel(DialogInterface dialog) {
4073 handler.cancel();
4074 BrowserActivity.this.resetTitleAndRevertLockIcon();
4075 mHttpAuthenticationDialog = null;
4076 mHttpAuthHandler = null;
4077 }})
4078 .create();
4079 // Make the IME appear when the dialog is displayed if applicable.
4080 dialog.getWindow().setSoftInputMode(
4081 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
4082 dialog.show();
4083 if (focusId != 0) {
4084 dialog.findViewById(focusId).requestFocus();
4085 } else {
4086 v.findViewById(R.id.username_edit).requestFocus();
4087 }
4088 mHttpAuthenticationDialog = dialog;
4089 }
4090
4091 public int getProgress() {
4092 WebView w = mTabControl.getCurrentWebView();
4093 if (w != null) {
4094 return w.getProgress();
4095 } else {
4096 return 100;
4097 }
4098 }
4099
4100 /**
4101 * Set HTTP authentication password.
4102 *
4103 * @param host The host for the password
4104 * @param realm The realm for the password
4105 * @param username The username for the password. If it is null, it means
4106 * password can't be saved.
4107 * @param password The password
4108 */
4109 public void setHttpAuthUsernamePassword(String host, String realm,
4110 String username,
4111 String password) {
4112 WebView w = mTabControl.getCurrentWebView();
4113 if (w != null) {
4114 w.setHttpAuthUsernamePassword(host, realm, username, password);
4115 }
4116 }
4117
4118 /**
4119 * connectivity manager says net has come or gone... inform the user
4120 * @param up true if net has come up, false if net has gone down
4121 */
4122 public void onNetworkToggle(boolean up) {
4123 if (up == mIsNetworkUp) {
4124 return;
4125 } else if (up) {
4126 mIsNetworkUp = true;
4127 if (mAlertDialog != null) {
4128 mAlertDialog.cancel();
4129 mAlertDialog = null;
4130 }
4131 } else {
4132 mIsNetworkUp = false;
4133 if (mInLoad && mAlertDialog == null) {
4134 mAlertDialog = new AlertDialog.Builder(this)
4135 .setTitle(R.string.loadSuspendedTitle)
4136 .setMessage(R.string.loadSuspended)
4137 .setPositiveButton(R.string.ok, null)
4138 .show();
4139 }
4140 }
4141 WebView w = mTabControl.getCurrentWebView();
4142 if (w != null) {
4143 w.setNetworkAvailable(up);
4144 }
4145 }
4146
4147 @Override
4148 protected void onActivityResult(int requestCode, int resultCode,
4149 Intent intent) {
4150 switch (requestCode) {
4151 case COMBO_PAGE:
4152 if (resultCode == RESULT_OK && intent != null) {
4153 String data = intent.getAction();
4154 Bundle extras = intent.getExtras();
4155 if (extras != null && extras.getBoolean("new_window", false)) {
4156 openTab(data);
4157 } else {
4158 final TabControl.Tab currentTab =
4159 mTabControl.getCurrentTab();
4160 // If the Window overview is up and we are not in the
4161 // middle of an animation, animate away from it to the
4162 // current tab.
4163 if (mTabOverview != null && mAnimationCount == 0) {
4164 sendAnimateFromOverview(currentTab, false, data,
4165 TAB_OVERVIEW_DELAY, null);
4166 } else {
4167 dismissSubWindow(currentTab);
4168 if (data != null && data.length() != 0) {
4169 getTopWindow().loadUrl(data);
4170 }
4171 }
4172 }
4173 }
4174 break;
Nicolas Roard78a98e42009-05-11 13:34:17 +01004175 case WEBSTORAGE_QUOTA_DIALOG:
4176 long currentQuota = 0;
4177 if (resultCode == RESULT_OK && intent != null) {
4178 currentQuota = intent.getLongExtra(
4179 PermissionDialog.PARAM_QUOTA, currentQuota);
4180 }
4181 mWebStorageQuotaUpdater.updateQuota(currentQuota);
4182 break;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004183 default:
4184 break;
4185 }
4186 getTopWindow().requestFocus();
4187 }
4188
4189 /*
4190 * This method is called as a result of the user selecting the options
4191 * menu to see the download window, or when a download changes state. It
4192 * shows the download window ontop of the current window.
4193 */
4194 /* package */ void viewDownloads(Uri downloadRecord) {
4195 Intent intent = new Intent(this,
4196 BrowserDownloadPage.class);
4197 intent.setData(downloadRecord);
4198 startActivityForResult(intent, this.DOWNLOAD_PAGE);
4199
4200 }
4201
4202 /**
4203 * Handle results from Tab Switcher mTabOverview tool
4204 */
4205 private class TabListener implements ImageGrid.Listener {
4206 public void remove(int position) {
4207 // Note: Remove is not enabled if we have only one tab.
Dave Bort31a6d1c2009-04-13 15:56:49 -07004208 if (DEBUG && mTabControl.getTabCount() == 1) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004209 throw new AssertionError();
4210 }
4211
4212 // Remember the current tab.
4213 TabControl.Tab current = mTabControl.getCurrentTab();
4214 final TabControl.Tab remove = mTabControl.getTab(position);
4215 mTabControl.removeTab(remove);
4216 // If we removed the current tab, use the tab at position - 1 if
4217 // possible.
4218 if (current == remove) {
4219 // If the user removes the last tab, act like the New Tab item
4220 // was clicked on.
4221 if (mTabControl.getTabCount() == 0) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07004222 current = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08004223 sendAnimateFromOverview(current, true,
4224 mSettings.getHomePage(), TAB_OVERVIEW_DELAY, null);
4225 } else {
4226 final int index = position > 0 ? (position - 1) : 0;
4227 current = mTabControl.getTab(index);
4228 }
4229 }
4230
4231 // The tab overview could have been dismissed before this method is
4232 // called.
4233 if (mTabOverview != null) {
4234 // Remove the tab and change the index.
4235 mTabOverview.remove(position);
4236 mTabOverview.setCurrentIndex(mTabControl.getTabIndex(current));
4237 }
4238
4239 // Only the current tab ensures its WebView is non-null. This
4240 // implies that we are reloading the freed tab.
4241 mTabControl.setCurrentTab(current);
4242 }
4243 public void onClick(int index) {
4244 // Change the tab if necessary.
4245 // Index equals ImageGrid.CANCEL when pressing back from the tab
4246 // overview.
4247 if (index == ImageGrid.CANCEL) {
4248 index = mTabControl.getCurrentIndex();
4249 // The current index is -1 if the current tab was removed.
4250 if (index == -1) {
4251 // Take the last tab as a fallback.
4252 index = mTabControl.getTabCount() - 1;
4253 }
4254 }
4255
The Android Open Source Project0c908882009-03-03 19:32:16 -08004256 // NEW_TAB means that the "New Tab" cell was clicked on.
4257 if (index == ImageGrid.NEW_TAB) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07004258 openTabAndShow(mSettings.getHomePage(), null, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004259 } else {
4260 sendAnimateFromOverview(mTabControl.getTab(index),
4261 false, null, 0, null);
4262 }
4263 }
4264 }
4265
4266 // A fake View that draws the WebView's picture with a fast zoom filter.
4267 // The View is used in case the tab is freed during the animation because
4268 // of low memory.
4269 private static class AnimatingView extends View {
4270 private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
4271 Paint.DITHER_FLAG | Paint.SUBPIXEL_TEXT_FLAG;
4272 private static final DrawFilter sZoomFilter =
4273 new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
4274 private final Picture mPicture;
4275 private final float mScale;
4276 private final int mScrollX;
4277 private final int mScrollY;
4278 final TabControl.Tab mTab;
4279
4280 AnimatingView(Context ctxt, TabControl.Tab t) {
4281 super(ctxt);
4282 mTab = t;
Patrick Scottae641ac2009-04-20 13:51:49 -04004283 if (t != null && t.getTopWindow() != null) {
4284 // Use the top window in the animation since the tab overview
4285 // will display the top window in each cell.
4286 final WebView w = t.getTopWindow();
4287 mPicture = w.capturePicture();
4288 mScale = w.getScale() / w.getWidth();
4289 mScrollX = w.getScrollX();
4290 mScrollY = w.getScrollY();
4291 } else {
4292 mPicture = null;
4293 mScale = 1.0f;
4294 mScrollX = mScrollY = 0;
4295 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08004296 }
4297
4298 @Override
4299 protected void onDraw(Canvas canvas) {
4300 canvas.save();
4301 canvas.drawColor(Color.WHITE);
4302 if (mPicture != null) {
4303 canvas.setDrawFilter(sZoomFilter);
4304 float scale = getWidth() * mScale;
4305 canvas.scale(scale, scale);
4306 canvas.translate(-mScrollX, -mScrollY);
4307 canvas.drawPicture(mPicture);
4308 }
4309 canvas.restore();
4310 }
4311 }
4312
4313 /**
4314 * Open the tab picker. This function will always use the current tab in
4315 * its animation.
4316 * @param stay boolean stating whether the tab picker is to remain open
4317 * (in which case it needs a listener and its menu) or not.
4318 * @param index The index of the tab to show as the selection in the tab
4319 * overview.
4320 * @param remove If true, the tab at index will be removed after the
4321 * animation completes.
4322 */
4323 private void tabPicker(final boolean stay, final int index,
4324 final boolean remove) {
4325 if (mTabOverview != null) {
4326 return;
4327 }
4328
4329 int size = mTabControl.getTabCount();
4330
4331 TabListener l = null;
4332 if (stay) {
4333 l = mTabListener = new TabListener();
4334 }
4335 mTabOverview = new ImageGrid(this, stay, l);
4336
4337 for (int i = 0; i < size; i++) {
4338 final TabControl.Tab t = mTabControl.getTab(i);
4339 mTabControl.populatePickerData(t);
4340 mTabOverview.add(t);
4341 }
4342
4343 // Tell the tab overview to show the current tab, the tab overview will
4344 // handle the "New Tab" case.
4345 int currentIndex = mTabControl.getCurrentIndex();
4346 mTabOverview.setCurrentIndex(currentIndex);
4347
4348 // Attach the tab overview.
4349 mContentView.addView(mTabOverview, COVER_SCREEN_PARAMS);
4350
4351 // Create a fake AnimatingView to animate the WebView's picture.
4352 final TabControl.Tab current = mTabControl.getCurrentTab();
4353 final AnimatingView v = new AnimatingView(this, current);
4354 mContentView.addView(v, COVER_SCREEN_PARAMS);
4355 removeTabFromContentView(current);
4356 // Pause timers to get the animation smoother.
4357 current.getWebView().pauseTimers();
4358
4359 // Send a message so the tab picker has a chance to layout and get
4360 // positions for all the cells.
4361 mHandler.sendMessage(mHandler.obtainMessage(ANIMATE_TO_OVERVIEW,
4362 index, remove ? 1 : 0, v));
4363 // Setting this will indicate that we are animating to the overview. We
4364 // set it here to prevent another request to animate from coming in
4365 // between now and when ANIMATE_TO_OVERVIEW is handled.
4366 mAnimationCount++;
4367 // Always change the title bar to the window overview title while
4368 // animating.
4369 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, null);
4370 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null);
4371 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
4372 Window.PROGRESS_VISIBILITY_OFF);
4373 setTitle(R.string.tab_picker_title);
4374 // Make the menu empty until the animation completes.
4375 mMenuState = EMPTY_MENU;
4376 }
4377
4378 private void bookmarksOrHistoryPicker(boolean startWithHistory) {
4379 WebView current = mTabControl.getCurrentWebView();
4380 if (current == null) {
4381 return;
4382 }
4383 Intent intent = new Intent(this,
4384 CombinedBookmarkHistoryActivity.class);
4385 String title = current.getTitle();
4386 String url = current.getUrl();
4387 // Just in case the user opens bookmarks before a page finishes loading
4388 // so the current history item, and therefore the page, is null.
4389 if (null == url) {
4390 url = mLastEnteredUrl;
4391 // This can happen.
4392 if (null == url) {
4393 url = mSettings.getHomePage();
4394 }
4395 }
4396 // In case the web page has not yet received its associated title.
4397 if (title == null) {
4398 title = url;
4399 }
4400 intent.putExtra("title", title);
4401 intent.putExtra("url", url);
4402 intent.putExtra("maxTabsOpen",
4403 mTabControl.getTabCount() >= TabControl.MAX_TABS);
4404 if (startWithHistory) {
4405 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
4406 CombinedBookmarkHistoryActivity.HISTORY_TAB);
4407 }
4408 startActivityForResult(intent, COMBO_PAGE);
4409 }
4410
4411 // Called when loading from context menu or LOAD_URL message
4412 private void loadURL(WebView view, String url) {
4413 // In case the user enters nothing.
4414 if (url != null && url.length() != 0 && view != null) {
4415 url = smartUrlFilter(url);
4416 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
4417 view.loadUrl(url);
4418 }
4419 }
4420 }
4421
4422 private void checkMemory() {
4423 ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
4424 ((ActivityManager) getSystemService(ACTIVITY_SERVICE))
4425 .getMemoryInfo(mi);
4426 // FIXME: mi.lowMemory is too aggressive, use (mi.availMem <
4427 // mi.threshold) for now
4428 // if (mi.lowMemory) {
4429 if (mi.availMem < mi.threshold) {
4430 Log.w(LOGTAG, "Browser is freeing memory now because: available="
4431 + (mi.availMem / 1024) + "K threshold="
4432 + (mi.threshold / 1024) + "K");
4433 mTabControl.freeMemory();
4434 }
4435 }
4436
4437 private String smartUrlFilter(Uri inUri) {
4438 if (inUri != null) {
4439 return smartUrlFilter(inUri.toString());
4440 }
4441 return null;
4442 }
4443
4444
4445 // get window count
4446
4447 int getWindowCount(){
4448 if(mTabControl != null){
4449 return mTabControl.getTabCount();
4450 }
4451 return 0;
4452 }
4453
Feng Qianb34f87a2009-03-24 21:27:26 -07004454 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08004455 "(?i)" + // switch on case insensitive matching
4456 "(" + // begin group for schema
4457 "(?:http|https|file):\\/\\/" +
4458 "|(?:data|about|content|javascript):" +
4459 ")" +
4460 "(.*)" );
4461
4462 /**
4463 * Attempts to determine whether user input is a URL or search
4464 * terms. Anything with a space is passed to search.
4465 *
4466 * Converts to lowercase any mistakenly uppercased schema (i.e.,
4467 * "Http://" converts to "http://"
4468 *
4469 * @return Original or modified URL
4470 *
4471 */
4472 String smartUrlFilter(String url) {
4473
4474 String inUrl = url.trim();
4475 boolean hasSpace = inUrl.indexOf(' ') != -1;
4476
4477 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4478 if (matcher.matches()) {
4479 if (hasSpace) {
4480 inUrl = inUrl.replace(" ", "%20");
4481 }
4482 // force scheme to lowercase
4483 String scheme = matcher.group(1);
4484 String lcScheme = scheme.toLowerCase();
4485 if (!lcScheme.equals(scheme)) {
4486 return lcScheme + matcher.group(2);
4487 }
4488 return inUrl;
4489 }
4490 if (hasSpace) {
4491 // FIXME: quick search, need to be customized by setting
4492 if (inUrl.length() > 2 && inUrl.charAt(1) == ' ') {
4493 // FIXME: Is this the correct place to add to searches?
4494 // what if someone else calls this function?
4495 char char0 = inUrl.charAt(0);
4496
4497 if (char0 == 'g') {
4498 Browser.addSearchUrl(mResolver, inUrl);
4499 return composeSearchUrl(inUrl.substring(2));
4500
4501 } else if (char0 == 'w') {
4502 Browser.addSearchUrl(mResolver, inUrl);
4503 return URLUtil.composeSearchUrl(inUrl.substring(2),
4504 QuickSearch_W,
4505 QUERY_PLACE_HOLDER);
4506
4507 } else if (char0 == 'd') {
4508 Browser.addSearchUrl(mResolver, inUrl);
4509 return URLUtil.composeSearchUrl(inUrl.substring(2),
4510 QuickSearch_D,
4511 QUERY_PLACE_HOLDER);
4512
4513 } else if (char0 == 'l') {
4514 Browser.addSearchUrl(mResolver, inUrl);
4515 // FIXME: we need location in this case
4516 return URLUtil.composeSearchUrl(inUrl.substring(2),
4517 QuickSearch_L,
4518 QUERY_PLACE_HOLDER);
4519 }
4520 }
4521 } else {
4522 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4523 return URLUtil.guessUrl(inUrl);
4524 }
4525 }
4526
4527 Browser.addSearchUrl(mResolver, inUrl);
4528 return composeSearchUrl(inUrl);
4529 }
4530
4531 /* package */ String composeSearchUrl(String search) {
4532 return URLUtil.composeSearchUrl(search, QuickSearch_G,
4533 QUERY_PLACE_HOLDER);
4534 }
4535
4536 /* package */void setBaseSearchUrl(String url) {
4537 if (url == null || url.length() == 0) {
4538 /*
4539 * get the google search url based on the SIM. Default is US. NOTE:
4540 * This code uses resources to optionally select the search Uri,
4541 * based on the MCC value from the SIM. The default string will most
4542 * likely be fine. It is parameterized to accept info from the
4543 * Locale, the language code is the first parameter (%1$s) and the
4544 * country code is the second (%2$s). This code must function in the
4545 * same way as a similar lookup in
4546 * com.android.googlesearch.SuggestionProvider#onCreate(). If you
4547 * change either of these functions, change them both. (The same is
4548 * true for the underlying resource strings, which are stored in
4549 * mcc-specific xml files.)
4550 */
4551 Locale l = Locale.getDefault();
Bill Napiere9651c32009-05-05 13:16:30 -07004552 String language = l.getLanguage();
4553 String country = l.getCountry().toLowerCase();
4554 // Chinese and Portuguese have two langauge variants.
4555 if ("zh".equals(language)) {
4556 if ("cn".equals(country)) {
4557 language = "zh-CN";
4558 } else if ("tw".equals(country)) {
4559 language = "zh-TW";
4560 }
4561 } else if ("pt".equals(language)) {
4562 if ("br".equals(country)) {
4563 language = "pt-BR";
4564 } else if ("pt".equals(country)) {
4565 language = "pt-PT";
4566 }
4567 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08004568 QuickSearch_G = getResources().getString(
Bill Napiere9651c32009-05-05 13:16:30 -07004569 R.string.google_search_base,
4570 language,
4571 country)
The Android Open Source Project0c908882009-03-03 19:32:16 -08004572 + "client=ms-"
Ramanan Rajeswarandd4f4292009-03-24 20:41:19 -07004573 + Partner.getString(this.getContentResolver(), Partner.CLIENT_ID)
The Android Open Source Project0c908882009-03-03 19:32:16 -08004574 + "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&q=%s";
4575 } else {
4576 QuickSearch_G = url;
4577 }
4578 }
4579
4580 private final static int LOCK_ICON_UNSECURE = 0;
4581 private final static int LOCK_ICON_SECURE = 1;
4582 private final static int LOCK_ICON_MIXED = 2;
4583
4584 private int mLockIconType = LOCK_ICON_UNSECURE;
4585 private int mPrevLockType = LOCK_ICON_UNSECURE;
4586
4587 private BrowserSettings mSettings;
4588 private TabControl mTabControl;
4589 private ContentResolver mResolver;
4590 private FrameLayout mContentView;
4591 private ImageGrid mTabOverview;
4592
4593 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4594 // view, we should rewrite this.
4595 private int mCurrentMenuState = 0;
4596 private int mMenuState = R.id.MAIN_MENU;
4597 private static final int EMPTY_MENU = -1;
4598 private Menu mMenu;
4599
4600 private FindDialog mFindDialog;
4601 // Used to prevent chording to result in firing two shortcuts immediately
4602 // one after another. Fixes bug 1211714.
4603 boolean mCanChord;
4604
4605 private boolean mInLoad;
4606 private boolean mIsNetworkUp;
4607
4608 private boolean mPageStarted;
4609 private boolean mActivityInPause = true;
4610
4611 private boolean mMenuIsDown;
4612
4613 private final KeyTracker mKeyTracker = new KeyTracker(this);
4614
4615 // As trackball doesn't send repeat down, we have to track it ourselves
4616 private boolean mTrackTrackball;
4617
4618 private static boolean mInTrace;
4619
4620 // Performance probe
4621 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4622 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4623 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4624 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4625 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4626 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4627 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4628 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4629 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4630 };
4631
4632 private long mStart;
4633 private long mProcessStart;
4634 private long mUserStart;
4635 private long mSystemStart;
4636 private long mIdleStart;
4637 private long mIrqStart;
4638
4639 private long mUiStart;
4640
4641 private Drawable mMixLockIcon;
4642 private Drawable mSecLockIcon;
4643 private Drawable mGenericFavicon;
4644
4645 /* hold a ref so we can auto-cancel if necessary */
4646 private AlertDialog mAlertDialog;
4647
4648 // Wait for credentials before loading google.com
4649 private ProgressDialog mCredsDlg;
4650
4651 // The up-to-date URL and title (these can be different from those stored
4652 // in WebView, since it takes some time for the information in WebView to
4653 // get updated)
4654 private String mUrl;
4655 private String mTitle;
4656
4657 // As PageInfo has different style for landscape / portrait, we have
4658 // to re-open it when configuration changed
4659 private AlertDialog mPageInfoDialog;
4660 private TabControl.Tab mPageInfoView;
4661 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4662 // dialog, we should not just dismiss it, but should get back to the
4663 // SSL-certificate-on-error dialog. This flag is used to store this state
4664 private Boolean mPageInfoFromShowSSLCertificateOnError;
4665
4666 // as SSLCertificateOnError has different style for landscape / portrait,
4667 // we have to re-open it when configuration changed
4668 private AlertDialog mSSLCertificateOnErrorDialog;
4669 private WebView mSSLCertificateOnErrorView;
4670 private SslErrorHandler mSSLCertificateOnErrorHandler;
4671 private SslError mSSLCertificateOnErrorError;
4672
4673 // as SSLCertificate has different style for landscape / portrait, we
4674 // have to re-open it when configuration changed
4675 private AlertDialog mSSLCertificateDialog;
4676 private TabControl.Tab mSSLCertificateView;
4677
4678 // as HttpAuthentication has different style for landscape / portrait, we
4679 // have to re-open it when configuration changed
4680 private AlertDialog mHttpAuthenticationDialog;
4681 private HttpAuthHandler mHttpAuthHandler;
4682
4683 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4684 new FrameLayout.LayoutParams(
4685 ViewGroup.LayoutParams.FILL_PARENT,
4686 ViewGroup.LayoutParams.FILL_PARENT);
4687 // We may provide UI to customize these
4688 // Google search from the browser
4689 static String QuickSearch_G;
4690 // Wikipedia search
4691 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4692 // Dictionary search
4693 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4694 // Google Mobile Local search
4695 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4696
4697 final static String QUERY_PLACE_HOLDER = "%s";
4698
4699 // "source" parameter for Google search through search key
4700 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4701 // "source" parameter for Google search through goto menu
4702 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4703 // "source" parameter for Google search through simplily type
4704 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4705 // "source" parameter for Google search suggested by the browser
4706 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4707 // "source" parameter for Google search from unknown source
4708 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4709
4710 private final static String LOGTAG = "browser";
4711
4712 private TabListener mTabListener;
4713
4714 private String mLastEnteredUrl;
4715
4716 private PowerManager.WakeLock mWakeLock;
4717 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4718
4719 private Toast mStopToast;
4720
4721 // Used during animations to prevent other animations from being triggered.
4722 // A count is used since the animation to and from the Window overview can
4723 // overlap. A count of 0 means no animation where a count of > 0 means
4724 // there are animations in progress.
4725 private int mAnimationCount;
4726
4727 // As the ids are dynamically created, we can't guarantee that they will
4728 // be in sequence, so this static array maps ids to a window number.
4729 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4730 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4731 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4732 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4733
4734 // monitor platform changes
4735 private IntentFilter mNetworkStateChangedFilter;
4736 private BroadcastReceiver mNetworkStateIntentReceiver;
4737
4738 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004739 final static int COMBO_PAGE = 1;
4740 final static int DOWNLOAD_PAGE = 2;
4741 final static int PREFERENCES_PAGE = 3;
4742 final static int WEBSTORAGE_QUOTA_DIALOG = 4;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004743
4744 // the frenquency of checking whether system memory is low
4745 final static int CHECK_MEMORY_INTERVAL = 30000; // 30 seconds
4746}