OSDN Git Service

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