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