Implemented voice search hint heuristics.
Voice search hints are now displayed at intervals as defined by Config. They are shown for a while, then hidden again.
Bug: 2812974
Change-Id: I983250ab1296031fa9b38f17fa105e5b88fd976b
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 94c9140..442b282 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -99,12 +99,6 @@
<string name="google_show_web_suggestions_summary_enabled">Show suggestions from Google as you type</string>
<string name="google_show_web_suggestions_summary_disabled">Don\'t show suggestions from Google as you type</string>
- <!-- Title for 'Search Widget' category of search settings -->
- <string name="search_widget_category_title">Search Widget</string>
- <!-- Title and summary for 'Show hints' check box setting -->
- <string name="search_widget_hints_enabled_title">Show hints</string>
- <string name="search_widget_hints_enabled_summary">Show hints in the search widget</string>
-
<!-- Title for Voice Search hints bubble -->
<string name="voice_search_hint_title">Try saying:</string>
<!-- Starting quotation marks of the voice search hint -->
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 00fe2b0..dbcdc35 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -42,17 +42,4 @@
</PreferenceCategory>
- <PreferenceCategory
- android:key="search_widget_settings_category"
- android:title="@string/search_widget_category_title">
-
- <CheckBoxPreference
- android:key="search_widget_hints_enabled"
- android:title="@string/search_widget_hints_enabled_title"
- android:summary="@string/search_widget_hints_enabled_summary"
- android:defaultValue="true"
- />
-
- </PreferenceCategory>
-
</PreferenceScreen>
diff --git a/src/com/android/quicksearchbox/Config.java b/src/com/android/quicksearchbox/Config.java
index 2e0290a..c56c054 100644
--- a/src/com/android/quicksearchbox/Config.java
+++ b/src/com/android/quicksearchbox/Config.java
@@ -16,6 +16,7 @@
package com.android.quicksearchbox;
+import android.app.AlarmManager;
import android.content.Context;
import android.content.res.Resources;
import android.os.Process;
@@ -34,7 +35,9 @@
private static final String TAG = "QSB.Config";
- private static final long DAY_MILLIS = 86400000L;
+ protected static final long SECOND_MILLIS = 1000L;
+ protected static final long MINUTE_MILLIS = 60L * SECOND_MILLIS;
+ protected static final long DAY_MILLIS = 86400000L;
private static final int NUM_SUGGESTIONS_ABOVE_KEYBOARD = 4;
private static final int NUM_PROMOTED_SOURCES = 3;
@@ -58,6 +61,18 @@
private static final long TYPING_SUGGESTIONS_UPDATE_DELAY_MILLIS = 100;
private static final long PUBLISH_RESULT_DELAY_MILLIS = 200;
+ private static final long VOICE_SEARCH_HINT_ACTIVE_PERIOD = 7L * DAY_MILLIS;
+
+ private static final long VOICE_SEARCH_HINT_UPDATE_INTERVAL
+ = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
+
+ private static final long VOICE_SEARCH_HINT_SHOW_PERIOD_MILLIS
+ = AlarmManager.INTERVAL_HOUR * 2;
+
+ private static final long VOICE_SEARCH_HINT_CHANGE_PERIOD = 2L * MINUTE_MILLIS;
+
+ private static final long VOICE_SEARCH_HINT_VISIBLE_PERIOD = 6L * MINUTE_MILLIS;
+
private final Context mContext;
private HashSet<String> mDefaultCorpora;
private HashSet<String> mHiddenCorpora;
@@ -233,4 +248,44 @@
public boolean allowVoiceSearchHints() {
return true;
}
+
+ /**
+ * The period of time for which after installing voice search we should consider showing voice
+ * search hints.
+ * @return The period in milliseconds.
+ */
+ public long getVoiceSearchHintActivePeriod() {
+ return VOICE_SEARCH_HINT_ACTIVE_PERIOD;
+ }
+
+ /**
+ * The time interval at which we should consider whether or not to show some voice search hints.
+ * @return The period in milliseconds.
+ */
+ public long getVoiceSearchHintUpdatePeriod() {
+ return VOICE_SEARCH_HINT_UPDATE_INTERVAL;
+ }
+
+ /**
+ * The time interval at which, on average, voice search hints are displayed.
+ */
+ public long getVoiceSearchHintShowPeriod() {
+ return VOICE_SEARCH_HINT_SHOW_PERIOD_MILLIS;
+ }
+
+ /**
+ * The amount of time for which voice search hints are displayed in one go.
+ * @return The period in milliseconds.
+ */
+ public long getVoiceSearchHintVisibleTime() {
+ return VOICE_SEARCH_HINT_VISIBLE_PERIOD;
+ }
+
+ /**
+ * The period that we change voice search hints at while they're being displayed.
+ * @return The period in milliseconds.
+ */
+ public long getVoiceSearchHintChangePeriod() {
+ return VOICE_SEARCH_HINT_CHANGE_PERIOD;
+ }
}
diff --git a/src/com/android/quicksearchbox/SearchSettings.java b/src/com/android/quicksearchbox/SearchSettings.java
index 3adbcef..c3d2252 100644
--- a/src/com/android/quicksearchbox/SearchSettings.java
+++ b/src/com/android/quicksearchbox/SearchSettings.java
@@ -27,7 +27,6 @@
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.os.Bundle;
-import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
@@ -59,16 +58,13 @@
private static final String CLEAR_SHORTCUTS_PREF = "clear_shortcuts";
private static final String SEARCH_ENGINE_SETTINGS_PREF = "search_engine_settings";
private static final String SEARCH_CORPORA_PREF = "search_corpora";
- private static final String SEARCH_WIDGET_CATEGORY = "search_widget_settings_category";
// Prefix of per-corpus enable preference
private static final String CORPUS_ENABLED_PREF_PREFIX = "enable_corpus_";
- private static final String SEARCH_WIDGET_HINTS_ENABLED_PREF = "search_widget_hints_enabled";
// References to the top-level preference objects
private Preference mClearShortcutsPreference;
private PreferenceScreen mSearchEngineSettingsPreference;
- private CheckBoxPreference mVoiceSearchHintsPreference;
// Dialog ids
private static final int CLEAR_SHORTCUTS_CONFIRM_DIALOG = 0;
@@ -85,21 +81,11 @@
mClearShortcutsPreference = preferenceScreen.findPreference(CLEAR_SHORTCUTS_PREF);
mSearchEngineSettingsPreference = (PreferenceScreen) preferenceScreen.findPreference(
SEARCH_ENGINE_SETTINGS_PREF);
- mVoiceSearchHintsPreference = (CheckBoxPreference)
- preferenceScreen.findPreference(SEARCH_WIDGET_HINTS_ENABLED_PREF);
Preference corporaPreference = preferenceScreen.findPreference(SEARCH_CORPORA_PREF);
corporaPreference.setIntent(getSearchableItemsIntent(this));
mClearShortcutsPreference.setOnPreferenceClickListener(this);
- if (getConfig().allowVoiceSearchHints()) {
- mVoiceSearchHintsPreference.setOnPreferenceClickListener(this);
- } else {
- preferenceScreen.removePreference(
- preferenceScreen.findPreference(SEARCH_WIDGET_CATEGORY));
- mVoiceSearchHintsPreference = null;
- }
-
updateClearShortcutsPreference();
populateSearchEnginePreference();
}
@@ -119,16 +105,6 @@
return CORPUS_ENABLED_PREF_PREFIX + corpus.getName();
}
- public static boolean areVoiceSearchHintsEnabled(Context context) {
- return getSearchPreferences(context).getBoolean(SEARCH_WIDGET_HINTS_ENABLED_PREF, true);
- }
-
- public static void setVoiceSearchHintsEnabled(Context context, boolean enabled) {
- getSearchPreferences(context)
- .edit().putBoolean(SEARCH_WIDGET_HINTS_ENABLED_PREF, enabled).commit();
- SearchWidgetProvider.updateSearchWidgets(context);
- }
-
public static SharedPreferences getSearchPreferences(Context context) {
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}
@@ -182,9 +158,6 @@
if (preference == mClearShortcutsPreference) {
showDialog(CLEAR_SHORTCUTS_CONFIRM_DIALOG);
return true;
- } else if (preference == mVoiceSearchHintsPreference) {
- SearchWidgetProvider.updateSearchWidgets(this);
- return true;
}
return false;
}
diff --git a/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java b/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
index 935cda3..dc03fb4 100644
--- a/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
+++ b/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
@@ -24,6 +24,7 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
+import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
@@ -32,10 +33,12 @@
* The configuration screen for search widgets.
*/
public class SearchWidgetConfigActivity extends ChoiceActivity {
- static final String TAG = "QSB.SearchWidgetConfigActivity";
+ private static final boolean DBG = false;
+ private static final String TAG = "QSB.SearchWidgetConfigActivity";
private static final String PREFS_NAME = "SearchWidgetConfig";
- private static final String WIDGET_CORPUS_PREF_PREFIX = "widget_corpus_";
+ private static final String WIDGET_CORPUS_NAME_PREFIX = "widget_corpus_";
+ private static final String WIDGET_CORPUS_SHOWING_HINT_PREFIX = "widget_showing_hint_";
private CorporaAdapter mAdapter;
@@ -83,7 +86,7 @@
}
protected void selectCorpus(Corpus corpus) {
- writeWidgetCorpusPref(mAppWidgetId, corpus);
+ setWidgetCorpusName(mAppWidgetId, corpus);
SearchWidgetProvider.updateSearchWidgets(this);
Intent result = new Intent();
@@ -96,20 +99,38 @@
return context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
}
- private static String getCorpusPrefKey(int appWidgetId) {
- return WIDGET_CORPUS_PREF_PREFIX + appWidgetId;
+ private static String getCorpusNameKey(int appWidgetId) {
+ return WIDGET_CORPUS_NAME_PREFIX + appWidgetId;
}
- private void writeWidgetCorpusPref(int appWidgetId, Corpus corpus) {
+ private static String getShowingHintKey(int appWidgetId) {
+ return WIDGET_CORPUS_SHOWING_HINT_PREFIX + appWidgetId;
+ }
+
+ private void setWidgetCorpusName(int appWidgetId, Corpus corpus) {
String corpusName = corpus == null ? null : corpus.getName();
SharedPreferences.Editor prefs = getWidgetPreferences(this).edit();
- prefs.putString(getCorpusPrefKey(appWidgetId), corpusName);
+ prefs.putString(getCorpusNameKey(appWidgetId), corpusName);
prefs.commit();
}
- public static String readWidgetCorpusPref(Context context, int appWidgetId) {
+ public static String getWidgetCorpusName(Context context, int appWidgetId) {
SharedPreferences prefs = getWidgetPreferences(context);
- return prefs.getString(getCorpusPrefKey(appWidgetId), null);
+ return prefs.getString(getCorpusNameKey(appWidgetId), null);
+ }
+
+ public static void setWidgetShowingHint(Context context, int appWidgetId, boolean showing) {
+ SharedPreferences.Editor prefs = getWidgetPreferences(context).edit();
+ prefs.putBoolean(getShowingHintKey(appWidgetId), showing);
+ boolean c = prefs.commit();
+ if (DBG) Log.d(TAG, "Widget " + appWidgetId + " set showing hint " + showing + "("+c+")");
+ }
+
+ public static boolean getWidgetShowingHint(Context context, int appWidgetId) {
+ SharedPreferences prefs = getWidgetPreferences(context);
+ boolean r = prefs.getBoolean(getShowingHintKey(appWidgetId), false);
+ if (DBG) Log.d(TAG, "Widget " + appWidgetId + " showing hint: " + r);
+ return r;
}
private CorpusRanker getCorpusRanker() {
diff --git a/src/com/android/quicksearchbox/SearchWidgetProvider.java b/src/com/android/quicksearchbox/SearchWidgetProvider.java
index 1b59658..1321054 100644
--- a/src/com/android/quicksearchbox/SearchWidgetProvider.java
+++ b/src/com/android/quicksearchbox/SearchWidgetProvider.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
@@ -44,6 +45,7 @@
import android.widget.RemoteViews;
import java.util.ArrayList;
+import java.util.Random;
/**
* Search widget provider.
@@ -62,60 +64,185 @@
"com.android.quicksearchbox.action.NEXT_VOICE_SEARCH_HINT";
/**
- * Broadcast intent action for disabling voice search hints.
+ * Broadcast intent action for hiding voice search hints.
*/
- private static final String ACTION_CLOSE_VOICE_SEARCH_HINT =
- "com.android.quicksearchbox.action.CLOSE_VOICE_SEARCH_HINT";
+ private static final String ACTION_HIDE_VOICE_SEARCH_HINT =
+ "com.android.quicksearchbox.action.HIDE_VOICE_SEARCH_HINT";
/**
- * Voice search hint update interval in milliseconds.
+ * Broadcast intent action for updating voice search hint display. Voice search hints will
+ * only be displayed with some probability.
*/
- private static final long VOICE_SEARCH_HINT_UPDATE_INTERVAL
- = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
+ private static final String ACTION_CONSIDER_VOICE_SEARCH_HINT =
+ "com.android.quicksearchbox.action.CONSIDER_VOICE_SEARCH_HINT";
/**
- * Preference key used for storing the index of the next vocie search hint to show.
+ * Preference key used for storing the index of the next voice search hint to show.
*/
private static final String NEXT_VOICE_SEARCH_HINT_INDEX_PREF = "next_voice_search_hint";
/**
+ * Preference key used to store the time at which the first voice search hint was displayed.
+ */
+ private static final String FIRST_VOICE_HINT_DISPLAY_TIME = "first_voice_search_hint_time";
+
+ /**
+ * Preference key for the version of voice search we last got hints from.
+ */
+ private static final String LAST_SEEN_VOICE_SEARCH_VERSION = "voice_search_version";
+
+ /**
* The {@link Search#SOURCE} value used when starting searches from the search widget.
*/
private static final String WIDGET_SEARCH_SOURCE = "launcher-widget";
+ private static Random sRandom;
+
@Override
public void onReceive(Context context, Intent intent) {
if (DBG) Log.d(TAG, "onReceive(" + intent.toUri(0) + ")");
String action = intent.getAction();
- if (ACTION_NEXT_VOICE_SEARCH_HINT.equals(action)) {
- getHintsFromVoiceSearch(context);
- } else if (ACTION_CLOSE_VOICE_SEARCH_HINT.equals(action)) {
- SearchSettings.setVoiceSearchHintsEnabled(context, false);
+ if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
+ scheduleVoiceHintUpdates(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
updateSearchWidgets(context);
+ } else if (ACTION_CONSIDER_VOICE_SEARCH_HINT.equals(action)) {
+ considerShowingVoiceSearchHints(context);
+ } else if (ACTION_NEXT_VOICE_SEARCH_HINT.equals(action)) {
+ getHintsFromVoiceSearch(context);
+ } else if (ACTION_HIDE_VOICE_SEARCH_HINT.equals(action)) {
+ hideVoiceSearchHint(context);
}
}
+ private static Random getRandom() {
+ if (sRandom == null) {
+ sRandom = new Random();
+ }
+ return sRandom;
+ }
+
+ private static boolean haveVoiceSearchHintsExpired(Context context) {
+ SharedPreferences prefs = SearchSettings.getSearchPreferences(context);
+ QsbApplication app = QsbApplication.get(context);
+ int currentVoiceSearchVersion = app.getVoiceSearch().getVersion();
+
+ if (currentVoiceSearchVersion != 0) {
+ long currentTime = System.currentTimeMillis();
+ int lastVoiceSearchVersion = prefs.getInt(LAST_SEEN_VOICE_SEARCH_VERSION, 0);
+ long firstHintTime = prefs.getLong(FIRST_VOICE_HINT_DISPLAY_TIME, 0);
+ if (firstHintTime == 0 || currentVoiceSearchVersion != lastVoiceSearchVersion) {
+ Editor e = prefs.edit();
+ e.putInt(LAST_SEEN_VOICE_SEARCH_VERSION, currentVoiceSearchVersion);
+ e.putLong(FIRST_VOICE_HINT_DISPLAY_TIME, currentTime);
+ e.commit();
+ firstHintTime = currentTime;
+ }
+ if (currentTime - firstHintTime > getConfig(context).getVoiceSearchHintActivePeriod()) {
+ if (DBG) Log.d(TAG, "Voice seach hint period expired; not showing hints.");
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ if (DBG) Log.d(TAG, "Could not determine voice search version; not showing hints.");
+ return true;
+ }
+ }
+
+ private static boolean shouldShowVoiceSearchHints(Context context) {
+ return (getConfig(context).allowVoiceSearchHints()
+ && !haveVoiceSearchHintsExpired(context));
+ }
+
+ private static SearchWidgetState[] getSearchWidgetStates
+ (Context context, boolean enableVoiceSearchHints) {
+
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ int[] appWidgetIds = appWidgetManager.getAppWidgetIds(myComponentName(context));
+ SearchWidgetState[] states = new SearchWidgetState[appWidgetIds.length];
+ for (int i = 0; i<appWidgetIds.length; ++i) {
+ states[i] = getSearchWidgetState(context, appWidgetIds[i], enableVoiceSearchHints);
+ }
+ return states;
+ }
+
+ private static void considerShowingVoiceSearchHints(Context context) {
+ if (DBG) Log.d(TAG, "considerShowingVoiceSearchHints");
+ if (!shouldShowVoiceSearchHints(context)) return;
+ SearchWidgetState[] states = getSearchWidgetStates(context, true);
+ boolean needHint = false;
+ boolean changed = false;
+ for (SearchWidgetState state : states) {
+ changed |= state.considerShowingHint(context);
+ needHint |= state.isShowingHint();
+ }
+ if (changed) {
+ getHintsFromVoiceSearch(context);
+ sceduleNextVoiceSearchHint(context, true);
+ }
+ }
+
+ private void hideVoiceSearchHint(Context context) {
+ if (DBG) Log.d(TAG, "hideVoiceSearchHint");
+ SearchWidgetState[] states = getSearchWidgetStates(context, true);
+ boolean needHint = false;
+ for (SearchWidgetState state : states) {
+ if (state.isShowingHint()) {
+ state.hideVoiceSearchHint(context);
+ state.updateWidget(context, AppWidgetManager.getInstance(context));
+ }
+ needHint |= state.isShowingHint();
+ }
+ sceduleNextVoiceSearchHint(context, false);
+ }
+
+ private static void voiceSearchHintReceived(Context context, CharSequence hint) {
+ if (DBG) Log.d(TAG, "voiceSearchHintReceived('" + hint + "')");
+ CharSequence formatted = formatVoiceSearchHint(context, hint);
+ SearchWidgetState[] states = getSearchWidgetStates(context, true);
+ boolean needHint = false;
+ for (SearchWidgetState state : states) {
+ if (state.isShowingHint()) {
+ state.setVoiceSearchHint(formatted);
+ state.updateWidget(context, AppWidgetManager.getInstance(context));
+ needHint = true;
+ }
+ }
+ if (!needHint) {
+ sceduleNextVoiceSearchHint(context, false);
+ }
+ }
+
+ private static void scheduleVoiceHintUpdates(Context context) {
+ if (DBG) Log.d(TAG, "scheduleVoiceHintUpdates");
+ if (!shouldShowVoiceSearchHints(context)) return;
+ scheduleVoiceSearchHintUpdates(context, true);
+ }
+
/**
* Updates all search widgets.
*/
public static void updateSearchWidgets(Context context) {
- updateSearchWidgets(context, true, null);
- }
+ if (DBG) Log.d(TAG, "updateSearchWidgets");
+ boolean showVoiceSearchHints = shouldShowVoiceSearchHints(context);
+ SearchWidgetState[] states = getSearchWidgetStates(context, showVoiceSearchHints);
- private static void updateSearchWidgets(Context context, boolean updateVoiceSearchHint,
- CharSequence voiceSearchHint) {
- AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- int[] appWidgetIds = appWidgetManager.getAppWidgetIds(myComponentName(context));
-
- boolean needsVoiceSearchHint = false;
- for (int appWidgetId : appWidgetIds) {
- SearchWidgetState state = getSearchWidgetState(context, appWidgetId, voiceSearchHint);
- state.updateWidget(context, appWidgetManager);
- needsVoiceSearchHint |= state.shouldShowVoiceSearchHint();
+ boolean needVoiceSearchHint = false;
+ for (SearchWidgetState state : states) {
+ if (state.isShowingHint()) {
+ needVoiceSearchHint = true;
+ // widget update will occur when voice search hint received
+ } else {
+ state.updateWidget(context, AppWidgetManager.getInstance(context));
+ }
}
- if (updateVoiceSearchHint) {
- scheduleVoiceSearchHintUpdates(context, needsVoiceSearchHint);
+ if (DBG) Log.d(TAG, "Need voice search hints=" + needVoiceSearchHint);
+ if (needVoiceSearchHint) {
+ getHintsFromVoiceSearch(context);
+ }
+ if (!showVoiceSearchHints) {
+ scheduleVoiceSearchHintUpdates(context, false);
}
}
@@ -129,14 +256,11 @@
}
private static SearchWidgetState getSearchWidgetState(Context context,
- int appWidgetId, CharSequence voiceSearchHint) {
+ int appWidgetId, boolean enableVoiceSearchHints) {
String corpusName =
- SearchWidgetConfigActivity.readWidgetCorpusPref(context, appWidgetId);
+ SearchWidgetConfigActivity.getWidgetCorpusName(context, appWidgetId);
Corpus corpus = corpusName == null ? null : getCorpora(context).getCorpus(corpusName);
- if (DBG) {
- Log.d(TAG, "Updating appwidget " + appWidgetId + ", corpus=" + corpus
- + ",VS hint=" + voiceSearchHint);
- }
+ if (DBG) Log.d(TAG, "Creating appwidget state " + appWidgetId + ", corpus=" + corpus);
SearchWidgetState state = new SearchWidgetState(appWidgetId);
Bundle widgetAppData = new Bundle();
@@ -173,12 +297,19 @@
state.setQueryTextViewIntent(qsbIntent);
// Voice search button
- Intent voiceSearchIntent = getVoiceSearchIntent(context, corpus, widgetAppData);
- state.setVoiceSearchIntent(voiceSearchIntent);
- if (voiceSearchIntent != null
- && RecognizerIntent.ACTION_WEB_SEARCH.equals(voiceSearchIntent.getAction())) {
- state.setShouldShowVoiceSearchHint(true);
- state.setVoiceSearchHint(formatVoiceSearchHint(context, voiceSearchHint));
+ if (enableVoiceSearchHints) {
+ Intent voiceSearchIntent = getVoiceSearchIntent(context, corpus, widgetAppData);
+ state.setVoiceSearchIntent(voiceSearchIntent);
+ if (voiceSearchIntent != null
+ && RecognizerIntent.ACTION_WEB_SEARCH.equals(voiceSearchIntent.getAction())) {
+ state.setVoiceSearchHintsEnabled(true);
+
+ boolean showingHint =
+ SearchWidgetConfigActivity.getWidgetShowingHint(context, appWidgetId);
+ if (DBG) Log.d(TAG, "Widget " + appWidgetId + " showing hint: " + showingHint);
+ state.setShowingHint(showingHint);
+
+ }
}
return state;
@@ -224,33 +355,36 @@
return spannedHint;
}
- private static boolean areVoiceSearchHintsEnabled(Context context) {
- return getConfig(context).allowVoiceSearchHints()
- && SearchSettings.areVoiceSearchHintsEnabled(context);
+ private static void rescheduleAction(Context context, boolean reshedule, String action, long period) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(action);
+ intent.setComponent(myComponentName(context));
+ PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0);
+ alarmManager.cancel(pending);
+ if (reshedule) {
+ if (DBG) Log.d(TAG, "Scheduling action " + action + " after period " + period);
+ alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + period, period, pending);
+ } else {
+ if (DBG) Log.d(TAG, "Cancelled action " + action);
+ }
}
public static void scheduleVoiceSearchHintUpdates(Context context, boolean enabled) {
- AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(ACTION_NEXT_VOICE_SEARCH_HINT);
- intent.setComponent(myComponentName(context));
- PendingIntent updateHint = PendingIntent.getBroadcast(context, 0, intent, 0);
- alarmManager.cancel(updateHint);
- if (enabled && areVoiceSearchHintsEnabled(context)) {
- // Do one update immediately, and then at VOICE_SEARCH_HINT_UPDATE_INTERVAL intervals
- getHintsFromVoiceSearch(context);
- long period = VOICE_SEARCH_HINT_UPDATE_INTERVAL;
- alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
- SystemClock.elapsedRealtime() + period, period, updateHint);
- }
+ rescheduleAction(context, enabled, ACTION_CONSIDER_VOICE_SEARCH_HINT,
+ getConfig(context).getVoiceSearchHintUpdatePeriod());
+ }
+
+ private static void sceduleNextVoiceSearchHint(Context context, boolean needUpdates) {
+ rescheduleAction(context, needUpdates, ACTION_NEXT_VOICE_SEARCH_HINT,
+ getConfig(context).getVoiceSearchHintChangePeriod());
}
/**
* Requests an asynchronous update of the voice search hints.
*/
private static void getHintsFromVoiceSearch(Context context) {
- if (!areVoiceSearchHintsEnabled(context)) return;
Intent intent = new Intent(RecognizerIntent.ACTION_GET_LANGUAGE_DETAILS);
- intent.putExtra(Recognition.EXTRA_HINT_CONTEXT, Recognition.HINT_CONTEXT_LAUNCHER);
if (DBG) Log.d(TAG, "Broadcasting " + intent);
context.sendOrderedBroadcast(intent, null,
new HintReceiver(), null, Activity.RESULT_OK, null, null);
@@ -265,7 +399,7 @@
ArrayList<CharSequence> hints = getResultExtras(true)
.getCharSequenceArrayList(Recognition.EXTRA_HINT_STRINGS);
CharSequence hint = getNextHint(context, hints);
- updateSearchWidgets(context, false, hint);
+ voiceSearchHintReceived(context, hint);
}
}
@@ -315,19 +449,28 @@
private int mQueryTextViewBackgroundResource;
private Intent mQueryTextViewIntent;
private Intent mVoiceSearchIntent;
- private boolean mShouldShowVoiceSearchHint;
+ private boolean mVoiceSearchHintsEnabled;
private CharSequence mVoiceSearchHint;
+ private boolean mShowHint;
public SearchWidgetState(int appWidgetId) {
mAppWidgetId = appWidgetId;
}
- public boolean shouldShowVoiceSearchHint() {
- return mShouldShowVoiceSearchHint;
+ public int getId() {
+ return mAppWidgetId;
}
- public void setShouldShowVoiceSearchHint(boolean shouldShowVoiceSearchHint) {
- mShouldShowVoiceSearchHint = shouldShowVoiceSearchHint;
+ public void setVoiceSearchHintsEnabled(boolean enabled) {
+ mVoiceSearchHintsEnabled = enabled;
+ }
+
+ public void setShowingHint(boolean show) {
+ mShowHint = show;
+ }
+
+ public boolean isShowingHint() {
+ return mShowHint;
}
public void setCorpusIconUri(Uri corpusIconUri) {
@@ -358,7 +501,64 @@
mVoiceSearchHint = voiceSearchHint;
}
- public void updateWidget(Context context, AppWidgetManager appWidgetManager) {
+ private boolean chooseToShowHint(Context context) {
+ // this is called every getConfig().getVoiceSearchHintUpdatePeriod() milliseconds
+ // we want to return true every getConfig().getVoiceSearchHintShowPeriod() milliseconds
+ // so:
+ Config cfg = getConfig(context);
+ float p = (float) cfg.getVoiceSearchHintUpdatePeriod()
+ / (float) cfg.getVoiceSearchHintShowPeriod();
+ float f = getRandom().nextFloat();
+ // if p > 1 we won't return true as often as we should (we can't return more times than
+ // we're called!) but we will always return true.
+ boolean r = (f < p);
+ if (DBG) Log.d(TAG, "chooseToShowHint p=" + p +"; f=" + f + "; r=" + r);
+ return r;
+ }
+
+ private Intent createIntent(Context context, String action) {
+ Intent intent = new Intent(action);
+ intent.setComponent(myComponentName(context));
+ return intent;
+ }
+
+ private void sheduleHintHiding(Context context) {
+ Intent hideIntent = createIntent(context, ACTION_HIDE_VOICE_SEARCH_HINT);
+
+ AlarmManager alarmManager =
+ (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ PendingIntent hideHint = PendingIntent.getBroadcast(context, 0, hideIntent, 0);
+
+ long period = getConfig(context).getVoiceSearchHintVisibleTime();
+ if (DBG) {
+ Log.d(TAG, "Scheduling action " + ACTION_HIDE_VOICE_SEARCH_HINT +
+ " after period " + period);
+ }
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + period, hideHint);
+
+ }
+
+ private void updateShowingHint(Context context) {
+ SearchWidgetConfigActivity.setWidgetShowingHint(context, mAppWidgetId, mShowHint);
+ }
+
+ public boolean considerShowingHint(Context context) {
+ if (!mVoiceSearchHintsEnabled || mShowHint) return false;
+ if (!chooseToShowHint(context)) return false;
+ sheduleHintHiding(context);
+ mShowHint = true;
+ updateShowingHint(context);
+ return true;
+ }
+
+ public void hideVoiceSearchHint(Context context) {
+ mShowHint = false;
+ updateShowingHint(context);
+ }
+
+ public void updateWidget(Context context,AppWidgetManager appWidgetMgr) {
+ if (DBG) Log.d(TAG, "Updating appwidget " + mAppWidgetId);
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.search_widget);
// Corpus indicator
// Before Froyo, android.resource URI could not be used in ImageViews.
@@ -384,25 +584,23 @@
} else {
views.setViewVisibility(R.id.search_widget_voice_btn, View.GONE);
}
+
// Voice Search hints
- if (mShouldShowVoiceSearchHint && !TextUtils.isEmpty(mVoiceSearchHint)) {
+ if (mShowHint && !TextUtils.isEmpty(mVoiceSearchHint)) {
views.setTextViewText(R.id.voice_search_hint_text, mVoiceSearchHint);
- Intent nextHintIntent = new Intent(ACTION_NEXT_VOICE_SEARCH_HINT);
- nextHintIntent.setComponent(myComponentName(context));
- setOnClickBroadcastIntent(context, views, R.id.voice_search_hint_text,
- nextHintIntent);
-
- Intent closeHintIntent = new Intent(ACTION_CLOSE_VOICE_SEARCH_HINT);
- closeHintIntent.setComponent(myComponentName(context));
+ Intent closeHintIntent = createIntent(context, ACTION_HIDE_VOICE_SEARCH_HINT);
setOnClickBroadcastIntent(context, views, R.id.voice_search_hint_close,
closeHintIntent);
+ setOnClickActivityIntent(context, views, R.id.voice_search_hint_text,
+ mVoiceSearchIntent);
+
views.setViewVisibility(R.id.voice_search_hint, View.VISIBLE);
} else {
views.setViewVisibility(R.id.voice_search_hint, View.GONE);
}
- appWidgetManager.updateAppWidget(mAppWidgetId, views);
+ appWidgetMgr.updateAppWidget(mAppWidgetId, views);
}
private void setOnClickBroadcastIntent(Context context, RemoteViews views, int viewId,
diff --git a/src/com/android/quicksearchbox/VoiceSearch.java b/src/com/android/quicksearchbox/VoiceSearch.java
index 69d3a12..e826784 100644
--- a/src/com/android/quicksearchbox/VoiceSearch.java
+++ b/src/com/android/quicksearchbox/VoiceSearch.java
@@ -18,16 +18,21 @@
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.speech.RecognizerIntent;
+import android.util.Log;
/**
* Voice Search integration.
*/
public class VoiceSearch {
+ private static final String TAG = "QSB.VoiceSearch";
+
private final Context mContext;
public VoiceSearch(Context context) {
@@ -50,11 +55,15 @@
return new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
}
- public boolean isVoiceSearchAvailable() {
+ private ResolveInfo getResolveInfo() {
Intent intent = createVoiceSearchIntent();
ResolveInfo ri = mContext.getPackageManager().
resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
- return ri != null;
+ return ri;
+ }
+
+ public boolean isVoiceSearchAvailable() {
+ return getResolveInfo() != null;
}
public Intent createVoiceWebSearchIntent(Bundle appData) {
@@ -69,4 +78,20 @@
return intent;
}
+ /**
+ * Gets the {@code versionCode} of the currently installed voice search package.
+ * @return The {@code versionCode} of voiceSearch, or 0 if none is installed.
+ */
+ public int getVersion() {
+ ResolveInfo ri = getResolveInfo();
+ if (ri == null) return 0;
+ ComponentInfo ci = ri.activityInfo != null ? ri.activityInfo : ri.serviceInfo;
+ try {
+ return getContext().getPackageManager().getPackageInfo(ci.packageName, 0).versionCode;
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Cannot find voice search package " + ci.packageName, e);
+ return 0;
+ }
+ }
+
}