OSDN Git Service

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