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