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