OSDN Git Service

Only show the host portion of the url in the title bar.
[android-x86/packages-apps-Browser.git] / src / com / android / browser / BrowserActivity.java
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
17 package com.android.browser;
18
19 import com.google.android.googleapps.IGoogleLoginService;
20 import com.google.android.googlelogin.GoogleLoginServiceConstants;
21 import com.google.android.providers.GoogleSettings.Partner;
22
23 import android.app.Activity;
24 import android.app.ActivityManager;
25 import android.app.AlertDialog;
26 import android.app.ProgressDialog;
27 import android.app.SearchManager;
28 import android.content.ActivityNotFoundException;
29 import android.content.BroadcastReceiver;
30 import android.content.ComponentName;
31 import android.content.ContentResolver;
32 import android.content.ContentValues;
33 import android.content.Context;
34 import android.content.DialogInterface;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.ServiceConnection;
38 import android.content.DialogInterface.OnCancelListener;
39 import android.content.pm.PackageInfo;
40 import android.content.pm.PackageManager;
41 import android.content.pm.ResolveInfo;
42 import android.content.res.AssetManager;
43 import android.content.res.Configuration;
44 import android.content.res.Resources;
45 import android.database.Cursor;
46 import android.database.sqlite.SQLiteDatabase;
47 import android.database.sqlite.SQLiteException;
48 import android.graphics.Bitmap;
49 import android.graphics.Canvas;
50 import android.graphics.Color;
51 import android.graphics.DrawFilter;
52 import android.graphics.Paint;
53 import android.graphics.PaintFlagsDrawFilter;
54 import android.graphics.Picture;
55 import android.graphics.drawable.BitmapDrawable;
56 import android.graphics.drawable.Drawable;
57 import android.graphics.drawable.LayerDrawable;
58 import android.graphics.drawable.PaintDrawable;
59 import android.hardware.SensorListener;
60 import android.hardware.SensorManager;
61 import android.location.Location;
62 import android.location.LocationManager;
63 import android.net.ConnectivityManager;
64 import android.net.Uri;
65 import android.net.WebAddress;
66 import android.net.http.EventHandler;
67 import android.net.http.SslCertificate;
68 import android.net.http.SslError;
69 import android.os.AsyncTask;
70 import android.os.Bundle;
71 import android.os.Debug;
72 import android.os.Environment;
73 import android.os.Handler;
74 import android.os.IBinder;
75 import android.os.Message;
76 import android.os.PowerManager;
77 import android.os.Process;
78 import android.os.RemoteException;
79 import android.os.ServiceManager;
80 import android.os.SystemClock;
81 import android.os.SystemProperties;
82 import android.preference.PreferenceManager;
83 import android.provider.Browser;
84 import android.provider.Contacts;
85 import android.provider.Downloads;
86 import android.provider.MediaStore;
87 import android.provider.Settings;
88 import android.provider.Contacts.Intents.Insert;
89 import android.text.IClipboard;
90 import android.text.TextUtils;
91 import android.text.format.DateFormat;
92 import android.text.util.Regex;
93 import android.util.Log;
94 import android.view.ContextMenu;
95 import android.view.Gravity;
96 import android.view.KeyEvent;
97 import android.view.LayoutInflater;
98 import android.view.Menu;
99 import android.view.MenuInflater;
100 import android.view.MenuItem;
101 import android.view.View;
102 import android.view.ViewGroup;
103 import android.view.Window;
104 import android.view.WindowManager;
105 import android.view.ContextMenu.ContextMenuInfo;
106 import android.view.MenuItem.OnMenuItemClickListener;
107 import android.view.animation.AlphaAnimation;
108 import android.view.animation.Animation;
109 import android.view.animation.AnimationSet;
110 import android.view.animation.DecelerateInterpolator;
111 import android.view.animation.ScaleAnimation;
112 import android.view.animation.TranslateAnimation;
113 import android.webkit.CookieManager;
114 import android.webkit.CookieSyncManager;
115 import android.webkit.DownloadListener;
116 import android.webkit.HttpAuthHandler;
117 import android.webkit.PluginManager;
118 import android.webkit.SslErrorHandler;
119 import android.webkit.URLUtil;
120 import android.webkit.WebChromeClient;
121 import android.webkit.WebHistoryItem;
122 import android.webkit.WebIconDatabase;
123 import android.webkit.WebStorage;
124 import android.webkit.WebView;
125 import android.webkit.WebViewClient;
126 import android.widget.EditText;
127 import android.widget.FrameLayout;
128 import android.widget.LinearLayout;
129 import android.widget.TextView;
130 import android.widget.Toast;
131
132 import java.io.BufferedOutputStream;
133 import java.io.File;
134 import java.io.FileInputStream;
135 import java.io.FileOutputStream;
136 import java.io.IOException;
137 import java.io.InputStream;
138 import java.net.MalformedURLException;
139 import java.net.URI;
140 import java.net.URL;
141 import java.net.URLEncoder;
142 import java.text.ParseException;
143 import java.util.Date;
144 import java.util.Enumeration;
145 import java.util.HashMap;
146 import java.util.LinkedList;
147 import java.util.Locale;
148 import java.util.Vector;
149 import java.util.regex.Matcher;
150 import java.util.regex.Pattern;
151 import java.util.zip.ZipEntry;
152 import java.util.zip.ZipFile;
153
154 public 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 }