OSDN Git Service

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