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