Merge "This should fix the build."
diff --git a/res/menu/browser.xml b/res/menu/browser.xml
index 2752c3a..4793c21 100644
--- a/res/menu/browser.xml
+++ b/res/menu/browser.xml
@@ -61,6 +61,9 @@
         <item android:id="@+id/dump_nav_menu_id"
             android:title="@string/dump_nav"
             android:visible="false" />
+        <item android:id="@+id/dump_counters_menu_id"
+            android:title="@string/dump_counters"
+            android:visible="false" />
     </group>
     <group android:id="@+id/MAIN_SHORTCUT_MENU" android:visible="false">
         <item android:id="@+id/homepage_menu_id"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d3514cd..20ccb48 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -662,6 +662,8 @@
     <string name="activity_instrumentation_functional_test_runner" translatable="false">Browser Functional Test Runner</string>
     <!-- Do not translate.  Testing only -->
     <string name="dump_nav" translatable="false">Dump navigation cache</string>
+    <!-- Do not translate.  Testing only -->
+    <string name="dump_counters" translatable="false">Dump V8 counters</string>
 
     <!-- The default homepage. If it starts with "http://www.google" and the
             user signs up the device with a Google sites account, the site's
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 6167b9a..8d471a2 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -1538,6 +1538,10 @@
                 getTopWindow().debugDump();
                 break;
 
+            case R.id.dump_counters_menu_id:
+                getTopWindow().dumpV8Counters();
+                break;
+
             case R.id.zoom_in_menu_id:
                 getTopWindow().zoomIn();
                 break;
@@ -1642,6 +1646,12 @@
                 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
                 nav.setVisible(isNavDump);
                 nav.setEnabled(isNavDump);
+
+                boolean showDebugSettings = mSettings.showDebugSettings();
+                final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id);
+                counter.setVisible(showDebugSettings);
+                counter.setEnabled(showDebugSettings);
+
                 break;
         }
         mCurrentMenuState = mMenuState;
diff --git a/src/com/android/browser/LogTag.java b/src/com/android/browser/LogTag.java
index 2be3134..38fea47 100644
--- a/src/com/android/browser/LogTag.java
+++ b/src/com/android/browser/LogTag.java
@@ -16,7 +16,6 @@
 
 package com.android.browser;
 
-import android.util.Log;
 import android.util.EventLog;
 
 public class LogTag {
@@ -36,11 +35,14 @@
      * Log when a page has finished loading with how much
      * time the browser used to load the page.
      *
+     * Note that a redirect will restart the timer, so this time is not
+     * always how long it takes for the user to load a page.
+     *
      * @param url the url of that page that finished loading.
      * @param duration the time the browser spent loading the page.
      */
     public static void logPageFinishedLoading(String url, long duration) {
-        EventLog.writeEvent(EventLogTags.BROWSER_BOOKMARK_ADDED, url + "|"
+        EventLog.writeEvent(EventLogTags.BROWSER_PAGE_LOADED, url + "|"
             + duration);
     }
 
@@ -51,7 +53,7 @@
      * @param duration the time spent on the webpage.
      */
     public static void logTimeOnPage(String url, long duration) {
-        EventLog.writeEvent(EventLogTags.BROWSER_BOOKMARK_ADDED, url + "|"
+        EventLog.writeEvent(EventLogTags.BROWSER_TIMEONPAGE, url + "|"
             + duration);
     }
 }
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 5db4848..b434a5d 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -37,6 +37,7 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Message;
+import android.os.SystemClock;
 import android.provider.Browser;
 import android.speech.RecognizerResultsIntent;
 import android.util.Log;
@@ -97,6 +98,8 @@
     private boolean mInForeground;
     // If true, the tab is in loading state.
     private boolean mInLoad;
+    // The time the load started, used to find load page time
+    private long mLoadStartTime;
     // Application identifier used to find tabs that another application wants
     // to reuse.
     private String mAppId;
@@ -170,47 +173,86 @@
      *      results when reusing the old results.
      */
     /* package */ void activateVoiceSearchMode(Intent intent) {
+        int index = 0;
         ArrayList<String> results = intent.getStringArrayListExtra(
                     RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_STRINGS);
-        ArrayList<String> urls = intent.getStringArrayListExtra(
-                    RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_URLS);
         if (results != null) {
+            ArrayList<String> urls = intent.getStringArrayListExtra(
+                        RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_URLS);
+            ArrayList<String> htmls = intent.getStringArrayListExtra(
+                        RecognizerResultsIntent.EXTRA_VOICE_SEARCH_RESULT_HTML);
+            ArrayList<String> baseUrls = intent.getStringArrayListExtra(
+                        RecognizerResultsIntent
+                        .EXTRA_VOICE_SEARCH_RESULT_HTML_BASE_URLS);
             // This tab is now entering voice search mode for the first time, or
             // a new voice search was done.
-            if (urls == null || results.size() != urls.size()) {
+            int size = results.size();
+            if (urls == null || size != urls.size()) {
                 throw new AssertionError("improper extras passed in Intent");
             }
-            mVoiceSearchData = new VoiceSearchData(results, urls);
+            if (htmls == null || htmls.size() != size || baseUrls == null ||
+                    (baseUrls.size() != size && baseUrls.size() != 1)) {
+                // If either of these arrays are empty/incorrectly sized, ignore
+                // them.
+                htmls = null;
+                baseUrls = null;
+            }
+            mVoiceSearchData = new VoiceSearchData(results, urls, htmls,
+                    baseUrls);
         } else {
             String extraData = intent.getStringExtra(
                     SearchManager.EXTRA_DATA_KEY);
             if (extraData != null) {
-                mVoiceSearchData.mLastVoiceSearchIndex
-                        = Integer.parseInt(extraData);
-                if (mVoiceSearchData.mLastVoiceSearchIndex
-                        >= mVoiceSearchData.mVoiceSearchResults.size()) {
+                index = Integer.parseInt(extraData);
+                if (index >= mVoiceSearchData.mVoiceSearchResults.size()) {
                     throw new AssertionError("index must be less than "
                             + " size of mVoiceSearchResults");
                 }
             }
         }
         mVoiceSearchData.mLastVoiceSearchTitle
-                = mVoiceSearchData.mVoiceSearchResults.get(mVoiceSearchData.
-                mLastVoiceSearchIndex);
+                = mVoiceSearchData.mVoiceSearchResults.get(index);
         if (mInForeground) {
             mActivity.showVoiceTitleBar(mVoiceSearchData.mLastVoiceSearchTitle);
         }
+        if (mVoiceSearchData.mVoiceSearchHtmls != null) {
+            // When index was found it was already ensured that it was valid
+            String uriString = mVoiceSearchData.mVoiceSearchHtmls.get(index);
+            if (uriString != null) {
+                Uri dataUri = Uri.parse(uriString);
+                if (RecognizerResultsIntent.URI_SCHEME_INLINE.equals(
+                        dataUri.getScheme())) {
+                    // If there is only one base URL, use it.  If there are
+                    // more, there will be one for each index, so use the base
+                    // URL corresponding to the index.
+                    String baseUrl = mVoiceSearchData.mVoiceSearchBaseUrls.get(
+                            mVoiceSearchData.mVoiceSearchBaseUrls.size() > 1 ?
+                            index : 0);
+                    mVoiceSearchData.mLastVoiceSearchUrl = baseUrl;
+                    mMainView.loadDataWithBaseURL(baseUrl,
+                            uriString.substring(RecognizerResultsIntent
+                            .URI_SCHEME_INLINE.length() + 1), "text/html",
+                            "utf-8", baseUrl);
+                    return;
+                }
+            }
+        }
         mVoiceSearchData.mLastVoiceSearchUrl
-                = mVoiceSearchData.mVoiceSearchUrls.get(mVoiceSearchData.
-                mLastVoiceSearchIndex);
+                = mVoiceSearchData.mVoiceSearchUrls.get(index);
+        if (null == mVoiceSearchData.mLastVoiceSearchUrl) {
+            mVoiceSearchData.mLastVoiceSearchUrl = mActivity.smartUrlFilter(
+                    mVoiceSearchData.mLastVoiceSearchTitle);
+        }
         mMainView.loadUrl(mVoiceSearchData.mLastVoiceSearchUrl);
     }
     /* package */ static class VoiceSearchData {
         public VoiceSearchData(ArrayList<String> results,
-                ArrayList<String> urls) {
+                ArrayList<String> urls, ArrayList<String> htmls,
+                ArrayList<String> baseUrls) {
             mVoiceSearchResults = results;
             mVoiceSearchUrls = urls;
-            mLastVoiceSearchIndex = 0;
+            mVoiceSearchHtmls = htmls;
+            mVoiceSearchBaseUrls = baseUrls;
         }
         /*
          * ArrayList of suggestions to be displayed when opening the
@@ -223,9 +265,20 @@
          */
         public ArrayList<String> mVoiceSearchUrls;
         /*
+         * ArrayList holding content to load for each item in
+         * mVoiceSearchResults.
+         */
+        public ArrayList<String> mVoiceSearchHtmls;
+        /*
+         * ArrayList holding base urls for the items in mVoiceSearchResults.
+         * If non null, this will either have the same size as
+         * mVoiceSearchResults or have a size of 1, in which case all will use
+         * the same base url
+         */
+        public ArrayList<String> mVoiceSearchBaseUrls;
+        /*
          * The last url provided by voice search.  Used for comparison to see if
-         * we are going to a page by some method besides voice search.  Only
-         * meaningful in voice search mode.
+         * we are going to a page by some method besides voice search.
          */
         public String mLastVoiceSearchUrl;
         /**
@@ -233,12 +286,6 @@
          * when switching tabs.
          */
         public String mLastVoiceSearchTitle;
-        /*
-         * The index into mVoiceSearchResults and mVoiceSearchUrls of the last
-         * voice search performed. Stored so it can be used to index into
-         * mVoiceSearchUrls to determine the url in getUrlDataFromIntent.
-         */
-        public int mLastVoiceSearchIndex;
     }
 
     // Container class for the next error dialog that needs to be displayed
@@ -317,6 +364,7 @@
         @Override
         public void onPageStarted(WebView view, String url, Bitmap favicon) {
             mInLoad = true;
+            mLoadStartTime = SystemClock.uptimeMillis();
             if (mVoiceSearchData != null
                     && !url.equals(mVoiceSearchData.mLastVoiceSearchUrl)) {
                 mVoiceSearchData = null;
@@ -370,6 +418,8 @@
 
         @Override
         public void onPageFinished(WebView view, String url) {
+            LogTag.logPageFinishedLoading(
+                    url, SystemClock.uptimeMillis() - mLoadStartTime);
             mInLoad = false;
 
             if (mInForeground && !mActivity.didUserStopLoading()