Handle the voice search intent.
Once the voice search intent has been handled, the title bar
background changes to green, and touching it displays other voice
search possibilities.
Fixes http://b/issue?id=2390686
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 90bacad..3f7c9e9 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -23,6 +23,8 @@
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
@@ -381,9 +383,11 @@
// the tab will be close when exit.
UrlData urlData = getUrlDataFromIntent(intent);
+ String action = intent.getAction();
final Tab t = mTabControl.createNewTab(
- Intent.ACTION_VIEW.equals(intent.getAction()) &&
- intent.getData() != null,
+ (Intent.ACTION_VIEW.equals(action) &&
+ intent.getData() != null)
+ || Tab.VoiceSearchData.VOICE_SEARCH_RESULTS.equals(action),
intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
mTabControl.setCurrentTab(t);
attachTabToContentView(t);
@@ -410,7 +414,7 @@
waitForCredentials();
}
} else {
- urlData.loadIn(webView);
+ urlData.loadIn(t);
}
} else {
// TabControl.restoreState() will create a new tab even if
@@ -425,6 +429,24 @@
}
}
+ /**
+ * Feed the previously stored results strings to the BrowserProvider so that
+ * the SearchDialog will show them instead of the standard searches.
+ * @param result String to show on the editable line of the SearchDialog.
+ */
+ /* package */ void showVoiceSearchResults(String result) {
+ ContentProviderClient client = mResolver.acquireContentProviderClient(
+ Browser.BOOKMARKS_URI);
+ ContentProvider prov = client.getLocalContentProvider();
+ BrowserProvider bp = (BrowserProvider) prov;
+ bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults());
+ client.release();
+
+ startSearch(result, false,
+ createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY),
+ false);
+ }
+
@Override
protected void onNewIntent(Intent intent) {
Tab current = mTabControl.getCurrentTab();
@@ -448,10 +470,13 @@
// just resume the browser
return;
}
+ boolean activateVoiceSearch = Tab.VoiceSearchData.VOICE_SEARCH_RESULTS
+ .equals(action);
if (Intent.ACTION_VIEW.equals(action)
|| Intent.ACTION_SEARCH.equals(action)
|| MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
- || Intent.ACTION_WEB_SEARCH.equals(action)) {
+ || Intent.ACTION_WEB_SEARCH.equals(action)
+ || activateVoiceSearch) {
// If this was a search request (e.g. search query directly typed into the address bar),
// pass it on to the default web search provider.
if (handleWebSearchIntent(intent)) {
@@ -465,7 +490,7 @@
final String appId = intent
.getStringExtra(Browser.EXTRA_APPLICATION_ID);
- if (Intent.ACTION_VIEW.equals(action)
+ if ((Intent.ACTION_VIEW.equals(action) || activateVoiceSearch)
&& !getPackageName().equals(appId)
&& (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
Tab appTab = mTabControl.getTabFromId(appId);
@@ -485,14 +510,14 @@
if (current != appTab) {
switchToTab(mTabControl.getTabIndex(appTab));
if (needsLoad) {
- urlData.loadIn(appTab.getWebView());
+ urlData.loadIn(appTab);
}
} else {
// If the tab was the current tab, we have to attach
// it to the view system again.
attachTabToContentView(appTab);
if (needsLoad) {
- urlData.loadIn(appTab.getWebView());
+ urlData.loadIn(appTab);
}
}
return;
@@ -541,7 +566,7 @@
}
// Get rid of the subwindow if it exists
dismissSubWindow(current);
- urlData.loadIn(current.getWebView());
+ urlData.loadIn(current);
}
}
}
@@ -571,6 +596,9 @@
String url = null;
final String action = intent.getAction();
+ if (Tab.VoiceSearchData.VOICE_SEARCH_RESULTS.equals(action)) {
+ return false;
+ }
if (Intent.ACTION_VIEW.equals(action)) {
Uri data = intent.getData();
if (data != null) url = data.toString();
@@ -622,7 +650,7 @@
}
private UrlData getUrlDataFromIntent(Intent intent) {
- String url = null;
+ String url = "";
Map<String, String> headers = null;
if (intent != null) {
final String action = intent.getAction();
@@ -673,9 +701,22 @@
}
}
}
- return new UrlData(url, headers);
+ return new UrlData(url, headers, intent);
}
+ /* package */ void showVoiceTitleBar(String title) {
+ mTitleBar.setInVoiceMode(true);
+ mFakeTitleBar.setInVoiceMode(true);
+ mTitleBar.setDisplayTitle(title);
+ mFakeTitleBar.setDisplayTitle(title);
+ }
+ /* package */ void revertVoiceTitleBar() {
+ mTitleBar.setInVoiceMode(false);
+ mFakeTitleBar.setInVoiceMode(false);
+
+ mTitleBar.setDisplayTitle(mTitle);
+ mFakeTitleBar.setDisplayTitle(mTitle);
+ }
/* package */ static String fixUrl(String inUrl) {
// FIXME: Converting the url to lower case
// duplicates functionality in smartUrlFilter().
@@ -1742,6 +1783,11 @@
WebView view = t.getWebView();
view.setEmbeddedTitleBar(mTitleBar);
+ if (t.isInVoiceSearchMode()) {
+ showVoiceTitleBar(t.getVoiceDisplayTitle());
+ } else {
+ revertVoiceTitleBar();
+ }
// Request focus on the top window.
t.getTopWindow().requestFocus();
}
@@ -1803,7 +1849,7 @@
mTabControl.setCurrentTab(tab);
attachTabToContentView(tab);
if (!urlData.isEmpty()) {
- urlData.loadIn(webview);
+ urlData.loadIn(tab);
}
return tab;
} else {
@@ -1811,10 +1857,10 @@
dismissSubWindow(currentTab);
if (!urlData.isEmpty()) {
// Load the given url.
- urlData.loadIn(currentTab.getWebView());
+ urlData.loadIn(currentTab);
}
+ return currentTab;
}
- return currentTab;
}
private Tab openTab(String url) {
@@ -1991,8 +2037,10 @@
mUrl = url;
mTitle = title;
- mTitleBar.setTitleAndUrl(title, url);
- mFakeTitleBar.setTitleAndUrl(title, url);
+ // If we are in voice search mode, the title has already been set.
+ if (mTabControl.getCurrentTab().isInVoiceSearchMode()) return;
+ mTitleBar.setDisplayTitle(url);
+ mFakeTitleBar.setDisplayTitle(url);
}
/**
@@ -3891,23 +3939,35 @@
private static class UrlData {
final String mUrl;
final Map<String, String> mHeaders;
+ final Intent mVoiceIntent;
UrlData(String url) {
this.mUrl = url;
this.mHeaders = null;
+ this.mVoiceIntent = null;
}
- UrlData(String url, Map<String, String> headers) {
+ UrlData(String url, Map<String, String> headers, Intent intent) {
this.mUrl = url;
this.mHeaders = headers;
+ if (Tab.VoiceSearchData.VOICE_SEARCH_RESULTS.equals(
+ intent.getAction())) {
+ this.mVoiceIntent = intent;
+ } else {
+ this.mVoiceIntent = null;
+ }
}
boolean isEmpty() {
- return mUrl == null || mUrl.length() == 0;
+ return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0);
}
- public void loadIn(WebView webView) {
- webView.loadUrl(mUrl, mHeaders);
+ public void loadIn(Tab t) {
+ if (mVoiceIntent != null) {
+ t.activateVoiceSearchMode(mVoiceIntent);
+ } else {
+ t.getWebView().loadUrl(mUrl, mHeaders);
+ }
}
};
diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java
index 4747721..bd6a723 100644
--- a/src/com/android/browser/BrowserProvider.java
+++ b/src/com/android/browser/BrowserProvider.java
@@ -53,6 +53,7 @@
import java.io.File;
import java.io.FilenameFilter;
+import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -718,16 +719,105 @@
}
}
+ private static class ResultsCursor extends AbstractCursor {
+ // Array indices for RESULTS_COLUMNS
+ private static final int RESULT_ACTION_ID = 1;
+ private static final int RESULT_DATA_ID = 2;
+ private static final int RESULT_TEXT_ID = 3;
+ private static final int RESULT_ICON_ID = 4;
+ private static final int RESULT_EXTRA_ID = 5;
+
+ private static final String[] RESULTS_COLUMNS = new String[] {
+ "_id",
+ SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
+ SearchManager.SUGGEST_COLUMN_INTENT_DATA,
+ SearchManager.SUGGEST_COLUMN_TEXT_1,
+ SearchManager.SUGGEST_COLUMN_ICON_1,
+ SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA
+ };
+ private final ArrayList<String> mResults;
+ public ResultsCursor(ArrayList<String> results) {
+ mResults = results;
+ }
+ public int getCount() { return mResults.size(); }
+
+ public String[] getColumnNames() {
+ return RESULTS_COLUMNS;
+ }
+
+ public String getString(int column) {
+ switch (column) {
+ case RESULT_ACTION_ID:
+ return Tab.VoiceSearchData.VOICE_SEARCH_RESULTS;
+ case RESULT_TEXT_ID:
+ // The data is used when the phone is in landscape mode. We
+ // still want to show the result string.
+ case RESULT_DATA_ID:
+ return mResults.get(mPos);
+ case RESULT_EXTRA_ID:
+ // The Intent's extra data will store the index into
+ // mResults so the BrowserActivity will know which result to
+ // use.
+ return Integer.toString(mPos);
+ case RESULT_ICON_ID:
+ return Integer.valueOf(R.drawable.magnifying_glass)
+ .toString();
+ default:
+ return null;
+ }
+ }
+ public short getShort(int column) {
+ throw new UnsupportedOperationException();
+ }
+ public int getInt(int column) {
+ throw new UnsupportedOperationException();
+ }
+ public long getLong(int column) {
+ if ((mPos != -1) && column == 0) {
+ return mPos; // use row# as the _id
+ }
+ throw new UnsupportedOperationException();
+ }
+ public float getFloat(int column) {
+ throw new UnsupportedOperationException();
+ }
+ public double getDouble(int column) {
+ throw new UnsupportedOperationException();
+ }
+ public boolean isNull(int column) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private ResultsCursor mResultsCursor;
+
+ /**
+ * Provide a set of results to be returned to query, intended to be used
+ * by the SearchDialog when the BrowserActivity is in voice search mode.
+ * @param results Strings to display in the dropdown from the SearchDialog
+ */
+ /* package */ void setQueryResults(ArrayList<String> results) {
+ if (results == null) {
+ mResultsCursor = null;
+ } else {
+ mResultsCursor = new ResultsCursor(results);
+ }
+ }
+
@Override
public Cursor query(Uri url, String[] projectionIn, String selection,
String[] selectionArgs, String sortOrder)
throws IllegalStateException {
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-
int match = URI_MATCHER.match(url);
if (match == -1) {
throw new IllegalArgumentException("Unknown URL");
}
+ if (match == URI_MATCH_SUGGEST && mResultsCursor != null) {
+ Cursor results = mResultsCursor;
+ mResultsCursor = null;
+ return results;
+ }
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
if (match == URI_MATCH_SUGGEST || match == URI_MATCH_BOOKMARKS_SUGGEST) {
String suggestSelection;
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 22c8286..0a54eee 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -17,14 +17,17 @@
package com.android.browser;
import java.io.File;
+import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Vector;
import android.app.AlertDialog;
+import android.app.SearchManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
@@ -133,6 +136,117 @@
// -------------------------------------------------------------------------
+ /**
+ * Private information regarding the latest voice search. If the Tab is not
+ * in voice search mode, this will be null.
+ */
+ private VoiceSearchData mVoiceSearchData;
+ /**
+ * Return whether the tab is in voice search mode.
+ */
+ public boolean isInVoiceSearchMode() {
+ return mVoiceSearchData != null;
+ }
+ /**
+ * Get the title to display for the current voice search page. If the Tab
+ * is not in voice search mode, return null.
+ */
+ public String getVoiceDisplayTitle() {
+ if (mVoiceSearchData == null) return null;
+ return mVoiceSearchData.mLastVoiceSearchTitle;
+ }
+ /**
+ * Get the latest array of voice search results, to be passed to the
+ * BrowserProvider. If the Tab is not in voice search mode, return null.
+ */
+ public ArrayList<String> getVoiceSearchResults() {
+ if (mVoiceSearchData == null) return null;
+ return mVoiceSearchData.mVoiceSearchResults;
+ }
+ /**
+ * Activate voice search mode.
+ * @param intent Intent which has the results to use, or an index into the
+ * results when reusing the old results.
+ */
+ /* package */ void activateVoiceSearchMode(Intent intent) {
+ ArrayList<String> results = intent.getStringArrayListExtra(
+ "result_strings");
+ ArrayList<String> urls = intent.getStringArrayListExtra(
+ "result_urls");
+ if (results != null) {
+ // 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()) {
+ throw new AssertionError("improper extras passed in Intent");
+ }
+ mVoiceSearchData = new VoiceSearchData(results, urls);
+ } else {
+ String extraData = intent.getStringExtra(
+ SearchManager.EXTRA_DATA_KEY);
+ if (extraData != null) {
+ mVoiceSearchData.mLastVoiceSearchIndex
+ = Integer.parseInt(extraData);
+ if (mVoiceSearchData.mLastVoiceSearchIndex
+ >= mVoiceSearchData.mVoiceSearchResults.size()) {
+ throw new AssertionError("index must be less than "
+ + " size of mVoiceSearchResults");
+ }
+ }
+ }
+ mVoiceSearchData.mLastVoiceSearchTitle
+ = mVoiceSearchData.mVoiceSearchResults.get(mVoiceSearchData.
+ mLastVoiceSearchIndex);
+ if (mInForeground) {
+ mActivity.showVoiceTitleBar(mVoiceSearchData.mLastVoiceSearchTitle);
+ }
+ mVoiceSearchData.mLastVoiceSearchUrl
+ = mVoiceSearchData.mVoiceSearchUrls.get(mVoiceSearchData.
+ mLastVoiceSearchIndex);
+ mMainView.loadUrl(mVoiceSearchData.mLastVoiceSearchUrl);
+ }
+ /* package */ static class VoiceSearchData {
+ /**
+ * Intent action for a voice search. Will be replaced with a global
+ * variable.
+ */
+ public static final String VOICE_SEARCH_RESULTS
+ = "android.speech.action.VOICE_SEARCH_RESULTS";
+
+ public VoiceSearchData(ArrayList<String> results,
+ ArrayList<String> urls) {
+ mVoiceSearchResults = results;
+ mVoiceSearchUrls = urls;
+ mLastVoiceSearchIndex = 0;
+ }
+ /*
+ * ArrayList of suggestions to be displayed when opening the
+ * SearchManager
+ */
+ public ArrayList<String> mVoiceSearchResults;
+ /*
+ * ArrayList of urls, associated with the suggestions in
+ * mVoiceSearchResults.
+ */
+ public ArrayList<String> mVoiceSearchUrls;
+ /*
+ * 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.
+ */
+ public String mLastVoiceSearchUrl;
+ /**
+ * The last title used for voice search. Needed to update the title bar
+ * 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
private class ErrorDialog {
public final int mTitle;
@@ -209,6 +323,13 @@
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
mInLoad = true;
+ if (mVoiceSearchData != null
+ && !url.equals(mVoiceSearchData.mLastVoiceSearchUrl)) {
+ mVoiceSearchData = null;
+ if (mInForeground) {
+ mActivity.revertVoiceTitleBar();
+ }
+ }
// We've started to load a new page. If there was a pending message
// to save a screenshot then we will now take the new page and save
diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java
index a9da7c0..743af9b 100644
--- a/src/com/android/browser/TitleBar.java
+++ b/src/com/android/browser/TitleBar.java
@@ -67,6 +67,8 @@
private MyHandler mHandler;
private Intent mVoiceSearchIntent;
private boolean mInVoiceMode;
+ private Drawable mVoiceModeBackground;
+ private Drawable mNormalBackground;
private static int LONG_PRESS = 1;
@@ -110,6 +112,9 @@
}
mStopDrawable = resources.getDrawable(R.drawable.ic_btn_stop_v2);
mBookmarkDrawable = mRtButton.getDrawable();
+ mVoiceModeBackground = resources.getDrawable(
+ R.drawable.textfield_voice_search);
+ mNormalBackground = mTitleBg.getBackground();
}
private class MyHandler extends Handler {
@@ -186,7 +191,12 @@
mRtButton.setPressed(false);
} else if (mTitleBg.isPressed()) {
mHandler.removeMessages(LONG_PRESS);
- mBrowserActivity.onSearchRequested();
+ if (mInVoiceMode) {
+ mBrowserActivity.showVoiceSearchResults(
+ mTitle.getText().toString());
+ } else {
+ mBrowserActivity.onSearchRequested();
+ }
mTitleBg.setPressed(false);
}
break;
@@ -222,13 +232,20 @@
/* package */ void setInVoiceMode(boolean inVoiceMode) {
if (mInVoiceMode == inVoiceMode) return;
mInVoiceMode = inVoiceMode && mVoiceSearchIntent != null;
+ Drawable rightButtonDrawable, titleDrawable;
if (mInVoiceMode) {
- mRtButton.setImageDrawable(mVoiceDrawable);
- } else if (mInLoad) {
- mRtButton.setImageDrawable(mStopDrawable);
+ rightButtonDrawable = mVoiceDrawable;
+ titleDrawable = mVoiceModeBackground;
} else {
- mRtButton.setImageDrawable(mBookmarkDrawable);
+ titleDrawable = mNormalBackground;
+ if (mInLoad) {
+ rightButtonDrawable = mStopDrawable;
+ } else {
+ rightButtonDrawable = mBookmarkDrawable;
+ }
}
+ mTitleBg.setBackgroundDrawable(titleDrawable);
+ mRtButton.setImageDrawable(rightButtonDrawable);
}
/**
@@ -275,13 +292,15 @@
}
/**
- * Update the title and url.
+ * Update the text displayed in the title bar.
+ * @param title String to display. If null, the loading string will be
+ * shown.
*/
- /* package */ void setTitleAndUrl(CharSequence title, CharSequence url) {
- if (url == null) {
+ /* package */ void setDisplayTitle(String title) {
+ if (title == null) {
mTitle.setText(R.string.title_bar_loading);
} else {
- mTitle.setText(url.toString());
+ mTitle.setText(title);
}
}