Enable voice search

       http://b/issue?id=3039695
       send voice search intent
       implement voice search title bar
       also fixes another Urlbar focus handling bug

Change-Id: Icdcbec9ffff586d445545cc7fe86ca045022f381
diff --git a/res/layout/url_bar.xml b/res/layout/url_bar.xml
index 61b03ed..884267e 100644
--- a/res/layout/url_bar.xml
+++ b/res/layout/url_bar.xml
@@ -48,7 +48,14 @@
             android:layout_weight="1.0"
             android:layout_marginLeft="16dip"
             android:orientation="horizontal"
-            android:background="@drawable/textfield_active_holo_dark">
+            android:background="@drawable/textfield_default_holo_dark">
+            <ImageView
+                android:id="@+id/voice_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:src="@drawable/ic_search_light"
+                style="@style/HoloIcon"
+                android:visibility="gone" />
             <ImageView
                 android:id="@+id/lock"
                 android:layout_width="wrap_content"
diff --git a/src/com/android/browser/BaseUi.java b/src/com/android/browser/BaseUi.java
index 6b8b447..28a144d 100644
--- a/src/com/android/browser/BaseUi.java
+++ b/src/com/android/browser/BaseUi.java
@@ -249,6 +249,10 @@
         tab.getTopWindow().requestFocus();
     }
 
+    Tab getActiveTab() {
+        return mActiveTab;
+    }
+
     @Override
     public void updateTabs(List<Tab> tabs) {
     }
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index e88a676..d49a778 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -36,7 +36,6 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Picture;
@@ -51,10 +50,10 @@
 import android.preference.PreferenceActivity;
 import android.provider.Browser;
 import android.provider.BrowserContract;
-import android.provider.BrowserContract.History;
 import android.provider.BrowserContract.Images;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Intents.Insert;
+import android.speech.RecognizerIntent;
 import android.speech.RecognizerResultsIntent;
 import android.text.TextUtils;
 import android.util.Log;
@@ -94,6 +93,9 @@
         implements WebViewController, UiController {
 
     private static final String LOGTAG = "Controller";
+    private static final String SEND_APP_ID_EXTRA =
+        "android.speech.extras.SEND_APPLICATION_ID_EXTRA";
+
 
     // public message ids
     public final static int LOAD_URL = 1001;
@@ -1009,6 +1011,16 @@
                 null, false);
     }
 
+    public void startVoiceSearch() {
+        Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+                RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
+        intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
+                mActivity.getComponentName().flattenToString());
+        intent.putExtra(SEND_APP_ID_EXTRA, false);
+        mActivity.startActivity(intent);
+    }
+
     public void activateVoiceSearchMode(String title) {
         mUi.showVoiceTitleBar(title);
     }
diff --git a/src/com/android/browser/SuggestionsAdapter.java b/src/com/android/browser/SuggestionsAdapter.java
index 6473dfd..4c0217c 100644
--- a/src/com/android/browser/SuggestionsAdapter.java
+++ b/src/com/android/browser/SuggestionsAdapter.java
@@ -23,7 +23,6 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Handler;
 import android.provider.BrowserContract;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
@@ -69,6 +68,7 @@
     int mLinesPortrait;
     int mLinesLandscape;
     Object mResultsLock = new Object();
+    List<String> mVoiceResults;
 
     interface CompletionListener {
 
@@ -92,6 +92,12 @@
         addSource(new CombinedCursor());
     }
 
+    void setVoiceResults(List<String> voiceResults) {
+        mVoiceResults = voiceResults;
+        notifyDataSetInvalidated();
+
+    }
+
     public void setLandscapeMode(boolean mode) {
         mLandscapeMode = mode;
         notifyDataSetChanged();
@@ -132,11 +138,18 @@
 
     @Override
     public int getCount() {
+        if (mVoiceResults != null) {
+            return mVoiceResults.size();
+        }
         return (mMixedResults == null) ? 0 : mMixedResults.getLineCount();
     }
 
     @Override
     public SuggestItem getItem(int position) {
+        if (mVoiceResults != null) {
+            return new SuggestItem(mVoiceResults.get(position), null,
+                    TYPE_SEARCH);
+        }
         if (mMixedResults == null) {
             return null;
         }
@@ -176,7 +189,7 @@
         View s1 = view.findViewById(R.id.suggest1);
         View s2 = view.findViewById(R.id.suggest2);
         View div = view.findViewById(R.id.suggestion_divider);
-        if (mLandscapeMode) {
+        if (mLandscapeMode  && (mVoiceResults == null)) {
             SuggestItem item = getItem(position);
             div.setVisibility(View.VISIBLE);
             if (item != null) {
@@ -310,25 +323,30 @@
         @Override
         protected FilterResults performFiltering(CharSequence constraint) {
             FilterResults res = new FilterResults();
-            if (TextUtils.isEmpty(constraint)) {
-                res.count = 0;
-                res.values = null;
-                return res;
-            }
-            startSuggestionsAsync(constraint);
-            List<SuggestItem> filterResults = new ArrayList<SuggestItem>();
-            if (constraint != null) {
-                for (CursorSource sc : mSources) {
-                    sc.runQuery(constraint);
+            if (mVoiceResults == null) {
+                if (TextUtils.isEmpty(constraint)) {
+                    res.count = 0;
+                    res.values = null;
+                    return res;
                 }
-                mixResults(filterResults);
+                startSuggestionsAsync(constraint);
+                List<SuggestItem> filterResults = new ArrayList<SuggestItem>();
+                if (constraint != null) {
+                    for (CursorSource sc : mSources) {
+                        sc.runQuery(constraint);
+                    }
+                    mixResults(filterResults);
+                }
+                synchronized (mResultsLock) {
+                    mFilterResults = filterResults;
+                }
+                SuggestionResults mixed = buildSuggestionResults();
+                res.count = mixed.getLineCount();
+                res.values = mixed;
+            } else {
+                res.count = mVoiceResults.size();
+                res.values = mVoiceResults;
             }
-            synchronized (mResultsLock) {
-                mFilterResults = filterResults;
-            }
-            SuggestionResults mixed = buildSuggestionResults();
-            res.count = mixed.getLineCount();
-            res.values = mixed;
             return res;
         }
 
@@ -348,8 +366,10 @@
 
         @Override
         protected void publishResults(CharSequence constraint, FilterResults fresults) {
-            mMixedResults = (SuggestionResults) fresults.values;
-            mListener.onFilterComplete(fresults.count);
+            if (fresults.values instanceof SuggestionResults) {
+                mMixedResults = (SuggestionResults) fresults.values;
+                mListener.onFilterComplete(fresults.count);
+            }
             notifyDataSetChanged();
         }
 
diff --git a/src/com/android/browser/TitleBarXLarge.java b/src/com/android/browser/TitleBarXLarge.java
index 5890810..b680512 100644
--- a/src/com/android/browser/TitleBarXLarge.java
+++ b/src/com/android/browser/TitleBarXLarge.java
@@ -36,6 +36,8 @@
 import android.webkit.WebView;
 import android.widget.ImageView;
 
+import java.util.List;
+
 /**
  * tabbed title bar for xlarge screen browser
  */
@@ -62,10 +64,15 @@
     private View mAllButton;
     private View mClearButton;
     private View mVoiceSearch;
+    private View mVoiceSearchIndicator;
     private PageProgressView mProgressView;
     private UrlInputView mUrlInput;
+    private Drawable mFocusDrawable;
+    private Drawable mUnfocusDrawable;
+    private boolean mInVoiceMode;
 
     private boolean mInLoad;
+    private boolean mEditable;
 
     public TitleBarXLarge(Activity activity, UiController controller,
             XLargeUi ui) {
@@ -75,7 +82,12 @@
         Resources resources = activity.getResources();
         mStopDrawable = resources.getDrawable(R.drawable.ic_stop_normal);
         mReloadDrawable = resources.getDrawable(R.drawable.ic_refresh_normal);
+        mFocusDrawable = resources.getDrawable(
+                R.drawable.textfield_active_holo_dark);
+        mUnfocusDrawable = resources.getDrawable(
+                R.drawable.textfield_default_holo_dark);
         rebuildLayout(activity, true);
+        mInVoiceMode = false;
     }
 
     private void rebuildLayout(Context context, boolean rebuildData) {
@@ -98,7 +110,7 @@
         mVoiceSearch = findViewById(R.id.voicesearch);
         mProgressView = (PageProgressView) findViewById(R.id.progress);
         mUrlContainer = findViewById(R.id.urlbar_focused);
-
+        mVoiceSearchIndicator = findViewById(R.id.voice_icon);
         mBackButton.setOnClickListener(this);
         mForwardButton.setOnClickListener(this);
         mStar.setOnClickListener(this);
@@ -107,6 +119,7 @@
         mSearchButton.setOnClickListener(this);
         mGoButton.setOnClickListener(this);
         mClearButton.setOnClickListener(this);
+        mVoiceSearch.setOnClickListener(this);
         mUrlContainer.setOnClickListener(this);
         mUrlInput.setUrlInputListener(this);
         mUrlInput.setContainer(mUrlContainer);
@@ -117,9 +130,25 @@
         setUrlMode(false);
     }
 
+    public void setEditable(boolean editable) {
+        mEditable = editable;
+        mUrlInput.setFocusable(mEditable);
+        if (!mEditable) {
+            mUrlInput.setOnClickListener(this);
+        } else {
+            mUrlContainer.setOnClickListener(null);
+        }
+    }
+
     @Override
     public void onFocusChange(View view, boolean hasFocus) {
-        setUrlMode(hasFocus);
+        if (!mEditable && hasFocus) {
+            mUi.editUrl(false);
+        } else {
+            setUrlMode(hasFocus);
+        }
+        mUrlContainer.setBackgroundDrawable(hasFocus
+                ? mFocusDrawable : mUnfocusDrawable);
     }
 
     public void setCurrentUrlIsBookmark(boolean isBookmark) {
@@ -133,9 +162,13 @@
      * @param clearInput clear the input field
      */
     void onEditUrl(boolean clearInput) {
-        mUrlInput.requestFocusFromTouch();
+        if (!mUrlInput.hasFocus()) {
+            mUrlInput.requestFocus();
+        }
         if (clearInput) {
             mUrlInput.setText("");
+        } else if (mInVoiceMode) {
+            mUrlInput.showDropDown();
         }
     }
 
@@ -146,6 +179,8 @@
     @Override
     public void onClick(View v) {
         if (mUrlInput == v) {
+            mUi.editUrl(false);
+        } else if (mUrlContainer == v) {
             if (!mUrlInput.hasFocus()) {
                 mUi.editUrl(false);
             }
@@ -169,6 +204,8 @@
             }
         } else if (mClearButton == v) {
             clearOrClose();
+        } else if (mVoiceSearch == v) {
+            mUiController.startVoiceSearch();
         }
     }
 
@@ -224,7 +261,7 @@
         mUi.hideFakeTitleBar();
         setUrlMode(false);
         // if top != null current must be set
-        if (top != null) {
+        if ((top != null) && !mInVoiceMode) {
             setDisplayTitle(mUiController.getCurrentWebView().getUrl());
         }
     }
@@ -248,6 +285,9 @@
             mSearchButton.setVisibility(View.GONE);
             mStar.setVisibility(View.GONE);
             mClearButton.setVisibility(View.VISIBLE);
+            if (mInVoiceMode) {
+                mVoiceSearchIndicator.setVisibility(View.VISIBLE);
+            }
             updateSearchMode();
         } else {
             mUrlInput.clearFocus();
@@ -256,6 +296,7 @@
             mVoiceSearch.setVisibility(View.GONE);
             mStar.setVisibility(View.VISIBLE);
             mClearButton.setVisibility(View.GONE);
+            mVoiceSearchIndicator.setVisibility(View.GONE);
         }
     }
 
@@ -315,6 +356,8 @@
         if (mUrlInput.hasFocus()) {
             // check if input field is empty and adjust voice search state
             updateSearchMode();
+            // clear voice mode when user types
+            setInVoiceMode(false, null);
         }
     }
 
@@ -326,4 +369,18 @@
     public void onTextChanged(CharSequence s, int start, int before, int count) {
     }
 
+    // voicesearch
+
+    @Override
+    public void setInVoiceMode(boolean voicemode) {
+        setInVoiceMode(voicemode, null);
+    }
+
+    public void setInVoiceMode(boolean voicemode, List<String> voiceResults) {
+        mInVoiceMode = voicemode;
+        mUrlInput.setVoiceResults(voiceResults);
+        mVoiceSearchIndicator.setVisibility(mInVoiceMode
+                ? View.VISIBLE : View.GONE);
+    }
+
 }
diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java
index c74d74e..68a32d3 100644
--- a/src/com/android/browser/UiController.java
+++ b/src/com/android/browser/UiController.java
@@ -53,6 +53,8 @@
 
     void bookmarksOrHistoryPicker(boolean openHistory);
 
+    void startVoiceSearch();
+
     void showVoiceSearchResults(String title);
 
     void editUrl();
diff --git a/src/com/android/browser/UrlInputView.java b/src/com/android/browser/UrlInputView.java
index bd47f6a..505662d 100644
--- a/src/com/android/browser/UrlInputView.java
+++ b/src/com/android/browser/UrlInputView.java
@@ -30,6 +30,8 @@
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
+import java.util.List;
+
 /**
  * url/search input view
  * handling suggestions
@@ -48,6 +50,7 @@
     private OnFocusChangeListener mWrappedFocusListener;
     private View mContainer;
     private boolean mLandscape;
+    private boolean mInVoiceMode;
 
     public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
@@ -85,6 +88,11 @@
         mContainer = container;
     }
 
+    void setVoiceResults(List<String> voiceResults) {
+        mAdapter.setVoiceResults(voiceResults);
+        mInVoiceMode = (voiceResults != null);
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration config) {
         super.onConfigurationChanged(config);
@@ -134,6 +142,10 @@
     public void onFocusChange(View v, boolean hasFocus) {
         if (hasFocus) {
             forceIme();
+            if (mInVoiceMode) {
+                performFiltering(getText().toString(), 0);
+                showDropDown();
+            }
         } else {
             finishInput(null, null, null);
         }
diff --git a/src/com/android/browser/XLargeUi.java b/src/com/android/browser/XLargeUi.java
index 30180aa..69e6724 100644
--- a/src/com/android/browser/XLargeUi.java
+++ b/src/com/android/browser/XLargeUi.java
@@ -46,7 +46,9 @@
         super(browser, controller);
         mTitleBar = new TitleBarXLarge(mActivity, mUiController, this);
         mTitleBar.setProgress(100);
+        mTitleBar.setEditable(false);
         mFakeTitleBar = new TitleBarXLarge(mActivity, mUiController, this);
+        mFakeTitleBar.setEditable(true);
         ActionBar actionBar = mActivity.getActionBar();
         mTabBar = new TabBar(mActivity, mUiController, this);
         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
@@ -218,4 +220,26 @@
         mTabBar.onFavicon(tab, icon);
     }
 
+    @Override
+    public void showVoiceTitleBar(String title) {
+        List<String> vsresults = null;
+        if (getActiveTab() != null) {
+            vsresults = getActiveTab().getVoiceSearchResults();
+        }
+        mTitleBar.setInVoiceMode(true, null);
+        mTitleBar.setDisplayTitle(title);
+        mFakeTitleBar.setInVoiceMode(true, vsresults);
+        mFakeTitleBar.setDisplayTitle(title);
+    }
+
+    @Override
+    public void revertVoiceTitleBar(Tab tab) {
+        mTitleBar.setInVoiceMode(false, null);
+        String url = tab.getCurrentUrl();
+        mTitleBar.setDisplayTitle(url);
+        mFakeTitleBar.setInVoiceMode(false, null);
+        mFakeTitleBar.setDisplayTitle(url);
+    }
+
+
 }