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