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);
     }