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