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