Merge remote branch 'goog/froyo' into merge_froyo_to_market-0
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 21a8fc4..18edda0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -62,8 +62,13 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="qsb.corpus" />
</intent-filter>
+ <meta-data android:name="android.app.search.shortcut.provider" android:value="content://com.android.quicksearchbox.shortcuts/shortcuts" />
</activity>
+ <provider android:name=".ShortcutsProvider"
+ android:authorities="com.android.quicksearchbox.shortcuts">
+ </provider>
+
<activity android:name=".SearchSettings"
android:label="@string/search_settings"
android:excludeFromRecents="true">
@@ -76,6 +81,24 @@
</intent-filter>
</activity>
+ <activity android:name=".SearchableItemsSettings"
+ android:label="@string/search_sources"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="com.android.quicksearchbox.action.SEARCHABLE_ITEMS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <receiver android:name=".CorporaUpdateReceiver">
+ <intent-filter>
+ <action android:name="android.search.action.SEARCHABLES_CHANGED" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.search.action.SETTINGS_CHANGED" />
+ </intent-filter>
+ </receiver>
+
<receiver android:name=".SearchWidgetProvider"
android:label="@string/app_name">
<intent-filter>
diff --git a/res/drawable-hdpi/refine_query_default.png b/res/drawable-hdpi/refine_query_default.png
new file mode 100644
index 0000000..10ae447
--- /dev/null
+++ b/res/drawable-hdpi/refine_query_default.png
Binary files differ
diff --git a/res/drawable-hdpi/refine_query_focused.png b/res/drawable-hdpi/refine_query_focused.png
new file mode 100644
index 0000000..5337bfa
--- /dev/null
+++ b/res/drawable-hdpi/refine_query_focused.png
Binary files differ
diff --git a/res/drawable-hdpi/refine_query_pressed.png b/res/drawable-hdpi/refine_query_pressed.png
new file mode 100644
index 0000000..883becb
--- /dev/null
+++ b/res/drawable-hdpi/refine_query_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/voice_search_hint_bg.9.png b/res/drawable-hdpi/voice_search_hint_bg.9.png
new file mode 100755
index 0000000..438c172
--- /dev/null
+++ b/res/drawable-hdpi/voice_search_hint_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/refine_query_default.png b/res/drawable-mdpi/refine_query_default.png
new file mode 100644
index 0000000..ab98e2c
--- /dev/null
+++ b/res/drawable-mdpi/refine_query_default.png
Binary files differ
diff --git a/res/drawable-mdpi/refine_query_focused.png b/res/drawable-mdpi/refine_query_focused.png
new file mode 100644
index 0000000..4a4bfc6
--- /dev/null
+++ b/res/drawable-mdpi/refine_query_focused.png
Binary files differ
diff --git a/res/drawable-mdpi/refine_query_pressed.png b/res/drawable-mdpi/refine_query_pressed.png
new file mode 100644
index 0000000..bd641da
--- /dev/null
+++ b/res/drawable-mdpi/refine_query_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/voice_search_hint_bg.9.png b/res/drawable-mdpi/voice_search_hint_bg.9.png
new file mode 100644
index 0000000..eb09cbd
--- /dev/null
+++ b/res/drawable-mdpi/voice_search_hint_bg.9.png
Binary files differ
diff --git a/res/drawable/refine_query.xml b/res/drawable/refine_query.xml
new file mode 100644
index 0000000..e80edca
--- /dev/null
+++ b/res/drawable/refine_query.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/refine_query_default" />
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/refine_query_pressed" />
+
+ <item android:state_enabled="true" android:state_focused="true"
+ android:drawable="@drawable/refine_query_focused" />
+
+ <item android:drawable="@drawable/refine_query_default" />
+
+</selector>
+
diff --git a/res/layout/choice_activity.xml b/res/layout/choice_activity.xml
index b0414b3..f34673e 100644
--- a/res/layout/choice_activity.xml
+++ b/res/layout/choice_activity.xml
@@ -50,7 +50,8 @@
android:src="@drawable/ic_dialog_menu_generic" />
<TextView android:id="@+id/alertTitle"
style="?android:attr/textAppearanceLarge"
- android:singleLine="true"
+ android:singleLine="false"
+ android:maxLines="3"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
diff --git a/res/layout/corpus_selection_dialog.xml b/res/layout/corpus_selection_dialog.xml
index 8a5a2f2..c826f86 100644
--- a/res/layout/corpus_selection_dialog.xml
+++ b/res/layout/corpus_selection_dialog.xml
@@ -52,9 +52,22 @@
android:layout_gravity="top|left"
android:horizontalSpacing="2dip"
android:verticalSpacing="2dip"
+ android:numColumns="@integer/corpus_selection_dialog_columns"
android:listSelector="@drawable/corpus_grid_item_bg"
/>
+ <TextView
+ android:id="@+id/corpus_edit_items"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="4dip"
+ android:layout_gravity="bottom|right"
+ android:singleLine="true"
+ android:textColor="@android:color/tertiary_text_light"
+ android:textSize="16dip"
+ android:text="@string/corpus_selection_edit_items"
+ />
+
</LinearLayout>
<ImageView
diff --git a/res/layout/search_widget.xml b/res/layout/search_widget.xml
index 99a8a02..bc0a8db 100644
--- a/res/layout/search_widget.xml
+++ b/res/layout/search_widget.xml
@@ -14,48 +14,66 @@
limitations under the License.
-->
-<LinearLayout
+<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/search_plate"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingRight="14dip"
- android:orientation="horizontal"
- android:background="@drawable/search_floater" >
+ android:layout_height="match_parent"
+ >
- <include layout="@layout/corpus_indicator" />
-
- <TextView
- android:id="@+id/search_widget_text"
- android:layout_width="0dip"
+ <LinearLayout
+ android:id="@+id/search_plate"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1.0"
- android:layout_marginTop="6dip"
- android:layout_marginBottom="6dip"
- android:paddingLeft="10dip"
- android:paddingRight="10dip"
- android:paddingTop="5dip"
- android:paddingBottom="5dip"
- android:gravity="center_vertical|left"
- android:singleLine="true"
- android:ellipsize="end"
- android:editable="false"
- android:focusable="true"
- android:inputType="none"
- android:background="@drawable/textfield_search_empty_google"
- android:textSize="18sp"
- android:textStyle="normal"
- android:textColor="@android:color/primary_text_light"
- android:textColorHint="@color/search_hint"
- />
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:paddingRight="14dip"
+ android:orientation="horizontal"
+ android:background="@drawable/search_floater" >
- <ImageButton
- android:id="@+id/search_widget_voice_btn"
+ <include layout="@layout/corpus_indicator" />
+
+ <TextView
+ android:id="@+id/search_widget_text"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:paddingLeft="10dip"
+ android:paddingRight="10dip"
+ android:paddingTop="5dip"
+ android:paddingBottom="5dip"
+ android:gravity="center_vertical|left"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:editable="false"
+ android:focusable="true"
+ android:inputType="none"
+ android:background="@drawable/textfield_search_empty_google"
+ android:textSize="18sp"
+ android:textStyle="normal"
+ android:textColor="@android:color/primary_text_light"
+ android:textColorHint="@color/search_hint"
+ />
+
+ <ImageButton
+ android:id="@+id/search_widget_voice_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:background="@drawable/btn_search_dialog_voice"
+ android:src="@drawable/ic_btn_speak_now"
+ android:layout_marginRight="-4dip"
+ />
+
+ </LinearLayout>
+
+ <include
+ android:id="@+id/voice_search_hint"
+ layout="@layout/voice_search_hint"
android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:background="@drawable/btn_search_dialog_voice"
- android:src="@drawable/ic_btn_speak_now"
- android:layout_marginRight="-4dip"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
/>
-</LinearLayout>
+</RelativeLayout>
diff --git a/res/layout/suggestion.xml b/res/layout/suggestion.xml
index 1a99fe3..c1864c7 100644
--- a/res/layout/suggestion.xml
+++ b/res/layout/suggestion.xml
@@ -68,6 +68,7 @@
android:textColor="@android:color/primary_text_light"
android:textSize="16sp"
android:singleLine="true"
+ android:ellipsize="start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
diff --git a/res/layout/voice_search_hint.xml b/res/layout/voice_search_hint.xml
new file mode 100644
index 0000000..d43e999
--- /dev/null
+++ b/res/layout/voice_search_hint.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/voice_search_hint_bg"
+ android:orientation="horizontal"
+ android:gravity="center_vertical|left"
+ android:visibility="gone"
+ >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0.0"
+ android:textColor="#88FFFFFF"
+ android:textSize="14dip"
+ android:gravity="left"
+ android:singleLine="true"
+ android:paddingRight="4dip"
+ android:text="@string/voice_search_hint_title"
+ />
+
+ <TextView
+ android:id="@+id/voice_search_hint_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0"
+ android:textColor="@android:color/white"
+ android:textSize="14dip"
+ android:gravity="left"
+ android:singleLine="true"
+ />
+
+ <ImageView
+ android:id="@+id/voice_search_hint_close"
+ android:layout_width="24dip"
+ android:layout_height="24dip"
+ android:layout_weight="0.0"
+ android:paddingLeft="4dip"
+ android:src="@android:drawable/btn_dialog"
+ android:scaleType="centerInside"
+ />
+
+</LinearLayout>
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
new file mode 100644
index 0000000..502f746
--- /dev/null
+++ b/res/values-land/styles.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <!-- The number of columns in the business listing launcher GridView -->
+ <integer name="corpus_selection_dialog_columns">6</integer>
+
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9aada1c..c90ee5d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -25,6 +25,7 @@
<!-- Source selector -->
<string name="corpus_selection_heading">Search</string>
+ <string name="corpus_selection_edit_items">Add or remove searchable items...</string>
<string name="corpus_label_global">All</string>
<!-- Name of the combined Web source shown in QSB -->
@@ -95,6 +96,19 @@
<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 'Voice Search' category of search settings -->
+ <string name="voice_search_category_title">Voice Search</string>
+ <!-- Title and summary for 'show voice search hints' check box setting -->
+ <string name="voice_search_hints_enabled_title">Show Voice Search hints</string>
+ <string name="voice_search_hints_enabled_summary">Shows hints in the search widget for how to use Voice Search</string>
+
+ <!-- Title for Voice Search hints bubble -->
+ <string name="voice_search_hint_title">Try saying:</string>
+ <!-- Starting quotation marks of the voice search hint -->
+ <string name="voice_search_hint_quotation_start">“</string>
+ <!-- Ending quotation marks of the voice search hint -->
+ <string name="voice_search_hint_quotation_end">”</string>
+
<!-- Note that this is the standard search url. It uses the current locale for language -->
<!-- (%1$s) and country (%2$s) and shouldn't need to be replaced by locale or mcc selected -->
<!-- resources. -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 4cec2ac..1dd2adf 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -41,4 +41,7 @@
<item name="android:windowExitAnimation">@anim/corpus_selector_close</item>
</style>
+ <!-- The number of columns in the business listing launcher GridView -->
+ <integer name="corpus_selection_dialog_columns">4</integer>
+
</resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 8f67eae..901f069 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -27,15 +27,11 @@
<PreferenceCategory
android:title="@string/system_search_category_title">
-
+
<PreferenceScreen
- android:title="@string/search_sources"
android:key="search_corpora"
- android:summary="@string/search_sources_summary">
-
- <!-- CheckBoxPreferences added here dynamically -->
-
- </PreferenceScreen>
+ android:title="@string/search_sources"
+ android:summary="@string/search_sources_summary" />
<!-- TODO: Use DialogPreference instead. -->
<Preference
@@ -46,4 +42,16 @@
</PreferenceCategory>
+ <PreferenceCategory
+ android:title="@string/voice_search_category_title">
+
+ <CheckBoxPreference
+ android:key="voice_search_hints_enabled"
+ android:title="@string/voice_search_hints_enabled_title"
+ android:summary="@string/voice_search_hints_enabled_summary"
+ android:defaultValue="true"
+ />
+
+ </PreferenceCategory>
+
</PreferenceScreen>
diff --git a/res/xml/preferences_searchable_items.xml b/res/xml/preferences_searchable_items.xml
new file mode 100644
index 0000000..4a20243
--- /dev/null
+++ b/res/xml/preferences_searchable_items.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/search_sources"
+ android:key="search_corpora"
+ android:summary="@string/search_sources_summary">
+
+ <!-- CheckBoxPreferences added here dynamically -->
+
+</PreferenceScreen>
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionCursor.java b/src/com/android/quicksearchbox/AbstractSuggestionCursor.java
index c00a3c8..7e66aef 100644
--- a/src/com/android/quicksearchbox/AbstractSuggestionCursor.java
+++ b/src/com/android/quicksearchbox/AbstractSuggestionCursor.java
@@ -16,6 +16,11 @@
package com.android.quicksearchbox;
+import android.app.SearchManager;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
/**
* Base class for suggestion cursors.
@@ -32,6 +37,46 @@
return mUserQuery;
}
+ public Intent getSuggestionIntent(Bundle appSearchData) {
+ Source source = getSuggestionSource();
+ String action = getSuggestionIntentAction();
+ // use specific action if supplied, or default action if supplied, or fixed default
+ if (action == null) {
+ action = source.getDefaultIntentAction();
+ if (action == null) {
+ action = Intent.ACTION_SEARCH;
+ }
+ }
+
+ String data = getSuggestionIntentDataString();
+ String query = getSuggestionQuery();
+ String userQuery = getUserQuery();
+ String extraData = getSuggestionIntentExtraData();
+
+ // Now build the Intent
+ Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ // We need CLEAR_TOP to avoid reusing an old task that has other activities
+ // on top of the one we want.
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ if (data != null) {
+ intent.setData(Uri.parse(data));
+ }
+ intent.putExtra(SearchManager.USER_QUERY, userQuery);
+ if (query != null) {
+ intent.putExtra(SearchManager.QUERY, query);
+ }
+ if (extraData != null) {
+ intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
+ }
+ if (appSearchData != null) {
+ intent.putExtra(SearchManager.APP_DATA, appSearchData);
+ }
+
+ intent.setComponent(source.getIntentComponent());
+ return intent;
+ }
+
public String getSuggestionDisplayQuery() {
String query = getSuggestionQuery();
if (query != null) {
diff --git a/src/com/android/quicksearchbox/Corpora.java b/src/com/android/quicksearchbox/Corpora.java
index d92603f..c5eae29 100644
--- a/src/com/android/quicksearchbox/Corpora.java
+++ b/src/com/android/quicksearchbox/Corpora.java
@@ -74,9 +74,9 @@
Corpus getCorpusForSource(Source source);
/**
- * Frees any resources used by the corpus set.
+ * Updates the corpora.
*/
- void close();
+ void update();
/**
* Registers an observer that is called when corpus set changes.
diff --git a/src/com/android/quicksearchbox/CorporaUpdateReceiver.java b/src/com/android/quicksearchbox/CorporaUpdateReceiver.java
new file mode 100644
index 0000000..11163c7
--- /dev/null
+++ b/src/com/android/quicksearchbox/CorporaUpdateReceiver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox;
+
+import android.app.SearchManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Listens for broadcasts that require updates to the corpus set.
+ */
+public class CorporaUpdateReceiver extends BroadcastReceiver {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "QSB.CorporaUpdateReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)
+ || SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED.equals(action)) {
+ if (DBG) Log.d(TAG, "onReceive(" + intent + ")");
+ updateCorpora(context);
+ SearchWidgetProvider.updateSearchWidgets(context);
+ }
+ }
+
+ private void updateCorpora(Context context) {
+ getQsbApplication(context).updateCorpora();
+ }
+
+ private QsbApplication getQsbApplication(Context context) {
+ return (QsbApplication) context.getApplicationContext();
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/CorpusSelectionDialog.java b/src/com/android/quicksearchbox/CorpusSelectionDialog.java
index e15837c..eece6a3 100644
--- a/src/com/android/quicksearchbox/CorpusSelectionDialog.java
+++ b/src/com/android/quicksearchbox/CorpusSelectionDialog.java
@@ -21,6 +21,7 @@
import android.app.Dialog;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
@@ -31,6 +32,7 @@
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.GridView;
+import android.widget.TextView;
/**
* Corpus selection dialog.
@@ -40,10 +42,10 @@
private static final boolean DBG = false;
private static final String TAG = "QSB.SelectSearchSourceDialog";
- private static final int NUM_COLUMNS = 4;
-
private GridView mCorpusGrid;
+ private final int mGridColumns;
+
private OnCorpusSelectedListener mListener;
private Corpus mCorpus;
@@ -52,6 +54,7 @@
public CorpusSelectionDialog(Context context) {
super(context, R.style.Theme_SelectSearchSource);
+ mGridColumns = context.getResources().getInteger(R.integer.corpus_selection_dialog_columns);
}
/**
@@ -72,12 +75,14 @@
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.corpus_selection_dialog);
mCorpusGrid = (GridView) findViewById(R.id.corpus_grid);
- mCorpusGrid.setNumColumns(NUM_COLUMNS);
mCorpusGrid.setOnItemClickListener(new CorpusClickListener());
// TODO: for some reason, putting this in the XML layout instead makes
// the list items unclickable.
mCorpusGrid.setFocusable(true);
+ TextView editItems = (TextView) findViewById(R.id.corpus_edit_items);
+ editItems.setOnClickListener(new CorpusEditListener());
+
Window window = getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
@@ -128,7 +133,7 @@
}
// Dismiss dialog on up move when nothing, or an item on the top row, is selected.
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
- int selectedRow = mCorpusGrid.getSelectedItemPosition() / NUM_COLUMNS;
+ int selectedRow = mCorpusGrid.getSelectedItemPosition() / mGridColumns;
if (selectedRow <= 0) {
cancel();
return true;
@@ -178,7 +183,8 @@
protected void selectCorpus(Corpus corpus) {
dismiss();
if (mListener != null) {
- mListener.onCorpusSelected(corpus);
+ String corpusName = corpus == null ? null : corpus.getName();
+ mListener.onCorpusSelected(corpusName);
}
}
@@ -189,7 +195,14 @@
}
}
+ private class CorpusEditListener implements View.OnClickListener {
+ public void onClick(View v) {
+ Intent intent = SearchSettings.getSearchableItemsIntent(getContext());
+ getContext().startActivity(intent);
+ }
+ }
+
public interface OnCorpusSelectedListener {
- void onCorpusSelected(Corpus corpus);
+ void onCorpusSelected(String corpusName);
}
}
diff --git a/src/com/android/quicksearchbox/Launcher.java b/src/com/android/quicksearchbox/Launcher.java
deleted file mode 100644
index 0f90da8..0000000
--- a/src/com/android/quicksearchbox/Launcher.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quicksearchbox;
-
-import android.app.SearchManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.speech.RecognizerIntent;
-import android.util.Log;
-
-/**
- * Launches suggestions and searches.
- *
- */
-public class Launcher {
-
- private static final String TAG = "Launcher";
-
- private final Context mContext;
-
- /**
- * Data sent by the app that launched QSB.
- */
- public Launcher(Context context) {
- mContext = context;
- }
-
- /**
- * Gets the corpus to use for any searches. This is the web corpus in "All" mode,
- * and the selected corpus otherwise.
- */
- public Corpus getSearchCorpus(Corpora corpora, Corpus selectedCorpus) {
- if (selectedCorpus != null) {
- return selectedCorpus;
- } else {
- Corpus webCorpus = corpora.getWebCorpus();
- if (webCorpus == null) {
- Log.e(TAG, "No web corpus");
- }
- return webCorpus;
- }
- }
-
- public boolean shouldShowVoiceSearch(Corpus corpus) {
- if (corpus != null && !corpus.voiceSearchEnabled()) {
- return false;
- }
- return isVoiceSearchAvailable();
- }
-
- private boolean isVoiceSearchAvailable() {
- Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
- ResolveInfo ri = mContext.getPackageManager().
- resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
- return ri != null;
- }
-
- public Intent getSuggestionIntent(SuggestionCursor cursor, int position,
- Bundle appSearchData) {
- cursor.moveTo(position);
- Source source = cursor.getSuggestionSource();
- String action = cursor.getSuggestionIntentAction();
- // use specific action if supplied, or default action if supplied, or fixed default
- if (action == null) {
- action = source.getDefaultIntentAction();
- if (action == null) {
- action = Intent.ACTION_SEARCH;
- }
- }
-
- String data = cursor.getSuggestionIntentDataString();
- String query = cursor.getSuggestionQuery();
- String userQuery = cursor.getUserQuery();
- String extraData = cursor.getSuggestionIntentExtraData();
-
- // Now build the Intent
- Intent intent = new Intent(action);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // We need CLEAR_TOP to avoid reusing an old task that has other activities
- // on top of the one we want.
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- if (data != null) {
- intent.setData(Uri.parse(data));
- }
- intent.putExtra(SearchManager.USER_QUERY, userQuery);
- if (query != null) {
- intent.putExtra(SearchManager.QUERY, query);
- }
- if (extraData != null) {
- intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
- }
- if (appSearchData != null) {
- intent.putExtra(SearchManager.APP_DATA, appSearchData);
- }
-
- intent.setComponent(cursor.getSuggestionSource().getComponentName());
- return intent;
- }
-
- public void launchIntent(Intent intent) {
- if (intent == null) {
- return;
- }
- try {
- mContext.startActivity(intent);
- } catch (RuntimeException ex) {
- // Since the intents for suggestions specified by suggestion providers,
- // guard against them not being handled, not allowed, etc.
- Log.e(TAG, "Failed to start " + intent.toUri(0), ex);
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/QsbApplication.java b/src/com/android/quicksearchbox/QsbApplication.java
index d565d14..3de143e 100644
--- a/src/com/android/quicksearchbox/QsbApplication.java
+++ b/src/com/android/quicksearchbox/QsbApplication.java
@@ -74,10 +74,6 @@
mConfig.close();
mConfig = null;
}
- if (mCorpora != null) {
- mCorpora.close();
- mCorpora = null;
- }
if (mShortcutRepository != null) {
mShortcutRepository.close();
mShortcutRepository = null;
@@ -99,6 +95,10 @@
return mUiThreadHandler;
}
+ public void runOnUiThread(Runnable action) {
+ getMainThreadHandler().post(action);
+ }
+
/**
* Gets the QSB configuration object.
* May be called from any thread.
@@ -129,12 +129,23 @@
protected Corpora createCorpora() {
SearchableCorpora corpora = new SearchableCorpora(this, getConfig(), createSources(),
createCorpusFactory());
- corpora.load();
+ corpora.update();
return corpora;
}
+ /**
+ * Updates the corpora, if they are loaded.
+ * May only be called from the main thread.
+ */
+ public void updateCorpora() {
+ checkThread();
+ if (mCorpora != null) {
+ mCorpora.update();
+ }
+ }
+
protected Sources createSources() {
- return new SearchableSources(this, getMainThreadHandler());
+ return new SearchableSources(this);
}
protected CorpusFactory createCorpusFactory() {
@@ -262,6 +273,7 @@
promoter,
getShortcutRepository(),
getCorpora(),
+ getCorpusRanker(),
getLogger());
return provider;
}
diff --git a/src/com/android/quicksearchbox/SearchActivity.java b/src/com/android/quicksearchbox/SearchActivity.java
index 1065098..afe7451 100644
--- a/src/com/android/quicksearchbox/SearchActivity.java
+++ b/src/com/android/quicksearchbox/SearchActivity.java
@@ -28,6 +28,7 @@
import android.app.SearchManager;
import android.content.DialogInterface;
import android.content.Intent;
+import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -43,12 +44,12 @@
import android.view.ViewGroup;
import android.view.View.OnFocusChangeListener;
import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsListView;
import android.widget.EditText;
import android.widget.ImageButton;
import java.io.File;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
/**
@@ -87,6 +88,8 @@
protected SuggestionsAdapter mSuggestionsAdapter;
+ private CorporaObserver mCorporaObserver;
+
protected EditText mQueryTextView;
// True if the query was empty on the previous call to updateQuery()
protected boolean mQueryWasEmpty = true;
@@ -98,7 +101,7 @@
protected ImageButton mVoiceSearchButton;
protected ImageButton mCorpusIndicator;
- private Launcher mLauncher;
+ private VoiceSearch mVoiceSearch;
private Corpus mCorpus;
private Bundle mAppSearchData;
@@ -106,14 +109,14 @@
private String mUserQuery;
private boolean mSelectAll;
- private Handler mHandler = new Handler();
- private Runnable mUpdateSuggestionsTask = new Runnable() {
+ private final Handler mHandler = new Handler();
+ private final Runnable mUpdateSuggestionsTask = new Runnable() {
public void run() {
updateSuggestions(getQuery());
}
};
- private Runnable mShowInputMethodTask = new Runnable() {
+ private final Runnable mShowInputMethodTask = new Runnable() {
public void run() {
showInputMethodForQuery();
}
@@ -135,7 +138,7 @@
mSuggestionsView = (SuggestionsView) findViewById(R.id.suggestions);
mSuggestionsView.setSuggestionClickListener(new ClickHandler());
mSuggestionsView.setSuggestionSelectionListener(new SelectionHandler());
- mSuggestionsView.setInteractionListener(new InputMethodCloser());
+ mSuggestionsView.setOnScrollListener(new InputMethodCloser());
mSuggestionsView.setOnKeyListener(new SuggestionsViewKeyListener());
mSuggestionsView.setOnFocusChangeListener(new SuggestListFocusListener());
@@ -147,7 +150,7 @@
mVoiceSearchButton = (ImageButton) findViewById(R.id.search_voice_btn);
mCorpusIndicator = (ImageButton) findViewById(R.id.corpus_indicator);
- mLauncher = new Launcher(this);
+ mVoiceSearch = new VoiceSearch(this);
mQueryTextView.addTextChangedListener(new SearchTextWatcher());
mQueryTextView.setOnKeyListener(new QueryTextViewKeyListener());
@@ -176,6 +179,9 @@
// is called.
mSuggestionsView.setAdapter(mSuggestionsAdapter);
mSuggestionsFooter.setAdapter(mSuggestionsAdapter);
+
+ mCorporaObserver = new CorporaObserver();
+ getCorpora().registerDataSetObserver(mCorporaObserver);
}
private void startMethodTracing() {
@@ -201,7 +207,7 @@
if (savedInstanceState == null) return;
String corpusName = savedInstanceState.getString(INSTANCE_KEY_CORPUS);
String query = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
- setCorpus(getCorpus(corpusName));
+ setCorpus(corpusName);
setUserQuery(query);
}
@@ -211,18 +217,17 @@
// We don't save appSearchData, since we always get the value
// from the intent and the user can't change it.
- String corpusName = mCorpus == null ? null : mCorpus.getName();
- outState.putString(INSTANCE_KEY_CORPUS, corpusName);
+ outState.putString(INSTANCE_KEY_CORPUS, getCorpusName());
outState.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
}
private void setupFromIntent(Intent intent) {
if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")");
- Corpus corpus = getCorpusFromUri(intent.getData());
+ String corpusName = getCorpusNameFromUri(intent.getData());
String query = intent.getStringExtra(SearchManager.QUERY);
Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA);
- setCorpus(corpus);
+ setCorpus(corpusName);
setUserQuery(query);
mSelectAll = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false);
mAppSearchData = appSearchData;
@@ -257,11 +262,10 @@
.build();
}
- private Corpus getCorpusFromUri(Uri uri) {
+ private String getCorpusNameFromUri(Uri uri) {
if (uri == null) return null;
if (!SCHEME_CORPUS.equals(uri.getScheme())) return null;
- String name = uri.getAuthority();
- return getCorpus(name);
+ return uri.getAuthority();
}
private Corpus getCorpus(String sourceName) {
@@ -274,21 +278,25 @@
return corpus;
}
- private void setCorpus(Corpus corpus) {
- if (DBG) Log.d(TAG, "setCorpus(" + corpus + ")");
- mCorpus = corpus;
+ private void setCorpus(String corpusName) {
+ if (DBG) Log.d(TAG, "setCorpus(" + corpusName + ")");
+ mCorpus = getCorpus(corpusName);
Drawable sourceIcon;
- if (corpus == null) {
+ if (mCorpus == null) {
sourceIcon = getCorpusViewFactory().getGlobalSearchIcon();
} else {
- sourceIcon = corpus.getCorpusIcon();
+ sourceIcon = mCorpus.getCorpusIcon();
}
- mSuggestionsAdapter.setCorpus(corpus);
+ mSuggestionsAdapter.setCorpus(mCorpus);
mCorpusIndicator.setImageDrawable(sourceIcon);
updateUi(getQuery().length() == 0);
}
+ private String getCorpusName() {
+ return mCorpus == null ? null : mCorpus.getName();
+ }
+
private QsbApplication getQsbApplication() {
return (QsbApplication) getApplication();
}
@@ -325,6 +333,7 @@
protected void onDestroy() {
if (DBG) Log.d(TAG, "onDestroy()");
super.onDestroy();
+ getCorpora().unregisterDataSetObserver(mCorporaObserver);
mSuggestionsFooter.setAdapter(null);
mSuggestionsView.setAdapter(null); // closes mSuggestionsAdapter
}
@@ -454,7 +463,7 @@
}
protected void updateVoiceSearchButton(boolean queryEmpty) {
- if (queryEmpty && mLauncher.shouldShowVoiceSearch(mCorpus)) {
+ if (queryEmpty && mVoiceSearch.shouldShowVoiceSearch(mCorpus)) {
mVoiceSearchButton.setVisibility(View.VISIBLE);
mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
} else {
@@ -483,15 +492,18 @@
}
}
- protected void onSearchClicked(int method) {
+ /**
+ * @return true if a search was performed as a result of this click, false otherwise.
+ */
+ protected boolean onSearchClicked(int method) {
String query = getQuery();
if (DBG) Log.d(TAG, "Search clicked, query=" + query);
// Don't do empty queries
- if (TextUtils.getTrimmedLength(query) == 0) return;
+ if (TextUtils.getTrimmedLength(query) == 0) return false;
- Corpus searchCorpus = mLauncher.getSearchCorpus(getCorpora(), mCorpus);
- if (searchCorpus == null) return;
+ Corpus searchCorpus = getSearchCorpus();
+ if (searchCorpus == null) return false;
mTookAction = true;
@@ -508,12 +520,13 @@
// Start search
Intent intent = searchCorpus.createSearchIntent(query, mAppSearchData);
- mLauncher.launchIntent(intent);
+ launchIntent(intent);
+ return true;
}
protected void onVoiceSearchClicked() {
if (DBG) Log.d(TAG, "Voice Search clicked");
- Corpus searchCorpus = mLauncher.getSearchCorpus(getCorpora(), mCorpus);
+ Corpus searchCorpus = getSearchCorpus();
if (searchCorpus == null) return;
mTookAction = true;
@@ -523,13 +536,42 @@
// Start voice search
Intent intent = searchCorpus.createVoiceSearchIntent(mAppSearchData);
- mLauncher.launchIntent(intent);
+ launchIntent(intent);
+ }
+
+ /**
+ * Gets the corpus to use for any searches. This is the web corpus in "All" mode,
+ * and the selected corpus otherwise.
+ */
+ private Corpus getSearchCorpus() {
+ if (mCorpus != null) {
+ return mCorpus;
+ } else {
+ Corpus webCorpus = getCorpora().getWebCorpus();
+ if (webCorpus == null) {
+ Log.e(TAG, "No web corpus");
+ }
+ return webCorpus;
+ }
}
protected SuggestionCursor getCurrentSuggestions() {
return mSuggestionsAdapter.getCurrentSuggestions();
}
+ protected void launchIntent(Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ try {
+ startActivity(intent);
+ } catch (RuntimeException ex) {
+ // Since the intents for suggestions specified by suggestion providers,
+ // guard against them not being handled, not allowed, etc.
+ Log.e(TAG, "Failed to start " + intent.toUri(0), ex);
+ }
+ }
+
protected boolean launchSuggestion(int position) {
SuggestionCursor suggestions = getCurrentSuggestions();
if (position < 0 || position >= suggestions.getCount()) {
@@ -548,8 +590,9 @@
getShortcutRepository().reportClick(suggestions, position);
// Launch intent
- Intent intent = mLauncher.getSuggestionIntent(suggestions, position, mAppSearchData);
- mLauncher.launchIntent(intent);
+ suggestions.moveTo(position);
+ Intent intent = suggestions.getSuggestionIntent(mAppSearchData);
+ launchIntent(intent);
return true;
}
@@ -637,14 +680,6 @@
}
}
- private List<Corpus> getCorporaToQuery() {
- if (mCorpus == null) {
- return getCorpusRanker().getRankedCorpora();
- } else {
- return Collections.singletonList(mCorpus);
- }
- }
-
private int getMaxSuggestions() {
Config config = getConfig();
return mCorpus == null
@@ -669,9 +704,8 @@
}
query = ltrim(query);
- List<Corpus> corporaToQuery = getCorporaToQuery();
Suggestions suggestions = getSuggestionsProvider().getSuggestions(
- query, corporaToQuery, getMaxSuggestions());
+ query, mCorpus, getMaxSuggestions());
mSuggestionsAdapter.setSuggestions(suggestions);
}
@@ -729,7 +763,9 @@
public boolean onKey(View view, int keyCode, KeyEvent event) {
// Handle IME search action key
if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
- onSearchClicked(Logger.SEARCH_METHOD_KEYBOARD);
+ // if no action was taken, consume the key event so that the keyboard
+ // remains on screen.
+ return !onSearchClicked(Logger.SEARCH_METHOD_KEYBOARD);
}
return false;
}
@@ -760,8 +796,13 @@
}
}
- private class InputMethodCloser implements SuggestionsView.InteractionListener {
- public void onInteraction() {
+ private class InputMethodCloser implements SuggestionsView.OnScrollListener {
+
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ }
+
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
hideInputMethod();
}
}
@@ -774,6 +815,21 @@
public boolean onSuggestionLongClicked(int position) {
return SearchActivity.this.onSuggestionLongClicked(position);
}
+
+ public void onSuggestionQueryRefineClicked(int position) {
+ if (DBG) Log.d(TAG, "query refine clicked, pos " + position);
+ SuggestionCursor suggestions = getCurrentSuggestions();
+ if (suggestions != null) {
+ suggestions.moveTo(position);
+ String query = suggestions.getSuggestionQuery();
+ if (!TextUtils.isEmpty(query)) {
+ query += " ";
+ setUserQuery(query);
+ setQuery(query, false);
+ updateSuggestions(query);
+ }
+ }
+ }
}
private class SelectionHandler implements SuggestionSelectionListener {
@@ -823,8 +879,8 @@
private class CorpusSelectionListener
implements CorpusSelectionDialog.OnCorpusSelectedListener {
- public void onCorpusSelected(Corpus corpus) {
- setCorpus(corpus);
+ public void onCorpusSelected(String corpusName) {
+ setCorpus(corpusName);
updateSuggestions(getQuery());
mQueryTextView.requestFocus();
showInputMethodForQuery();
@@ -840,6 +896,14 @@
}
}
+ private class CorporaObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ setCorpus(getCorpusName());
+ updateSuggestions(getQuery());
+ }
+ }
+
private static String ltrim(String text) {
int start = 0;
int length = text.length();
diff --git a/src/com/android/quicksearchbox/SearchSettings.java b/src/com/android/quicksearchbox/SearchSettings.java
index 8c63696..73dfb2d 100644
--- a/src/com/android/quicksearchbox/SearchSettings.java
+++ b/src/com/android/quicksearchbox/SearchSettings.java
@@ -30,9 +30,7 @@
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
-import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
-import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.provider.Settings;
import android.provider.Settings.System;
@@ -42,11 +40,10 @@
import java.util.List;
/**
- * Activity for setting global search preferences. Changes to search preferences trigger a broadcast
- * intent that causes all SuggestionSources objects to be updated.
+ * Activity for setting global search preferences.
*/
public class SearchSettings extends PreferenceActivity
- implements OnPreferenceClickListener, OnPreferenceChangeListener {
+ implements OnPreferenceClickListener {
private static final boolean DBG = false;
private static final String TAG = "SearchSettings";
@@ -54,6 +51,10 @@
// Name of the preferences file used to store search preference
public static final String PREFERENCES_NAME = "SearchSettings";
+ // Intent action that opens the "Searchable Items" preference
+ public static final String ACTION_SEARCHABLE_ITEMS =
+ "com.android.quicksearchbox.action.SEARCHABLE_ITEMS";
+
// Only used to find the preferences after inflating
private static final String CLEAR_SHORTCUTS_PREF = "clear_shortcuts";
private static final String SEARCH_ENGINE_SETTINGS_PREF = "search_engine_settings";
@@ -61,11 +62,12 @@
// Preifx of per-corpus enable preference
private static final String CORPUS_ENABLED_PREF_PREFIX = "enable_corpus_";
+ private static final String VOICE_SEARCH_HINTS_ENABLED_PREF = "voice_search_hints_enabled";
// References to the top-level preference objects
private Preference mClearShortcutsPreference;
private PreferenceScreen mSearchEngineSettingsPreference;
- private PreferenceGroup mSourcePreferences;
+ private CheckBoxPreference mVoiceSearchHintsPreference;
// Dialog ids
private static final int CLEAR_SHORTCUTS_CONFIRM_DIALOG = 0;
@@ -82,16 +84,24 @@
mClearShortcutsPreference = preferenceScreen.findPreference(CLEAR_SHORTCUTS_PREF);
mSearchEngineSettingsPreference = (PreferenceScreen) preferenceScreen.findPreference(
SEARCH_ENGINE_SETTINGS_PREF);
- mSourcePreferences = (PreferenceGroup) getPreferenceScreen().findPreference(
- SEARCH_CORPORA_PREF);
+ mVoiceSearchHintsPreference = (CheckBoxPreference)
+ preferenceScreen.findPreference(VOICE_SEARCH_HINTS_ENABLED_PREF);
+ Preference corporaPreference = preferenceScreen.findPreference(SEARCH_CORPORA_PREF);
+ corporaPreference.setIntent(getSearchableItemsIntent(this));
mClearShortcutsPreference.setOnPreferenceClickListener(this);
+ mVoiceSearchHintsPreference.setOnPreferenceClickListener(this);
updateClearShortcutsPreference();
- populateSourcePreference();
populateSearchEnginePreference();
}
+ public static Intent getSearchableItemsIntent(Context context) {
+ Intent intent = new Intent(SearchSettings.ACTION_SEARCHABLE_ITEMS);
+ intent.setPackage(context.getPackageName());
+ return intent;
+ }
+
/**
* Gets the preference key of the preference for whether the given corpus
* is enabled. The preference is stored in the {@link #PREFERENCES_NAME}
@@ -101,6 +111,16 @@
return CORPUS_ENABLED_PREF_PREFIX + corpus.getName();
}
+ public static boolean areVoiceSearchHintsEnabled(Context context) {
+ return getSearchPreferences(context).getBoolean(VOICE_SEARCH_HINTS_ENABLED_PREF, true);
+ }
+
+ public static void setVoiceSearchHintsEnabled(Context context, boolean enabled) {
+ getSearchPreferences(context)
+ .edit().putBoolean(VOICE_SEARCH_HINTS_ENABLED_PREF, enabled).commit();
+ SearchWidgetProvider.updateSearchWidgets(context);
+ }
+
public static SharedPreferences getSearchPreferences(Context context) {
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}
@@ -109,10 +129,6 @@
return (QsbApplication) getApplication();
}
- private Corpora getCorpora() {
- return getQsbApplication().getCorpora();
- }
-
private ShortcutRepository getShortcuts() {
return getQsbApplication().getShortcutRepository();
}
@@ -154,47 +170,13 @@
return resolveInfos.get(0).activityInfo.loadLabel(pm);
}
- /**
- * Fills the suggestion source list.
- */
- private void populateSourcePreference() {
- mSourcePreferences.setOrderingAsAdded(false);
- for (Corpus corpus : getCorpora().getAllCorpora()) {
- Preference pref = createCorpusPreference(corpus);
- if (pref != null) {
- if (DBG) Log.d(TAG, "Adding corpus: " + corpus);
- mSourcePreferences.addPreference(pref);
- }
- }
- }
-
- /**
- * Adds a suggestion source to the list of suggestion source checkbox preferences.
- */
- private Preference createCorpusPreference(Corpus corpus) {
- CheckBoxPreference sourcePref = new CheckBoxPreference(this);
- sourcePref.setKey(getCorpusEnabledPreference(corpus));
- // Put web corpus first. The rest are alphabetical.
- if (corpus.isWebCorpus()) {
- sourcePref.setOrder(0);
- }
- sourcePref.setDefaultValue(getCorpora().isCorpusDefaultEnabled(corpus));
- sourcePref.setOnPreferenceChangeListener(this);
- CharSequence label = corpus.getLabel();
- sourcePref.setTitle(label);
- CharSequence description = corpus.getSettingsDescription();
- sourcePref.setSummaryOn(description);
- sourcePref.setSummaryOff(description);
- return sourcePref;
- }
-
- /**
- * Handles clicks on the "Clear search shortcuts" preference.
- */
public synchronized boolean onPreferenceClick(Preference preference) {
if (preference == mClearShortcutsPreference) {
showDialog(CLEAR_SHORTCUTS_CONFIRM_DIALOG);
return true;
+ } else if (preference == mVoiceSearchHintsPreference) {
+ SearchWidgetProvider.updateSearchWidgets(this);
+ return true;
}
return false;
}
@@ -223,16 +205,11 @@
/**
* Informs our listeners about the updated settings data.
*/
- private void broadcastSettingsChanged() {
+ public static void broadcastSettingsChanged(Context context) {
// We use a message broadcast since the listeners could be in multiple processes.
Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED);
Log.i(TAG, "Broadcasting: " + intent);
- sendBroadcast(intent);
- }
-
- public synchronized boolean onPreferenceChange(Preference preference, Object newValue) {
- broadcastSettingsChanged();
- return true;
+ context.sendBroadcast(intent);
}
public static boolean getShowWebSuggestions(Context context) {
diff --git a/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java b/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
index 17d7d7c..b07b21f 100644
--- a/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
+++ b/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
@@ -78,7 +78,7 @@
protected void selectCorpus(Corpus corpus) {
writeWidgetCorpusPref(mAppWidgetId, corpus);
- updateWidget(corpus);
+ SearchWidgetProvider.updateSearchWidgets(this);
Intent result = new Intent();
result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
@@ -86,12 +86,6 @@
finish();
}
- private void updateWidget(Corpus corpus) {
- AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
- SearchWidgetProvider.setupSearchWidget(this, appWidgetManager,
- mAppWidgetId, corpus);
- }
-
private static SharedPreferences getWidgetPreferences(Context context) {
return context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
}
diff --git a/src/com/android/quicksearchbox/SearchWidgetProvider.java b/src/com/android/quicksearchbox/SearchWidgetProvider.java
index 2dc6b7f..9a0e62c 100644
--- a/src/com/android/quicksearchbox/SearchWidgetProvider.java
+++ b/src/com/android/quicksearchbox/SearchWidgetProvider.java
@@ -17,69 +17,150 @@
package com.android.quicksearchbox;
import com.android.common.Search;
+import com.android.common.speech.Recognition;
import com.android.quicksearchbox.ui.CorpusViewFactory;
+import android.app.Activity;
+import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProvider;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
+import android.os.SystemClock;
+import android.speech.RecognizerIntent;
+import android.text.Annotation;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
+import java.util.ArrayList;
+
/**
* Search widget provider.
*
*/
-public class SearchWidgetProvider extends AppWidgetProvider {
+public class SearchWidgetProvider extends BroadcastReceiver {
private static final boolean DBG = false;
private static final String TAG = "QSB.SearchWidgetProvider";
+ /**
+ * Broadcast intent action for showing the next voice search hint
+ * (if voice search hints are enabled).
+ */
+ private static final String ACTION_NEXT_VOICE_SEARCH_HINT =
+ "com.android.quicksearchbox.action.NEXT_VOICE_SEARCH_HINT";
+
+ /**
+ * Broadcast intent action for disabling voice search hints.
+ */
+ private static final String ACTION_CLOSE_VOICE_SEARCH_HINT =
+ "com.android.quicksearchbox.action.CLOSE_VOICE_SEARCH_HINT";
+
+ /**
+ * Voice search hint update interval in milliseconds.
+ */
+ private static final long VOICE_SEARCH_HINT_UPDATE_INTERVAL
+ = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
+
+ /**
+ * Preference key used for storing the index of the next vocie search hint to show.
+ */
+ private static final String NEXT_VOICE_SEARCH_HINT_INDEX_PREF = "next_voice_search_hint";
+
+ /**
+ * The {@link Search#SOURCE} value used when starting searches from the search widget.
+ */
private static final String WIDGET_SEARCH_SOURCE = "launcher-widget";
@Override
- public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- final int count = appWidgetIds.length;
- for (int i = 0; i < count; i++) {
- updateSearchWidget(context, appWidgetManager, appWidgetIds[i]);
+ 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);
+ } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
+ updateSearchWidgets(context);
}
}
- private void updateSearchWidget(Context context, AppWidgetManager appWidgetManager,
- int appWidgetId) {
- String corpusName = SearchWidgetConfigActivity.readWidgetCorpusPref(context, appWidgetId);
- Corpus corpus = corpusName == null ? null : getCorpora(context).getCorpus(corpusName);
- setupSearchWidget(context, appWidgetManager, appWidgetId, corpus);
+ /**
+ * Updates all search widgets.
+ */
+ public static void updateSearchWidgets(Context context) {
+ updateSearchWidgets(context, true, null);
}
- public static void setupSearchWidget(Context context, AppWidgetManager appWidgetManager,
- int appWidgetId, Corpus corpus) {
- if (DBG) Log.d(TAG, "setupSearchWidget()");
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.search_widget);
+ 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();
+ }
+ if (updateVoiceSearchHint) {
+ scheduleVoiceSearchHintUpdates(context, needsVoiceSearchHint);
+ }
+ }
+
+ /**
+ * Gets the component name of this search widget provider.
+ */
+ private static ComponentName myComponentName(Context context) {
+ String pkg = context.getPackageName();
+ String cls = pkg + ".SearchWidgetProvider";
+ return new ComponentName(pkg, cls);
+ }
+
+ private static SearchWidgetState getSearchWidgetState(Context context,
+ int appWidgetId, CharSequence voiceSearchHint) {
+ String corpusName =
+ SearchWidgetConfigActivity.readWidgetCorpusPref(context, appWidgetId);
+ Corpus corpus = corpusName == null ? null : getCorpora(context).getCorpus(corpusName);
+ if (DBG) {
+ Log.d(TAG, "Updating appwidget " + appWidgetId + ", corpus=" + corpus
+ + ",VS hint=" + voiceSearchHint);
+ }
+ SearchWidgetState state = new SearchWidgetState(appWidgetId);
Bundle widgetAppData = new Bundle();
widgetAppData.putString(Search.SOURCE, WIDGET_SEARCH_SOURCE);
// Corpus indicator
- bindCorpusIndicator(context, views, widgetAppData, corpus);
+ state.setCorpusIconUri(getCorpusIconUri(context, corpus));
- // Hint
- CharSequence hint;
- int backgroundId;
+ Intent corpusIconIntent = new Intent(SearchActivity.INTENT_ACTION_QSB_AND_SELECT_CORPUS);
+ corpusIconIntent.setPackage(context.getPackageName());
+ corpusIconIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ corpusIconIntent.putExtra(SearchManager.APP_DATA, widgetAppData);
+ corpusIconIntent.setData(SearchActivity.getCorpusUri(corpus));
+ state.setCorpusIndicatorIntent(corpusIconIntent);
+
+ // Query text view hint
if (corpus == null || corpus.isWebCorpus()) {
- hint = null;
- backgroundId = R.drawable.textfield_search_empty_google;
+ state.setQueryTextViewBackgroundResource(R.drawable.textfield_search_empty_google);
} else {
- hint = corpus.getHint();
- backgroundId = R.drawable.textfield_search_empty;
+ state.setQueryTextViewHint(corpus.getHint());
+ state.setQueryTextViewBackgroundResource(R.drawable.textfield_search_empty);
}
- views.setCharSequence(R.id.search_widget_text, "setHint", hint);
- views.setInt(R.id.search_widget_text, "setBackgroundResource", backgroundId);
// Text field click
Intent qsbIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
@@ -89,51 +170,31 @@
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
qsbIntent.putExtra(SearchManager.APP_DATA, widgetAppData);
qsbIntent.setData(SearchActivity.getCorpusUri(corpus));
- setOnClickIntent(context, views, R.id.search_widget_text, qsbIntent);
+ state.setQueryTextViewIntent(qsbIntent);
+ // Voice search button
Intent voiceSearchIntent = getVoiceSearchIntent(context, corpus, widgetAppData);
- if (voiceSearchIntent != null) {
- setOnClickIntent(context, views, R.id.search_widget_voice_btn, voiceSearchIntent);
- views.setViewVisibility(R.id.search_widget_voice_btn, View.VISIBLE);
- } else {
- views.setViewVisibility(R.id.search_widget_voice_btn, View.GONE);
+ state.setVoiceSearchIntent(voiceSearchIntent);
+ if (voiceSearchIntent != null
+ && RecognizerIntent.ACTION_WEB_SEARCH.equals(voiceSearchIntent.getAction())) {
+ state.setShouldShowVoiceSearchHint(true);
+ state.setVoiceSearchHint(formatVoiceSearchHint(context, voiceSearchHint));
}
- appWidgetManager.updateAppWidget(appWidgetId, views);
- }
-
- private static void bindCorpusIndicator(Context context, RemoteViews views,
- Bundle widgetAppData, Corpus corpus) {
- Uri sourceIconUri = getCorpusIconUri(context, corpus);
- views.setImageViewUri(R.id.corpus_indicator, sourceIconUri);
-
- Intent intent = new Intent(SearchActivity.INTENT_ACTION_QSB_AND_SELECT_CORPUS);
- intent.setPackage(context.getPackageName());
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_CLEAR_TOP
- | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- intent.putExtra(SearchManager.APP_DATA, widgetAppData);
- intent.setData(SearchActivity.getCorpusUri(corpus));
- setOnClickIntent(context, views, R.id.corpus_indicator, intent);
+ return state;
}
private static Intent getVoiceSearchIntent(Context context, Corpus corpus,
Bundle widgetAppData) {
- Launcher launcher = new Launcher(context);
- if (!launcher.shouldShowVoiceSearch(corpus)) return null;
+ VoiceSearch voiceSearch = new VoiceSearch(context);
+ if (!voiceSearch.shouldShowVoiceSearch(corpus)) return null;
if (corpus == null) {
- return WebCorpus.createVoiceWebSearchIntent(widgetAppData);
+ return voiceSearch.createVoiceWebSearchIntent(widgetAppData);
} else {
return corpus.createVoiceSearchIntent(widgetAppData);
}
}
- private static void setOnClickIntent(Context context, RemoteViews views,
- int viewId, Intent intent) {
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
- views.setOnClickPendingIntent(viewId, pendingIntent);
- }
-
private static Uri getCorpusIconUri(Context context, Corpus corpus) {
if (corpus == null) {
return getCorpusViewFactory(context).getGlobalSearchIconUri();
@@ -141,6 +202,94 @@
return corpus.getCorpusIconUri();
}
+ private static CharSequence formatVoiceSearchHint(Context context, CharSequence hint) {
+ if (TextUtils.isEmpty(hint)) return null;
+ SpannableStringBuilder spannedHint = new SpannableStringBuilder(
+ context.getString(R.string.voice_search_hint_quotation_start));
+ spannedHint.append(hint);
+ Object[] items = spannedHint.getSpans(0, spannedHint.length(), Object.class);
+ for (Object item : items) {
+ if (item instanceof Annotation) {
+ Annotation annotation = (Annotation) item;
+ if (annotation.getKey().equals("action")
+ && annotation.getValue().equals("true")) {
+ final int start = spannedHint.getSpanStart(annotation);
+ final int end = spannedHint.getSpanEnd(annotation);
+ spannedHint.removeSpan(item);
+ spannedHint.setSpan(new StyleSpan(Typeface.BOLD), start, end, 0);
+ }
+ }
+ }
+ spannedHint.append(context.getString(R.string.voice_search_hint_quotation_end));
+ return spannedHint;
+ }
+
+ 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 && SearchSettings.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);
+ }
+ }
+
+ /**
+ * Requests an asynchronous update of the voice search hints.
+ */
+ private static void getHintsFromVoiceSearch(Context context) {
+ if (!SearchSettings.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);
+ }
+
+ private static class HintReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (getResultCode() != Activity.RESULT_OK) {
+ return;
+ }
+ ArrayList<CharSequence> hints = getResultExtras(true)
+ .getCharSequenceArrayList(Recognition.EXTRA_HINT_STRINGS);
+ CharSequence hint = getNextHint(context, hints);
+ updateSearchWidgets(context, false, hint);
+ }
+ }
+
+ /**
+ * Gets the next formatted hint, if there are any hints.
+ * Must be called on the application main thread.
+ *
+ * @return A hint, or {@code null} if no hints are available.
+ */
+ private static CharSequence getNextHint(Context context, ArrayList<CharSequence> hints) {
+ if (hints == null || hints.isEmpty()) return null;
+ int i = getNextVoiceSearchHintIndex(context, hints.size());
+ return hints.get(i);
+ }
+
+ private static int getNextVoiceSearchHintIndex(Context context, int size) {
+ int i = getAndIncrementIntPreference(
+ SearchSettings.getSearchPreferences(context),
+ NEXT_VOICE_SEARCH_HINT_INDEX_PREF);
+ return i % size;
+ }
+
+ // TODO: Could this be made atomic to avoid races?
+ private static int getAndIncrementIntPreference(SharedPreferences prefs, String name) {
+ int i = prefs.getInt(name, 0);
+ prefs.edit().putInt(name, i + 1).commit();
+ return i;
+ }
+
private static QsbApplication getQsbApplication(Context context) {
return (QsbApplication) context.getApplicationContext();
}
@@ -153,4 +302,109 @@
return getQsbApplication(context).getCorpusViewFactory();
}
+ private static class SearchWidgetState {
+ private final int mAppWidgetId;
+ private Uri mCorpusIconUri;
+ private Intent mCorpusIndicatorIntent;
+ private CharSequence mQueryTextViewHint;
+ private int mQueryTextViewBackgroundResource;
+ private Intent mQueryTextViewIntent;
+ private Intent mVoiceSearchIntent;
+ private boolean mShouldShowVoiceSearchHint;
+ private CharSequence mVoiceSearchHint;
+
+ public SearchWidgetState(int appWidgetId) {
+ mAppWidgetId = appWidgetId;
+ }
+
+ public boolean shouldShowVoiceSearchHint() {
+ return mShouldShowVoiceSearchHint;
+ }
+
+ public void setShouldShowVoiceSearchHint(boolean shouldShowVoiceSearchHint) {
+ mShouldShowVoiceSearchHint = shouldShowVoiceSearchHint;
+ }
+
+ public void setCorpusIconUri(Uri corpusIconUri) {
+ mCorpusIconUri = corpusIconUri;
+ }
+
+ public void setCorpusIndicatorIntent(Intent corpusIndicatorIntent) {
+ mCorpusIndicatorIntent = corpusIndicatorIntent;
+ }
+
+ public void setQueryTextViewHint(CharSequence queryTextViewHint) {
+ mQueryTextViewHint = queryTextViewHint;
+ }
+
+ public void setQueryTextViewBackgroundResource(int queryTextViewBackgroundResource) {
+ mQueryTextViewBackgroundResource = queryTextViewBackgroundResource;
+ }
+
+ public void setQueryTextViewIntent(Intent queryTextViewIntent) {
+ mQueryTextViewIntent = queryTextViewIntent;
+ }
+
+ public void setVoiceSearchIntent(Intent voiceSearchIntent) {
+ mVoiceSearchIntent = voiceSearchIntent;
+ }
+
+ public void setVoiceSearchHint(CharSequence voiceSearchHint) {
+ mVoiceSearchHint = voiceSearchHint;
+ }
+
+ public void updateWidget(Context context, AppWidgetManager appWidgetManager) {
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.search_widget);
+ // Corpus indicator
+ views.setImageViewUri(R.id.corpus_indicator, mCorpusIconUri);
+ setOnClickActivityIntent(context, views, R.id.corpus_indicator,
+ mCorpusIndicatorIntent);
+ // Query TextView
+ views.setCharSequence(R.id.search_widget_text, "setHint", mQueryTextViewHint);
+ views.setInt(R.id.search_widget_text, "setBackgroundResource",
+ mQueryTextViewBackgroundResource);
+ setOnClickActivityIntent(context, views, R.id.search_widget_text,
+ mQueryTextViewIntent);
+ // Voice Search button
+ if (mVoiceSearchIntent != null) {
+ setOnClickActivityIntent(context, views, R.id.search_widget_voice_btn,
+ mVoiceSearchIntent);
+ views.setViewVisibility(R.id.search_widget_voice_btn, View.VISIBLE);
+ } else {
+ views.setViewVisibility(R.id.search_widget_voice_btn, View.GONE);
+ }
+ // Voice Search hints
+ if (mShouldShowVoiceSearchHint && !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));
+ setOnClickBroadcastIntent(context, views, R.id.voice_search_hint_close,
+ closeHintIntent);
+
+ views.setViewVisibility(R.id.voice_search_hint, View.VISIBLE);
+ } else {
+ views.setViewVisibility(R.id.voice_search_hint, View.GONE);
+ }
+ appWidgetManager.updateAppWidget(mAppWidgetId, views);
+ }
+
+ private void setOnClickBroadcastIntent(Context context, RemoteViews views, int viewId,
+ Intent intent) {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
+ views.setOnClickPendingIntent(viewId, pendingIntent);
+ }
+
+ private void setOnClickActivityIntent(Context context, RemoteViews views, int viewId,
+ Intent intent) {
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+ views.setOnClickPendingIntent(viewId, pendingIntent);
+ }
+ }
+
}
diff --git a/src/com/android/quicksearchbox/SearchableCorpora.java b/src/com/android/quicksearchbox/SearchableCorpora.java
index 4a38d81..b3d38f0 100644
--- a/src/com/android/quicksearchbox/SearchableCorpora.java
+++ b/src/com/android/quicksearchbox/SearchableCorpora.java
@@ -20,6 +20,7 @@
import android.content.SharedPreferences;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
+import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
@@ -29,7 +30,7 @@
import java.util.List;
/**
- * Maintains the list of all suggestion sources.
+ * Maintains the list of all corpora.
*/
public class SearchableCorpora implements Corpora {
@@ -44,8 +45,6 @@
private final CorpusFactory mCorpusFactory;
private final SharedPreferences mPreferences;
- private boolean mLoaded = false;
-
private Sources mSources;
// Maps corpus names to corpora
private HashMap<String,Corpus> mCorporaByName;
@@ -73,24 +72,15 @@
return mContext;
}
- private void checkLoaded() {
- if (!mLoaded) {
- throw new IllegalStateException("corpora not loaded.");
- }
- }
-
public Collection<Corpus> getAllCorpora() {
- checkLoaded();
return Collections.unmodifiableCollection(mCorporaByName.values());
}
public Collection<Corpus> getEnabledCorpora() {
- checkLoaded();
return mEnabledCorpora;
}
public Corpus getCorpus(String name) {
- checkLoaded();
return mCorporaByName.get(name);
}
@@ -99,48 +89,20 @@
}
public Corpus getCorpusForSource(Source source) {
- checkLoaded();
return mCorporaBySource.get(source);
}
public Source getSource(String name) {
- checkLoaded();
+ if (TextUtils.isEmpty(name)) {
+ Log.w(TAG, "Empty source name");
+ return null;
+ }
return mSources.getSource(name);
}
- /**
- * After calling, clients must call {@link #close()} when done with this object.
- */
- public void load() {
- if (mLoaded) {
- throw new IllegalStateException("load(): Already loaded.");
- }
+ public void update() {
+ mSources.update();
- mSources.registerDataSetObserver(new DataSetObserver() {
- @Override
- public void onChanged() {
- updateCorpora();
- }
- });
-
- // will cause a callback to updateCorpora()
- mSources.load();
- mLoaded = true;
- }
-
- /**
- * Releases all resources used by this object. It is possible to call
- * {@link #load()} again after calling this method.
- */
- public void close() {
- checkLoaded();
-
- mSources.close();
- mSources = null;
- mLoaded = false;
- }
-
- private void updateCorpora() {
Collection<Corpus> corpora = mCorpusFactory.createCorpora(mSources);
mCorporaByName = new HashMap<String,Corpus>(corpora.size());
diff --git a/src/com/android/quicksearchbox/SearchableCorpusFactory.java b/src/com/android/quicksearchbox/SearchableCorpusFactory.java
index 45a1831..4d34fc5 100644
--- a/src/com/android/quicksearchbox/SearchableCorpusFactory.java
+++ b/src/com/android/quicksearchbox/SearchableCorpusFactory.java
@@ -19,6 +19,7 @@
import com.android.quicksearchbox.util.Factory;
import android.content.Context;
+import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
@@ -30,6 +31,8 @@
*/
public class SearchableCorpusFactory implements CorpusFactory {
+ private static final String TAG = "QSB.SearchableCorpusFactory";
+
private final Context mContext;
private final Factory<Executor> mWebCorpusExecutorFactory;
@@ -61,8 +64,8 @@
* @param sources All available sources.
*/
protected void addSpecialCorpora(ArrayList<Corpus> corpora, Sources sources) {
- corpora.add(createWebCorpus(sources));
- corpora.add(createAppsCorpus(sources));
+ addCorpus(corpora, createWebCorpus(sources));
+ addCorpus(corpora, createAppsCorpus(sources));
}
/**
@@ -82,14 +85,26 @@
// Creates corpora for all unclaimed sources
for (Source source : sources.getSources()) {
if (!claimedSources.contains(source)) {
- corpora.add(createSingleSourceCorpus(source));
+ addCorpus(corpora, createSingleSourceCorpus(source));
}
}
}
+ private void addCorpus(ArrayList<Corpus> corpora, Corpus corpus) {
+ if (corpus != null) corpora.add(corpus);
+ }
+
protected Corpus createWebCorpus(Sources sources) {
Source webSource = sources.getWebSearchSource();
+ if (webSource != null && !webSource.canRead()) {
+ Log.w(TAG, "Can't read web source " + webSource.getName());
+ webSource = null;
+ }
Source browserSource = getBrowserSource(sources);
+ if (browserSource != null && !browserSource.canRead()) {
+ Log.w(TAG, "Can't read browser source " + browserSource.getName());
+ browserSource = null;
+ }
Executor executor = createWebCorpusExecutor();
return new WebCorpus(mContext, executor, webSource, browserSource);
}
@@ -100,6 +115,7 @@
}
protected Corpus createSingleSourceCorpus(Source source) {
+ if (!source.canRead()) return null;
return new SingleSourceCorpus(mContext, source);
}
diff --git a/src/com/android/quicksearchbox/SearchableItemsSettings.java b/src/com/android/quicksearchbox/SearchableItemsSettings.java
new file mode 100644
index 0000000..5dc4cf8
--- /dev/null
+++ b/src/com/android/quicksearchbox/SearchableItemsSettings.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox;
+
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.util.Log;
+
+/**
+ * Activity for selecting searchable items.
+ */
+public class SearchableItemsSettings extends PreferenceActivity
+ implements OnPreferenceChangeListener {
+
+ private static final boolean DBG = false;
+ private static final String TAG = "QSB.SearchableItemsSettings";
+
+ // Only used to find the preferences after inflating
+ private static final String SEARCH_CORPORA_PREF = "search_corpora";
+
+ // References to the top-level preference objects
+ private PreferenceGroup mCorporaPreferences;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getPreferenceManager().setSharedPreferencesName(SearchSettings.PREFERENCES_NAME);
+
+ addPreferencesFromResource(R.xml.preferences_searchable_items);
+
+ mCorporaPreferences = (PreferenceGroup) getPreferenceScreen().findPreference(
+ SEARCH_CORPORA_PREF);
+
+ populateSourcePreference();
+ }
+
+ private QsbApplication getQsbApplication() {
+ return (QsbApplication) getApplication();
+ }
+
+ private Corpora getCorpora() {
+ return getQsbApplication().getCorpora();
+ }
+
+ /**
+ * Fills the suggestion source list.
+ */
+ private void populateSourcePreference() {
+ mCorporaPreferences.setOrderingAsAdded(false);
+ for (Corpus corpus : getCorpora().getAllCorpora()) {
+ Preference pref = createCorpusPreference(corpus);
+ if (pref != null) {
+ if (DBG) Log.d(TAG, "Adding corpus: " + corpus);
+ mCorporaPreferences.addPreference(pref);
+ }
+ }
+ }
+
+ /**
+ * Adds a suggestion source to the list of suggestion source checkbox preferences.
+ */
+ private Preference createCorpusPreference(Corpus corpus) {
+ CheckBoxPreference sourcePref = new CheckBoxPreference(this);
+ sourcePref.setKey(SearchSettings.getCorpusEnabledPreference(corpus));
+ // Put web corpus first. The rest are alphabetical.
+ if (corpus.isWebCorpus()) {
+ sourcePref.setOrder(0);
+ }
+ sourcePref.setDefaultValue(getCorpora().isCorpusDefaultEnabled(corpus));
+ sourcePref.setOnPreferenceChangeListener(this);
+ CharSequence label = corpus.getLabel();
+ sourcePref.setTitle(label);
+ CharSequence description = corpus.getSettingsDescription();
+ sourcePref.setSummaryOn(description);
+ sourcePref.setSummaryOff(description);
+ return sourcePref;
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ SearchSettings.broadcastSettingsChanged(this);
+ return true;
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/SearchableSource.java b/src/com/android/quicksearchbox/SearchableSource.java
index a5c8e96..aa00c21 100644
--- a/src/com/android/quicksearchbox/SearchableSource.java
+++ b/src/com/android/quicksearchbox/SearchableSource.java
@@ -69,7 +69,7 @@
// Cached icon for the activity
private Drawable.ConstantState mSourceIcon = null;
- private final IconLoader mIconLoader;
+ private IconLoader mIconLoader;
public SearchableSource(Context context, SearchableInfo searchable)
throws NameNotFoundException {
@@ -81,7 +81,6 @@
mActivityInfo = pm.getActivityInfo(componentName, 0);
PackageInfo pkgInfo = pm.getPackageInfo(componentName.getPackageName(), 0);
mVersionCode = pkgInfo.versionCode;
- mIconLoader = createIconLoader(context, searchable.getSuggestPackage());
}
protected Context getContext() {
@@ -98,8 +97,9 @@
public boolean canRead() {
String authority = mSearchable.getSuggestAuthority();
if (authority == null) {
- Log.w(TAG, getName() + " has no searchSuggestAuthority");
- return false;
+ // TODO: maybe we should have a way to distinguish between having suggestions
+ // and being readable.
+ return true;
}
Uri.Builder uriBuilder = new Uri.Builder()
@@ -161,12 +161,20 @@
return false;
}
- private IconLoader createIconLoader(Context context, String providerPackage) {
- if (providerPackage == null) return null;
- return new CachingIconLoader(new PackageIconLoader(context, providerPackage));
+ private IconLoader getIconLoader() {
+ if (mIconLoader == null) {
+ // Get icons from the package containing the suggestion provider, if any
+ String iconPackage = mSearchable.getSuggestPackage();
+ if (iconPackage == null) {
+ // Fall back to the package containing the searchable activity
+ iconPackage = mSearchable.getSearchActivity().getPackageName();
+ }
+ mIconLoader = new CachingIconLoader(new PackageIconLoader(mContext, iconPackage));
+ }
+ return mIconLoader;
}
- public ComponentName getComponentName() {
+ public ComponentName getIntentComponent() {
return mSearchable.getSearchActivity();
}
@@ -179,11 +187,11 @@
}
public Drawable getIcon(String drawableId) {
- return mIconLoader == null ? null : mIconLoader.getIcon(drawableId);
+ return getIconLoader().getIcon(drawableId);
}
public Uri getIconUri(String drawableId) {
- return mIconLoader == null ? null : mIconLoader.getIconUri(drawableId);
+ return getIconLoader().getIconUri(drawableId);
}
public CharSequence getLabel() {
@@ -236,7 +244,7 @@
}
public Intent createSearchIntent(String query, Bundle appData) {
- return createSourceSearchIntent(getComponentName(), query, appData);
+ return createSourceSearchIntent(getIntentComponent(), query, appData);
}
public static Intent createSourceSearchIntent(ComponentName activity, String query,
@@ -261,7 +269,7 @@
public Intent createVoiceSearchIntent(Bundle appData) {
if (mSearchable.getVoiceSearchLaunchWebSearch()) {
- return WebCorpus.createVoiceWebSearchIntent(appData);
+ return new VoiceSearch(mContext).createVoiceWebSearchIntent(appData);
} else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
return createVoiceAppSearchIntent(appData);
}
diff --git a/src/com/android/quicksearchbox/SearchableSources.java b/src/com/android/quicksearchbox/SearchableSources.java
index a8f0e62..315b382 100644
--- a/src/com/android/quicksearchbox/SearchableSources.java
+++ b/src/com/android/quicksearchbox/SearchableSources.java
@@ -18,16 +18,11 @@
import android.app.SearchManager;
import android.app.SearchableInfo;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.DataSetObservable;
-import android.database.DataSetObserver;
-import android.os.Handler;
import android.util.Log;
import java.util.Collection;
@@ -43,15 +38,8 @@
private static final boolean DBG = false;
private static final String TAG = "QSB.SearchableSources";
- // The number of milliseconds that source update requests are delayed to
- // allow grouping multiple requests.
- private static final long UPDATE_SOURCES_DELAY_MILLIS = 200;
-
- private final DataSetObservable mDataSetObservable = new DataSetObservable();
-
private final Context mContext;
private final SearchManager mSearchManager;
- private boolean mLoaded;
// All suggestion sources, by name.
private HashMap<String, Source> mSources;
@@ -59,31 +47,16 @@
// The web search source to use.
private Source mWebSearchSource;
- private final Handler mUiThread;
-
- private Runnable mUpdateSources = new Runnable() {
- public void run() {
- mUiThread.removeCallbacks(this);
- updateSources();
- notifyDataSetChanged();
- }
- };
-
/**
*
* @param context Used for looking up source information etc.
*/
- public SearchableSources(Context context, Handler uiThread) {
+ public SearchableSources(Context context) {
mContext = context;
- mUiThread = uiThread;
mSearchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
- mLoaded = false;
}
public Collection<Source> getSources() {
- if (!mLoaded) {
- throw new IllegalStateException("getSources(): sources not loaded.");
- }
return mSources.values();
}
@@ -92,62 +65,14 @@
}
public Source getWebSearchSource() {
- if (!mLoaded) {
- throw new IllegalStateException("getWebSearchSource(): sources not loaded.");
- }
return mWebSearchSource;
}
- // Broadcast receiver for package change notifications
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)
- || SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED.equals(action)) {
- if (DBG) Log.d(TAG, "onReceive(" + intent + ")");
- // TODO: Instead of rebuilding the whole list on every change,
- // just add, remove or update the application that has changed.
- // Adding and updating seem tricky, since I can't see an easy way to list the
- // launchable activities in a given package.
- mUiThread.postDelayed(mUpdateSources, UPDATE_SOURCES_DELAY_MILLIS);
- }
- }
- };
-
- public void load() {
- if (mLoaded) {
- throw new IllegalStateException("load(): Already loaded.");
- }
-
- // Listen for searchables changes.
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
- intentFilter.addAction(SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED);
- mContext.registerReceiver(mBroadcastReceiver, intentFilter);
-
- // update list of sources
- updateSources();
-
- mLoaded = true;
-
- notifyDataSetChanged();
- }
-
- public void close() {
- mContext.unregisterReceiver(mBroadcastReceiver);
-
- mDataSetObservable.unregisterAll();
-
- mSources = null;
- mLoaded = false;
- }
-
/**
- * Loads the list of suggestion sources.
+ * Updates the list of suggestion sources.
*/
- private void updateSources() {
- if (DBG) Log.d(TAG, "updateSources()");
+ public void update() {
+ if (DBG) Log.d(TAG, "update()");
mSources = new HashMap<String,Source>();
addSearchableSources();
@@ -164,7 +89,7 @@
}
for (SearchableInfo searchable : searchables) {
SearchableSource source = createSearchableSource(searchable);
- if (source != null && source.canRead()) {
+ if (source != null) {
if (DBG) Log.d(TAG, "Created source " + source);
addSource(source);
}
@@ -204,16 +129,4 @@
return null;
}
}
-
- public void registerDataSetObserver(DataSetObserver observer) {
- mDataSetObservable.registerObserver(observer);
- }
-
- public void unregisterDataSetObserver(DataSetObserver observer) {
- mDataSetObservable.unregisterObserver(observer);
- }
-
- protected void notifyDataSetChanged() {
- mDataSetObservable.notifyChanged();
- }
}
diff --git a/src/com/android/quicksearchbox/ShortcutRepository.java b/src/com/android/quicksearchbox/ShortcutRepository.java
index fbbe155..bfc24ef 100644
--- a/src/com/android/quicksearchbox/ShortcutRepository.java
+++ b/src/com/android/quicksearchbox/ShortcutRepository.java
@@ -16,7 +16,7 @@
package com.android.quicksearchbox;
-import java.util.List;
+import java.util.Collection;
import java.util.Map;
/**
@@ -54,7 +54,7 @@
* @param maxShortcuts The maximum number of shortcuts to return.
* @return A cursor containing shortcutted results for the query.
*/
- SuggestionCursor getShortcutsForQuery(String query, List<Corpus> allowedCorpora,
+ SuggestionCursor getShortcutsForQuery(String query, Collection<Corpus> allowedCorpora,
int maxShortcuts);
/**
diff --git a/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java b/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java
index ea8150c..1331afb 100644
--- a/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java
+++ b/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java
@@ -34,8 +34,8 @@
import android.util.Log;
import java.io.File;
+import java.util.Collection;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -223,7 +223,7 @@
reportClickAtTime(suggestions, position, now);
}
- public SuggestionCursor getShortcutsForQuery(String query, List<Corpus> allowedCorpora,
+ public SuggestionCursor getShortcutsForQuery(String query, Collection<Corpus> allowedCorpora,
int maxShortcuts) {
ShortcutCursor shortcuts = getShortcutsForQuery(query, allowedCorpora, maxShortcuts,
System.currentTimeMillis());
@@ -245,7 +245,7 @@
}
/* package for testing */ ShortcutCursor getShortcutsForQuery(String query,
- List<Corpus> allowedCorpora, int maxShortcuts, long now) {
+ Collection<Corpus> allowedCorpora, int maxShortcuts, long now) {
if (DBG) Log.d(TAG, "getShortcutsForQuery(" + query + "," + allowedCorpora + ")");
String sql = query.length() == 0 ? mEmptyQueryShortcutQuery : mShortcutQuery;
String[] params = buildShortcutQueryParams(query, now);
diff --git a/src/com/android/quicksearchbox/ShortcutsProvider.java b/src/com/android/quicksearchbox/ShortcutsProvider.java
new file mode 100644
index 0000000..acc1822
--- /dev/null
+++ b/src/com/android/quicksearchbox/ShortcutsProvider.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quicksearchbox;
+
+import android.app.SearchManager;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Binder;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Handles broadcast intents for adding shortcuts to QSB.
+ */
+public class ShortcutsProvider extends ContentProvider {
+
+ private static final boolean DBG = true;
+ private static final String TAG = "QSB.ExternalShortcutReceiver";
+
+ public static final String EXTRA_SHORTCUT_SOURCE = "shortcut_source";
+
+ private static final int URI_CODE_SHORTCUTS = 0;
+
+ private UriMatcher mUriMatcher;
+
+ @Override
+ public boolean onCreate() {
+ mUriMatcher = buildUriMatcher();
+ return true;
+ }
+
+ private UriMatcher buildUriMatcher() {
+ String authority = getAuthority();
+ UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
+ matcher.addURI(authority, "shortcuts", URI_CODE_SHORTCUTS);
+ return matcher;
+ }
+
+ private String getAuthority() {
+ return getContext().getPackageName() + ".shortcuts";
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ switch (mUriMatcher.match(uri)) {
+ case URI_CODE_SHORTCUTS:
+ return SearchManager.SUGGEST_MIME_TYPE;
+ default:
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ switch (mUriMatcher.match(uri)) {
+ case URI_CODE_SHORTCUTS:
+ addShortcut(values);
+ return null;
+ default:
+ throw new IllegalArgumentException("Unknown URI: " + uri);
+ }
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ private void addShortcut(final ContentValues shortcut) {
+ String sourceName = shortcut.getAsString(EXTRA_SHORTCUT_SOURCE);
+ if (TextUtils.isEmpty(sourceName)) {
+ Log.e(TAG, "Missing " + EXTRA_SHORTCUT_SOURCE);
+ return;
+ }
+ final ComponentName sourceComponent = ComponentName.unflattenFromString(sourceName);
+ if (!checkCallingPackage(sourceComponent.getPackageName())) {
+ Log.w(TAG, "Got shortcut for " + sourceComponent + " from a different process");
+ return;
+ }
+
+ getQsbApplication().runOnUiThread(new Runnable() {
+ public void run() {
+ storeShortcut(sourceComponent, shortcut);
+ }
+ });
+ }
+
+ // Called on the main thread
+ private void storeShortcut(ComponentName sourceComponent, ContentValues shortcut) {
+ if (DBG) Log.d(TAG, "Adding (PID: " + Binder.getCallingPid() + "): " + shortcut);
+
+ Source source = getCorpora().getSource(sourceComponent.flattenToShortString());
+ if (source == null) {
+ Log.w(TAG, "Unknown shortcut source " + sourceComponent);
+ return;
+ }
+
+ String userQuery = shortcut.getAsString(SearchManager.USER_QUERY);
+ if (userQuery == null) userQuery = "";
+
+ DataSuggestionCursor cursor = new DataSuggestionCursor(userQuery);
+ cursor.add(makeSuggestion(source, shortcut));
+ getShortcutRepository().reportClick(cursor, 0);
+ }
+
+ private boolean checkCallingPackage(String packageName) {
+ int callingUid = Binder.getCallingUid();
+ PackageManager pm = getContext().getPackageManager();
+ String[] uidPkgs = pm.getPackagesForUid(callingUid);
+ if (uidPkgs == null) return false;
+ for (String uidPkg : uidPkgs) {
+ if (packageName.equals(uidPkg)) return true;
+ }
+ return false;
+ }
+
+ private SuggestionData makeSuggestion(Source source, ContentValues shortcut) {
+ String format = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_FORMAT);
+ String text1 = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_TEXT_1);
+ String text2 = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_TEXT_2);
+ String text2Url = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
+ String icon1 = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_ICON_1);
+ String icon2 = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_ICON_2);
+ String shortcutId = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
+ boolean spinnerWhileRefreshing = unboxBoolean(
+ shortcut.getAsBoolean(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING),
+ false);
+ String intentAction = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
+ String intentData = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
+ String intentExtraData =
+ shortcut.getAsString(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
+ String query = shortcut.getAsString(SearchManager.SUGGEST_COLUMN_QUERY);
+
+ SuggestionData suggestion = new SuggestionData(source);
+ suggestion.setFormat(format);
+ suggestion.setText1(text1);
+ suggestion.setText2(text2);
+ suggestion.setText2Url(text2Url);
+ suggestion.setIcon1(icon1);
+ suggestion.setIcon2(icon2);
+ suggestion.setShortcutId(shortcutId);
+ suggestion.setSpinnerWhileRefreshing(spinnerWhileRefreshing);
+ suggestion.setIntentAction(intentAction);
+ suggestion.setIntentData(intentData);
+ suggestion.setIntentExtraData(intentExtraData);
+ suggestion.setSuggestionQuery(query);
+ return suggestion;
+ }
+
+ private static boolean unboxBoolean(Boolean value, boolean defValue) {
+ return value == null ? defValue : value;
+ }
+
+ private QsbApplication getQsbApplication() {
+ return (QsbApplication) getContext().getApplicationContext();
+ }
+
+ private ShortcutRepository getShortcutRepository() {
+ return getQsbApplication().getShortcutRepository();
+ }
+
+ private Corpora getCorpora() {
+ return getQsbApplication().getCorpora();
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/Source.java b/src/com/android/quicksearchbox/Source.java
index 04cab39..f32ca15 100644
--- a/src/com/android/quicksearchbox/Source.java
+++ b/src/com/android/quicksearchbox/Source.java
@@ -29,10 +29,9 @@
public interface Source extends SuggestionCursorProvider<SourceResult> {
/**
- * Gets the name of the activity that this source is for. When a suggestion is
- * clicked, the resulting intent will be sent to this activity.
+ * Gets the name activity that intents from this source are sent to.
*/
- ComponentName getComponentName();
+ ComponentName getIntentComponent();
/**
* Gets the version code of the source. This is expected to change when the app that
@@ -107,6 +106,11 @@
Intent createVoiceSearchIntent(Bundle appData);
/**
+ * Checks if the current process can read the suggestions from this source.
+ */
+ boolean canRead();
+
+ /**
* Gets suggestions from this source.
*
* @param query The user query.
@@ -126,11 +130,6 @@
SuggestionCursor refreshShortcut(String shortcutId, String extraData);
/**
- * Checks whether this is a web suggestion source.
- */
- boolean isWebSuggestionSource();
-
- /**
* Checks whether the text in the query field should come from the suggestion intent data.
*/
boolean shouldRewriteQueryFromData();
diff --git a/src/com/android/quicksearchbox/Sources.java b/src/com/android/quicksearchbox/Sources.java
index bc8641f..76a7cce 100644
--- a/src/com/android/quicksearchbox/Sources.java
+++ b/src/com/android/quicksearchbox/Sources.java
@@ -1,8 +1,6 @@
package com.android.quicksearchbox;
-import android.database.DataSetObserver;
-
import java.util.Collection;
/**
@@ -28,29 +26,8 @@
Source getWebSearchSource();
/**
- * After calling, clients must call {@link #close()} when done with this object.
+ * Updates the list of sources.
*/
- void load();
-
- /**
- * Releases all resources used by this object. It is possible to call
- * {@link #load()} again after calling this method.
- */
- void close();
-
- /**
- * Register an observer that is called when changes happen to this data set.
- *
- * @param observer gets notified when the data set changes.
- */
- void registerDataSetObserver(DataSetObserver observer);
-
- /**
- * Unregister an observer that has previously been registered with
- * {@link #registerDataSetObserver(DataSetObserver)}
- *
- * @param observer the observer to unregister.
- */
- void unregisterDataSetObserver(DataSetObserver observer);
+ void update();
}
diff --git a/src/com/android/quicksearchbox/SuggestionCursor.java b/src/com/android/quicksearchbox/SuggestionCursor.java
index 93effe4..23ab5b2 100644
--- a/src/com/android/quicksearchbox/SuggestionCursor.java
+++ b/src/com/android/quicksearchbox/SuggestionCursor.java
@@ -16,7 +16,9 @@
package com.android.quicksearchbox;
+import android.content.Intent;
import android.database.DataSetObserver;
+import android.os.Bundle;
/**
@@ -144,10 +146,15 @@
String getSuggestionIntentDataString();
/**
- * Gets the data associated with this suggestion's intent.
+ * Gets the query associated with this suggestion's intent.
*/
String getSuggestionQuery();
+ /**
+ * Gets the intent launched by this suggestion.
+ */
+ Intent getSuggestionIntent(Bundle appSearchData);
+
String getSuggestionDisplayQuery();
/**
diff --git a/src/com/android/quicksearchbox/SuggestionsProvider.java b/src/com/android/quicksearchbox/SuggestionsProvider.java
index b7ac4b9..3070a10 100644
--- a/src/com/android/quicksearchbox/SuggestionsProvider.java
+++ b/src/com/android/quicksearchbox/SuggestionsProvider.java
@@ -16,7 +16,6 @@
package com.android.quicksearchbox;
-import java.util.List;
/**
* Provides a set of suggestion results for a query..
@@ -27,10 +26,10 @@
* Gets suggestions for a query.
*
* @param query The query.
- * @param corpora The corpora to query.
+ * @param singleCorpus The corpora to query, {@code null} for all enabled corpora.
* @param maxSuggestions The maximum number of suggestions to return.
*/
- Suggestions getSuggestions(String query, List<Corpus> corpora, int maxSuggestions);
+ Suggestions getSuggestions(String query, Corpus singleCorpus, int maxSuggestions);
void close();
}
diff --git a/src/com/android/quicksearchbox/SuggestionsProviderImpl.java b/src/com/android/quicksearchbox/SuggestionsProviderImpl.java
index 36edb19..2c7b493 100644
--- a/src/com/android/quicksearchbox/SuggestionsProviderImpl.java
+++ b/src/com/android/quicksearchbox/SuggestionsProviderImpl.java
@@ -19,14 +19,14 @@
import com.android.quicksearchbox.util.BatchingNamedTaskExecutor;
import com.android.quicksearchbox.util.Consumer;
import com.android.quicksearchbox.util.NamedTaskExecutor;
-import com.android.quicksearchbox.util.Util;
import android.os.Handler;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
-import java.util.Set;
/**
* Suggestions provider implementation.
@@ -49,12 +49,14 @@
private final ShortcutRepository mShortcutRepo;
- private final Logger mLogger;
-
private final ShouldQueryStrategy mShouldQueryStrategy = new ShouldQueryStrategy();
private final Corpora mCorpora;
+ private final CorpusRanker mCorpusRanker;
+
+ private final Logger mLogger;
+
private BatchingNamedTaskExecutor mBatchingExecutor;
public SuggestionsProviderImpl(Config config,
@@ -63,14 +65,16 @@
Promoter promoter,
ShortcutRepository shortcutRepo,
Corpora corpora,
+ CorpusRanker corpusRanker,
Logger logger) {
mConfig = config;
mQueryExecutor = queryExecutor;
mPublishThread = publishThread;
mPromoter = promoter;
mShortcutRepo = shortcutRepo;
- mLogger = logger;
mCorpora = corpora;
+ mCorpusRanker = corpusRanker;
+ mLogger = logger;
}
public void close() {
@@ -87,16 +91,19 @@
}
}
- protected SuggestionCursor getShortcutsForQuery(String query, List<Corpus> corpora,
+ protected SuggestionCursor getShortcutsForQuery(String query, Corpus singleCorpus,
int maxShortcuts) {
if (mShortcutRepo == null) return null;
- return mShortcutRepo.getShortcutsForQuery(query, corpora, maxShortcuts);
+ Collection<Corpus> allowedCorpora = mCorpora.getEnabledCorpora();
+ return mShortcutRepo.getShortcutsForQuery(query, allowedCorpora, maxShortcuts);
}
/**
* Gets the sources that should be queried for the given query.
*/
- private List<Corpus> getCorporaToQuery(String query, List<Corpus> orderedCorpora) {
+ private List<Corpus> getCorporaToQuery(String query, Corpus singleCorpus) {
+ if (singleCorpus != null) return Collections.singletonList(singleCorpus);
+ List<Corpus> orderedCorpora = mCorpusRanker.getRankedCorpora();
ArrayList<Corpus> corporaToQuery = new ArrayList<Corpus>(orderedCorpora.size());
for (Corpus corpus : orderedCorpora) {
if (shouldQueryCorpus(corpus, query)) {
@@ -121,16 +128,16 @@
}
}
- public Suggestions getSuggestions(String query, List<Corpus> corpora, int maxSuggestions) {
+ public Suggestions getSuggestions(String query, Corpus singleCorpus, int maxSuggestions) {
if (DBG) Log.d(TAG, "getSuggestions(" + query + ")");
cancelPendingTasks();
- List<Corpus> corporaToQuery = getCorporaToQuery(query, corpora);
+ List<Corpus> corporaToQuery = getCorporaToQuery(query, singleCorpus);
final Suggestions suggestions = new Suggestions(mPromoter,
maxSuggestions,
query,
corporaToQuery.size());
int maxShortcuts = mConfig.getMaxShortcutsReturned();
- SuggestionCursor shortcuts = getShortcutsForQuery(query, corpora, maxShortcuts);
+ SuggestionCursor shortcuts = getShortcutsForQuery(query, singleCorpus, maxShortcuts);
if (shortcuts != null) {
suggestions.setShortcuts(shortcuts);
}
diff --git a/src/com/android/quicksearchbox/VoiceSearch.java b/src/com/android/quicksearchbox/VoiceSearch.java
new file mode 100644
index 0000000..acc5860
--- /dev/null
+++ b/src/com/android/quicksearchbox/VoiceSearch.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quicksearchbox;
+
+import android.app.SearchManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.speech.RecognizerIntent;
+
+/**
+ * Voice Search integration.
+ */
+public class VoiceSearch {
+
+ private final Context mContext;
+
+ public VoiceSearch(Context context) {
+ mContext = context;
+ }
+
+ public boolean shouldShowVoiceSearch(Corpus corpus) {
+ if (corpus != null && !corpus.voiceSearchEnabled()) {
+ return false;
+ }
+ return isVoiceSearchAvailable();
+ }
+
+ private boolean isVoiceSearchAvailable() {
+ Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ ResolveInfo ri = mContext.getPackageManager().
+ resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ return ri != null;
+ }
+
+ public Intent createVoiceWebSearchIntent(Bundle appData) {
+ Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+ RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
+ if (appData != null) {
+ intent.putExtra(SearchManager.APP_DATA, appData);
+ }
+ return intent;
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/WebCorpus.java b/src/com/android/quicksearchbox/WebCorpus.java
index 84306bb..26fe504 100644
--- a/src/com/android/quicksearchbox/WebCorpus.java
+++ b/src/com/android/quicksearchbox/WebCorpus.java
@@ -25,7 +25,6 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
-import android.speech.RecognizerIntent;
import android.util.Patterns;
import android.webkit.URLUtil;
@@ -65,27 +64,16 @@
}
public Intent createSearchIntent(String query, Bundle appData) {
- return isUrl(query)? createBrowseIntent(query) : createWebSearchIntent(query, appData);
- }
-
- private static Intent createWebSearchIntent(String query, Bundle appData) {
- Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // We need CLEAR_TOP to avoid reusing an old task that has other activities
- // on top of the one we want.
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.putExtra(SearchManager.USER_QUERY, query);
- intent.putExtra(SearchManager.QUERY, query);
- if (appData != null) {
- intent.putExtra(SearchManager.APP_DATA, appData);
+ if (isUrl(query)) {
+ return createBrowseIntent(query);
+ } else if (mWebSearchSource != null){
+ return mWebSearchSource.createSearchIntent(query, appData);
+ } else {
+ return null;
}
- // TODO: Include something like this, to let the web search activity
- // know how this query was started.
- //intent.putExtra(SearchManager.SEARCH_MODE, SearchManager.MODE_GLOBAL_SEARCH_TYPED_QUERY);
- return intent;
}
- private static Intent createBrowseIntent(String query) {
+ private Intent createBrowseIntent(String query) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -114,18 +102,7 @@
}
public Intent createVoiceSearchIntent(Bundle appData) {
- return createVoiceWebSearchIntent(appData);
- }
-
- public static Intent createVoiceWebSearchIntent(Bundle appData) {
- Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
- RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
- if (appData != null) {
- intent.putExtra(SearchManager.APP_DATA, appData);
- }
- return intent;
+ return new VoiceSearch(getContext()).createVoiceWebSearchIntent(appData);
}
private int getCorpusIconResource() {
diff --git a/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java b/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java
index 3a29ae9..a4b007f 100644
--- a/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java
+++ b/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java
@@ -86,10 +86,44 @@
Log.d(TAG, "bindAsSuggestion(), text1=" + text1 + ",text2=" + text2
+ ",icon1=" + icon1 + ",icon2=" + icon2);
}
+ // If there is no text for the second line, allow the first line to be up to two lines
+ if (TextUtils.isEmpty(text2)) {
+ mText1.setSingleLine(false);
+ mText1.setMaxLines(2);
+ mText1.setEllipsize(TextUtils.TruncateAt.START);
+ } else {
+ mText1.setSingleLine(true);
+ mText1.setMaxLines(1);
+ mText1.setEllipsize(TextUtils.TruncateAt.MIDDLE);
+ }
setText1(text1);
setText2(text2);
setIcon1(icon1);
setIcon2(icon2);
+ updateRefinable(suggestion);
+ }
+
+ protected void updateRefinable(SuggestionCursor suggestion) {
+ boolean refinable = mIcon2.getDrawable() == null
+ && !TextUtils.isEmpty(suggestion.getSuggestionQuery());
+ setRefinable(suggestion, refinable);
+ }
+
+ protected void setRefinable(SuggestionCursor suggestion, boolean refinable) {
+ if (refinable) {
+ final int position = suggestion.getPosition();
+ mIcon2.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ Log.d(TAG, "Clicked query refine");
+ SuggestionsView suggestions = (SuggestionsView) getParent();
+ suggestions.onIcon2Clicked(position);
+ }
+ });
+ Drawable icon2 = getContext().getResources().getDrawable(R.drawable.refine_query);
+ setIcon2(icon2);
+ } else {
+ mIcon2.setOnClickListener(null);
+ }
}
private CharSequence formatUrl(CharSequence url) {
diff --git a/src/com/android/quicksearchbox/ui/SuggestionClickListener.java b/src/com/android/quicksearchbox/ui/SuggestionClickListener.java
index 9cc3b10..10fc714 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionClickListener.java
+++ b/src/com/android/quicksearchbox/ui/SuggestionClickListener.java
@@ -20,16 +20,25 @@
* Listener interface for clicks on suggestions.
*/
public interface SuggestionClickListener {
+
/**
* Called when a suggestion is clicked.
*
* @param position Position of the clicked suggestion.
*/
void onSuggestionClicked(int position);
+
/**
* Called when a suggestion is long clicked.
*
* @param position Position of the long clicked suggestion.
*/
boolean onSuggestionLongClicked(int position);
+
+ /**
+ * Called when the "query refine" button of a suggestion is clicked.
+ *
+ * @param position Position of the suggestion.
+ */
+ void onSuggestionQueryRefineClicked(int position);
}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsView.java b/src/com/android/quicksearchbox/ui/SuggestionsView.java
index 6fa7ac7..a73f1fa 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionsView.java
+++ b/src/com/android/quicksearchbox/ui/SuggestionsView.java
@@ -22,7 +22,6 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
@@ -39,8 +38,6 @@
private SuggestionSelectionListener mSuggestionSelectionListener;
- private InteractionListener mInteractionListener;
-
public SuggestionsView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -61,10 +58,6 @@
mSuggestionSelectionListener = listener;
}
- public void setInteractionListener(InteractionListener listener) {
- mInteractionListener = listener;
- }
-
/**
* Gets the position of the selected suggestion.
*
@@ -84,14 +77,6 @@
}
@Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN && mInteractionListener != null) {
- mInteractionListener.onInteraction();
- }
- return super.onInterceptTouchEvent(event);
- }
-
- @Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (DBG) {
@@ -130,13 +115,6 @@
}
}
- public interface InteractionListener {
- /**
- * Called when the user interacts with this view.
- */
- void onInteraction();
- }
-
private class ItemClickListener implements AdapterView.OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (DBG) Log.d(TAG, "onItemClick(" + position + ")");
@@ -174,4 +152,11 @@
fireNothingSelected();
}
}
+
+ public void onIcon2Clicked(int position) {
+ if (mSuggestionClickListener != null) {
+ mSuggestionClickListener.onSuggestionQueryRefineClicked(position);
+ }
+ }
+
}
diff --git a/tests/src/com/android/quicksearchbox/MockCorpora.java b/tests/src/com/android/quicksearchbox/MockCorpora.java
index cd0eb14..7a6adb0 100644
--- a/tests/src/com/android/quicksearchbox/MockCorpora.java
+++ b/tests/src/com/android/quicksearchbox/MockCorpora.java
@@ -102,8 +102,7 @@
return true;
}
- public void close() {
- // Nothing to release
+ public void update() {
}
public void registerDataSetObserver(DataSetObserver observer) {
diff --git a/tests/src/com/android/quicksearchbox/MockShortcutRepository.java b/tests/src/com/android/quicksearchbox/MockShortcutRepository.java
index ab7ea38..73c2223 100644
--- a/tests/src/com/android/quicksearchbox/MockShortcutRepository.java
+++ b/tests/src/com/android/quicksearchbox/MockShortcutRepository.java
@@ -16,7 +16,7 @@
package com.android.quicksearchbox;
-import java.util.List;
+import java.util.Collection;
import java.util.Map;
/**
@@ -31,7 +31,7 @@
public void close() {
}
- public SuggestionCursor getShortcutsForQuery(String query, List<Corpus> corporaToQuery,
+ public SuggestionCursor getShortcutsForQuery(String query, Collection<Corpus> corporaToQuery,
int maxShortcuts) {
// TODO: should look at corporaToQuery
DataSuggestionCursor cursor = new DataSuggestionCursor(query);
diff --git a/tests/src/com/android/quicksearchbox/MockSource.java b/tests/src/com/android/quicksearchbox/MockSource.java
index f07471c..2b5ff46 100644
--- a/tests/src/com/android/quicksearchbox/MockSource.java
+++ b/tests/src/com/android/quicksearchbox/MockSource.java
@@ -47,7 +47,7 @@
mVersionCode = versionCode;
}
- public ComponentName getComponentName() {
+ public ComponentName getIntentComponent() {
// Not an activity, but no code should treat it as one.
return new ComponentName("com.android.quicksearchbox",
getClass().getName() + "." + mName);
@@ -58,7 +58,7 @@
}
public String getName() {
- return getComponentName().flattenToShortString();
+ return getIntentComponent().flattenToShortString();
}
public String getDefaultIntentAction() {
@@ -101,6 +101,10 @@
return null;
}
+ public boolean canRead() {
+ return true;
+ }
+
public SourceResult getSuggestions(String query, int queryLimit) {
if (query.length() == 0) {
return null;
@@ -155,6 +159,10 @@
return null;
}
+ public boolean isExternal() {
+ return false;
+ }
+
public boolean isWebSuggestionSource() {
return false;
}
diff --git a/tests/src/com/android/quicksearchbox/MockSources.java b/tests/src/com/android/quicksearchbox/MockSources.java
index 1bf39cc..2817007 100644
--- a/tests/src/com/android/quicksearchbox/MockSources.java
+++ b/tests/src/com/android/quicksearchbox/MockSources.java
@@ -27,8 +27,6 @@
*/
public class MockSources implements Sources {
- private final DataSetObservable mDataSetObservable = new DataSetObservable();
-
private final HashMap<String, Source> mSources = new HashMap<String, Source>();
public void addSource(Source source) {
@@ -47,21 +45,7 @@
return null;
}
- public void load() {
- notifyDataSetChanged();
+ public void update() {
}
- public void close() {
- }
-
- public void registerDataSetObserver(DataSetObserver observer) {
- mDataSetObservable.registerObserver(observer);
- }
-
- public void unregisterDataSetObserver(DataSetObserver observer) {
- mDataSetObservable.unregisterObserver(observer);
- }
-
- protected void notifyDataSetChanged() {
- mDataSetObservable.notifyChanged();
- }}
+}
diff --git a/tests/src/com/android/quicksearchbox/SearchableCorporaTest.java b/tests/src/com/android/quicksearchbox/SearchableCorporaTest.java
index 164823e..6173163 100644
--- a/tests/src/com/android/quicksearchbox/SearchableCorporaTest.java
+++ b/tests/src/com/android/quicksearchbox/SearchableCorporaTest.java
@@ -39,7 +39,7 @@
sources.addSource(MockSource.SOURCE_1);
sources.addSource(MockSource.SOURCE_2);
mCorpora = new SearchableCorpora(mContext, config, sources, new MockCorpusFactory());
- mCorpora.load();
+ mCorpora.update();
}
public void testGetAllCorpora() {
diff --git a/tests/src/com/android/quicksearchbox/ShortcutPromoterTest.java b/tests/src/com/android/quicksearchbox/ShortcutPromoterTest.java
index 2107e8b..3ae4efa 100644
--- a/tests/src/com/android/quicksearchbox/ShortcutPromoterTest.java
+++ b/tests/src/com/android/quicksearchbox/ShortcutPromoterTest.java
@@ -42,7 +42,7 @@
protected void setUp() throws Exception {
mQuery = "foo";
List<Corpus> corpora = Arrays.asList(MockCorpus.CORPUS_1, MockCorpus.CORPUS_2);
- mShortcuts = new MockShortcutRepository().getShortcutsForQuery(mQuery, corpora, 8);
+ mShortcuts = new MockShortcutRepository().getShortcutsForQuery(mQuery, null, 8);
mSuggestions = new ArrayList<CorpusResult>();
for (Corpus corpus : corpora) {
mSuggestions.add(corpus.getSuggestions(mQuery, 10));
diff --git a/tests/src/com/android/quicksearchbox/ShortcutRepositoryTest.java b/tests/src/com/android/quicksearchbox/ShortcutRepositoryTest.java
index b41997c..7874552 100644
--- a/tests/src/com/android/quicksearchbox/ShortcutRepositoryTest.java
+++ b/tests/src/com/android/quicksearchbox/ShortcutRepositoryTest.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -803,7 +804,7 @@
}
}
- void assertShortcuts(String message, String query, List<Corpus> allowedCorpora,
+ void assertShortcuts(String message, String query, Collection<Corpus> allowedCorpora,
SuggestionCursor expected) {
SuggestionCursor cursor = mRepo.getShortcutsForQuery(query, allowedCorpora,
mConfig.getMaxShortcutsReturned(), NOW);
@@ -814,7 +815,7 @@
}
}
- void assertShortcuts(String message, String query, List<Corpus> allowedCorpora,
+ void assertShortcuts(String message, String query, Collection<Corpus> allowedCorpora,
SuggestionData... expected) {
assertShortcuts(message, query, allowedCorpora, new DataSuggestionCursor(query, expected));
}