Allow user to pick corpus when adding search widget
Fixes http://b/issue?id=2408618
Change-Id: I318b6cf30469e1058364e6ce9c4dd1b936536c67
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 93f3cec..0544571 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -48,6 +48,11 @@
<action android:name="android.search.action.GLOBAL_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
+ <intent-filter>
+ <action android:name="android.search.action.GLOBAL_SEARCH" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="android.component" />
+ </intent-filter>
</activity>
<activity android:name=".SelectSearchSourceActivity"
@@ -58,13 +63,13 @@
android:windowSoftInputMode="stateUnchanged|adjustResize"
>
<intent-filter>
- <action android:name="android.intent.action.SELECT_SEARCH_SOURCE" />
+ <action android:name="com.android.quicksearchbox.action.SELECT_SEARCH_SOURCE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
- <action android:name="android.intent.action.SELECT_SEARCH_SOURCE" />
+ <action android:name="com.android.quicksearchbox.action.SELECT_SEARCH_SOURCE" />
<category android:name="android.intent.category.DEFAULT" />
- <data android:scheme="content" android:mimeType="application/vnd.android.application" />
+ <data android:scheme="android.component" />
</intent-filter>
</activity>
@@ -88,6 +93,14 @@
<meta-data android:name="android.appwidget.provider" android:resource="@xml/search_widget_info" />
</receiver>
+ <!-- This class name is referenced in res/xml/search_widget_info.xml -->
+ <activity android:name="com.android.quicksearchbox.SearchWidgetConfigActivity"
+ android:theme="@style/Theme.QuickSearchBox">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".google.GoogleSearch"
android:label="@string/google_search_label"
android:icon="@drawable/google_icon"
@@ -119,4 +132,4 @@
android:authorities="com.android.quicksearchbox.google" />
</application>
-</manifest>
+</manifest>
diff --git a/res/layout/widget_config.xml b/res/layout/widget_config.xml
new file mode 100644
index 0000000..c9cf47b
--- /dev/null
+++ b/res/layout/widget_config.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="4dip"
+ android:text="@string/search_source_list_heading"
+ />
+
+ <GridView
+ android:id="@+id/widget_source_list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|left"
+ android:numColumns="4"
+ android:horizontalSpacing="8dip"
+ android:verticalSpacing="8dip"
+ />
+
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0732ac6..65ec46f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -20,6 +20,9 @@
<!-- Search widget -->
<string name="search_widget">Search</string>
+ <!-- Search widget config -->
+ <string name="search_widget_config_ok">OK</string>
+
<!-- Source selector -->
<string name="search_source_list_heading">Search in:</string>
<string name="global_search_label">Everything</string>
diff --git a/res/xml/search_widget_info.xml b/res/xml/search_widget_info.xml
index 52df7c7..734dcf6 100644
--- a/res/xml/search_widget_info.xml
+++ b/res/xml/search_widget_info.xml
@@ -18,5 +18,6 @@
android:minWidth="294dip"
android:minHeight="72dip"
android:updatePeriodMillis="0"
- android:initialLayout="@layout/search_widget">
+ android:initialLayout="@layout/search_widget"
+ android:configure="com.android.quicksearchbox.SearchWidgetConfigActivity">
</appwidget-provider>
diff --git a/src/com/android/quicksearchbox/SearchActivity.java b/src/com/android/quicksearchbox/SearchActivity.java
index 381984b..c06da7b 100644
--- a/src/com/android/quicksearchbox/SearchActivity.java
+++ b/src/com/android/quicksearchbox/SearchActivity.java
@@ -35,7 +35,6 @@
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.util.EventLog;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
@@ -60,9 +59,6 @@
public final static String INTENT_ACTION_SEARCH_SETTINGS
= "android.search.action.SEARCH_SETTINGS";
- public static final String EXTRA_KEY_SEARCH_SOURCE
- = "search_source";
-
// Keys for the saved instance state.
private static final String INSTANCE_KEY_SOURCE = "source";
private static final String INSTANCE_KEY_USER_QUERY = "query";
@@ -178,28 +174,16 @@
private void setupFromIntent(Intent intent) {
if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")");
- if (intent.hasExtra(EXTRA_KEY_SEARCH_SOURCE)) {
- Source source = getSourceByName(intent.getStringExtra(EXTRA_KEY_SEARCH_SOURCE));
- setSource(source);
- } else {
- setSource(null);
- }
+ ComponentName sourceName = SearchSourceSelector.getSource(intent);
+ Source source = getSourceByComponentName(sourceName);
+ setSource(source);
setUserQuery(intent.getStringExtra(SearchManager.QUERY));
mSelectAll = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false);
setAppSearchData(intent.getBundleExtra(SearchManager.APP_DATA));
}
- private Source getSourceByName(String sourceNameStr) {
- if (sourceNameStr == null) return null;
- ComponentName sourceName = ComponentName.unflattenFromString(sourceNameStr);
- if (sourceName == null) {
- Log.w(TAG, "Malformed source name: " + sourceName);
- return null;
- }
- return getSourceByComponentName(sourceName);
- }
-
private Source getSourceByComponentName(ComponentName sourceName) {
+ if (sourceName == null) return null;
Source source = getSources().getSourceByComponentName(sourceName);
if (source == null) {
Log.w(TAG, "Unknown source " + sourceName);
diff --git a/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java b/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
new file mode 100644
index 0000000..8a67ff5
--- /dev/null
+++ b/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
@@ -0,0 +1,126 @@
+/*
+ * 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 com.android.quicksearchbox.ui.SourcesAdapter;
+import com.android.quicksearchbox.ui.SuggestionViewFactory;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.GridView;
+
+/**
+ * The configuration screen for search widgets.
+ */
+public class SearchWidgetConfigActivity extends Activity {
+ static final String TAG = "QSB.SearchWidgetConfigActivity";
+
+ private static final String PREFS_NAME = "SearchWidgetConfig";
+ private static final String WIDGET_SOURCE_PREF_PREFIX = "widget_source_";
+
+ private int mAppWidgetId;
+
+ private GridView mSourceList;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.widget_config);
+
+ mSourceList = (GridView) findViewById(R.id.widget_source_list);
+ mSourceList.setOnItemClickListener(new SourceClickListener());
+ // TODO: for some reason, putting this in the XML layout instead makes
+ // the list items unclickable.
+ mSourceList.setFocusable(true);
+ mSourceList.setAdapter(
+ new SourcesAdapter(getViewFactory(), getGlobalSuggestionsProvider()));
+
+ Intent intent = getIntent();
+ mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ finish();
+ }
+ }
+
+ protected void selectSource(Source source) {
+ writeWidgetSourcePref(mAppWidgetId, source);
+ updateWidget(source);
+
+ Intent result = new Intent();
+ result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
+ setResult(RESULT_OK, result);
+ finish();
+ }
+
+ private void updateWidget(Source source) {
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
+ SearchWidgetProvider.setupSearchWidget(this, appWidgetManager,
+ mAppWidgetId, source);
+ }
+
+ private static SharedPreferences getWidgetPreferences(Context context) {
+ return context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
+ }
+
+ private static String getSourcePrefKey(int appWidgetId) {
+ return WIDGET_SOURCE_PREF_PREFIX + appWidgetId;
+ }
+
+ private void writeWidgetSourcePref(int appWidgetId, Source source) {
+ String sourceName = source == null ? null : source.getFlattenedComponentName();
+ SharedPreferences.Editor prefs = getWidgetPreferences(this).edit();
+ prefs.putString(getSourcePrefKey(appWidgetId), sourceName);
+ prefs.commit();
+ }
+
+ public static ComponentName readWidgetSourcePref(Context context, int appWidgetId) {
+ SharedPreferences prefs = getWidgetPreferences(context);
+ String sourceName = prefs.getString(getSourcePrefKey(appWidgetId), null);
+ return sourceName == null ? null : ComponentName.unflattenFromString(sourceName);
+ }
+
+ private QsbApplication getQsbApplication() {
+ return (QsbApplication) getApplication();
+ }
+
+ private SuggestionsProvider getGlobalSuggestionsProvider() {
+ return getQsbApplication().getGlobalSuggestionsProvider();
+ }
+
+ private SuggestionViewFactory getViewFactory() {
+ return getQsbApplication().getSuggestionViewFactory();
+ }
+
+ private class SourceClickListener implements AdapterView.OnItemClickListener {
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ Source source = (Source) parent.getItemAtPosition(position);
+ selectSource(source);
+ }
+ }
+}
+
+
+
diff --git a/src/com/android/quicksearchbox/SearchWidgetProvider.java b/src/com/android/quicksearchbox/SearchWidgetProvider.java
index b899846..f8390ec 100644
--- a/src/com/android/quicksearchbox/SearchWidgetProvider.java
+++ b/src/com/android/quicksearchbox/SearchWidgetProvider.java
@@ -50,31 +50,43 @@
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- updateSearchWidgets(context, appWidgetManager, appWidgetIds);
+ final int count = appWidgetIds.length;
+ for (int i = 0; i < count; i++) {
+ updateSearchWidget(context, appWidgetManager, appWidgetIds[i]);
+ }
}
- /**
- * Updates a set of search widgets.
- */
- private void updateSearchWidgets(Context context, AppWidgetManager appWidgetManager,
- int[] appWidgetIds) {
- if (DBG) Log.d(TAG, "updateSearchWidgets()");
+ private void updateSearchWidget(Context context, AppWidgetManager appWidgetManager,
+ int appWidgetId) {
+ ComponentName sourceName = SearchWidgetConfigActivity.readWidgetSourcePref(context, appWidgetId);
+ Source source = getSources(context).getSourceByComponentName(sourceName);
+ setupSearchWidget(context, appWidgetManager, appWidgetId, source);
+ }
+
+ public static void setupSearchWidget(Context context, AppWidgetManager appWidgetManager,
+ int appWidgetId, Source source) {
+ if (DBG) Log.d(TAG, "setupSearchWidget()");
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.search_widget);
+ ComponentName sourceName = source == null ? null : source.getComponentName();
+
Bundle widgetAppData = new Bundle();
widgetAppData.putString(SOURCE, WIDGET_SEARCH_SOURCE);
// Source selector
- bindSourceSelector(context, views, widgetAppData);
+ bindSourceSelector(context, views, widgetAppData, source);
// Text field
Intent qsbIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
qsbIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
qsbIntent.putExtra(SearchManager.APP_DATA, widgetAppData);
+ SearchSourceSelector.setSource(qsbIntent, sourceName);
PendingIntent textPendingIntent = PendingIntent.getActivity(context, 0, qsbIntent, 0);
views.setOnClickPendingIntent(R.id.search_widget_text, textPendingIntent);
// Voice search button. Only shown if voice search is available.
+ // TODO: This should be Voice Search for the selected source,
+ // and only show if available for that source
Intent voiceSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
voiceSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
voiceSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
@@ -90,12 +102,11 @@
views.setViewVisibility(R.id.search_widget_voice_btn, View.GONE);
}
- appWidgetManager.updateAppWidget(appWidgetIds, views);
+ appWidgetManager.updateAppWidget(appWidgetId, views);
}
- private void bindSourceSelector(Context context, RemoteViews views, Bundle widgetAppData) {
- // TODO: Allow user to select source when adding the widget
- Source source = null;
+ private static void bindSourceSelector(Context context, RemoteViews views,
+ Bundle widgetAppData, Source source) {
Uri sourceIconUri = getSourceIconUri(context, source);
views.setImageViewUri(SearchSourceSelector.ICON_VIEW_ID, sourceIconUri);
Intent intent = SearchSourceSelector.createIntent(null, "", widgetAppData);
@@ -103,22 +114,22 @@
views.setOnClickPendingIntent(SearchSourceSelector.ICON_VIEW_ID, pendingIntent);
}
- private Uri getSourceIconUri(Context context, Source source) {
+ private static Uri getSourceIconUri(Context context, Source source) {
if (source == null) {
return getSuggestionViewFactory(context).getGlobalSearchIconUri();
}
return source.getSourceIconUri();
}
- private QsbApplication getQsbApplication(Context context) {
+ private static QsbApplication getQsbApplication(Context context) {
return (QsbApplication) context.getApplicationContext();
}
- private SourceLookup getSources(Context context) {
+ private static SourceLookup getSources(Context context) {
return getQsbApplication(context).getSources();
}
- private SuggestionViewFactory getSuggestionViewFactory(Context context) {
+ private static SuggestionViewFactory getSuggestionViewFactory(Context context) {
return getQsbApplication(context).getSuggestionViewFactory();
}
diff --git a/src/com/android/quicksearchbox/SelectSearchSourceActivity.java b/src/com/android/quicksearchbox/SelectSearchSourceActivity.java
index 82dded1..82d1b9b 100644
--- a/src/com/android/quicksearchbox/SelectSearchSourceActivity.java
+++ b/src/com/android/quicksearchbox/SelectSearchSourceActivity.java
@@ -138,8 +138,8 @@
Intent searchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
searchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- String sourceName = source == null ? null : source.getFlattenedComponentName();
- searchIntent.putExtra(SearchActivity.EXTRA_KEY_SEARCH_SOURCE, sourceName);
+ ComponentName sourceName = source == null ? null : source.getComponentName();
+ SearchSourceSelector.setSource(searchIntent, sourceName);
searchIntent.putExtra(SearchManager.QUERY, query);
searchIntent.putExtra(SearchManager.APP_DATA, appSearchData);
diff --git a/src/com/android/quicksearchbox/ui/SearchSourceSelector.java b/src/com/android/quicksearchbox/ui/SearchSourceSelector.java
index ef92746..41db1db 100644
--- a/src/com/android/quicksearchbox/ui/SearchSourceSelector.java
+++ b/src/com/android/quicksearchbox/ui/SearchSourceSelector.java
@@ -21,7 +21,6 @@
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -37,10 +36,6 @@
/**
* Utilities for setting up the search source selector.
*
- * This class has two copies:
- * android.app.SearchSourceSelector
- * com.android.quicksearchbox.ui.SearchSourceSelector
- *
* They should keep the same look and feel as much as possible,
* but only the intent details must absolutely stay in sync.
*
@@ -50,9 +45,10 @@
private static final String TAG = "SearchSourceSelector";
- // TODO: This should be defined in android.provider.Applications,
- // and have a less made-up value.
- private static final String APPLICATION_TYPE = "application/vnd.android.application";
+ public static final String INTENT_ACTION_SELECT_SEARCH_SOURCE
+ = "com.android.quicksearchbox.action.SELECT_SEARCH_SOURCE";
+
+ private static final String SCHEME_COMPONENT = "android.component";
public static final int ICON_VIEW_ID = R.id.search_source_selector_icon;
@@ -116,14 +112,11 @@
* activity if the user opens the source selector and chooses a source.
*/
public static Intent createIntent(ComponentName source, String query, Bundle appSearchData) {
- Intent intent = new Intent(SearchManager.INTENT_ACTION_SELECT_SEARCH_SOURCE);
+ Intent intent = new Intent(INTENT_ACTION_SELECT_SEARCH_SOURCE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- Uri sourceUri = componentNameToUri(source);
- if (sourceUri != null) {
- intent.setDataAndType(sourceUri, APPLICATION_TYPE);
- }
+ setSource(intent, source);
if (query != null) {
intent.putExtra(SearchManager.QUERY, query);
}
@@ -133,32 +126,32 @@
return intent;
}
- /**
- * Gets the search source from which the given
- * {@link SearchManager.INTENT_ACTION_SELECT_SEARCH_SOURCE} intent was sent.
- */
public static ComponentName getSource(Intent intent) {
return uriToComponentName(intent.getData());
}
+ public static void setSource(Intent intent, ComponentName source) {
+ if (source != null) {
+ intent.setData(componentNameToUri(source));
+ }
+ }
+
private static Uri componentNameToUri(ComponentName name) {
if (name == null) return null;
- // TODO: This URI format is specificed in android.provider.Applications which is @hidden
return new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority("applications")
- .appendEncodedPath("applications")
- .appendPath(name.getPackageName())
- .appendPath(name.getClassName())
+ .scheme(SCHEME_COMPONENT)
+ .authority(name.getPackageName())
+ .path(name.getClassName())
.build();
}
private static ComponentName uriToComponentName(Uri uri) {
if (uri == null) return null;
+ if (!SCHEME_COMPONENT.equals(uri.getScheme())) return null;
+ String pkg = uri.getAuthority();
List<String> path = uri.getPathSegments();
- if (path == null || path.size() != 3) return null;
- String pkg = path.get(1);
- String cls = path.get(2);
+ if (path == null || path.isEmpty()) return null;
+ String cls = path.get(0);
if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(cls)) return null;
return new ComponentName(pkg, cls);
}