Merge Google and Browser sources and call them Web
This required a gigantic refactoring of QuickSearchBox to work nicely.
This change also reduces the number of promoted source to 3,
since Web is now one source instead of two.
As a side effect of the recatoring, VoiceSearch now searches
the selected corpus (fixes http://b/issue?id=2438309)
Fixes http://b/issue?id=2365770
Change-Id: Ife8d40ef62ea004e8d0f20a60e9196fc589f01fc
diff --git a/src/com/android/quicksearchbox/AbstractCorpus.java b/src/com/android/quicksearchbox/AbstractCorpus.java
new file mode 100644
index 0000000..c6bae42
--- /dev/null
+++ b/src/com/android/quicksearchbox/AbstractCorpus.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+
+/**
+ * Base class for corpus implementations.
+ */
+public abstract class AbstractCorpus implements Corpus {
+
+ public AbstractCorpus() {
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o != null && getClass().equals(o.getClass())) {
+ return getName().equals(((Corpus) o).getName());
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/AbstractSourceSuggestionCursor.java b/src/com/android/quicksearchbox/AbstractSourceSuggestionCursor.java
deleted file mode 100644
index 960acaa..0000000
--- a/src/com/android/quicksearchbox/AbstractSourceSuggestionCursor.java
+++ /dev/null
@@ -1,67 +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.content.ComponentName;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-
-/**
- * Base class for SuggestionCursor implementations that can get a {@link Source}
- * object for each suggestion (possibly the same for all suggestions).
- *
- */
-public abstract class AbstractSourceSuggestionCursor extends AbstractSuggestionCursor {
-
- public AbstractSourceSuggestionCursor(String userQuery) {
- super(userQuery);
- }
-
- /**
- * Gets the source for the current suggestion.
- */
- protected abstract Source getSource();
-
- public ComponentName getSourceComponentName() {
- return getSource().getComponentName();
- }
-
- public String getLogName() {
- return getSource().getLogName();
- }
-
- public CharSequence getSourceLabel() {
- return getSource().getLabel();
- }
-
- public Drawable getSourceIcon() {
- return getSource().getSourceIcon();
- }
-
- public Uri getSourceIconUri() {
- return getSource().getSourceIconUri();
- }
-
- public Drawable getIcon(String drawableId) {
- return getSource().getIcon(drawableId);
- }
-
- public Uri getIconUri(String iconId) {
- return getSource().getIconUri(iconId);
- }
-
-}
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionCursor.java b/src/com/android/quicksearchbox/AbstractSuggestionCursor.java
index 1909426..66f634d 100644
--- a/src/com/android/quicksearchbox/AbstractSuggestionCursor.java
+++ b/src/com/android/quicksearchbox/AbstractSuggestionCursor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -16,23 +16,14 @@
package com.android.quicksearchbox;
-import android.database.DataSetObservable;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.text.Html;
-import android.text.TextUtils;
/**
- * Base class for SuggestionCursor implementations.
- *
+ * Base class for suggestion cursors.
*/
public abstract class AbstractSuggestionCursor implements SuggestionCursor {
- /** The user query that returned these suggestions. */
private final String mUserQuery;
- private final DataSetObservable mDataSetObservable = new DataSetObservable();
-
public AbstractSuggestionCursor(String userQuery) {
mUserQuery = userQuery;
}
@@ -41,55 +32,4 @@
return mUserQuery;
}
- public void registerDataSetObserver(DataSetObserver observer) {
- mDataSetObservable.registerObserver(observer);
- }
-
- public void unregisterDataSetObserver(DataSetObserver observer) {
- mDataSetObservable.unregisterObserver(observer);
- }
-
- protected void notifyDataSetChanged() {
- mDataSetObservable.notifyChanged();
- }
-
- public void close() {
- mDataSetObservable.unregisterAll();
- }
-
- public Drawable getSuggestionDrawableIcon1() {
- String icon1Id = getSuggestionIcon1();
- Drawable icon1 = getIcon(icon1Id);
- return icon1 == null ? getSourceIcon() : icon1;
- }
-
- public Drawable getSuggestionDrawableIcon2() {
- return getIcon(getSuggestionIcon2());
- }
-
- public CharSequence getSuggestionFormattedText1() {
- return formatText(getSuggestionText1());
- }
-
- public CharSequence getSuggestionFormattedText2() {
- return formatText(getSuggestionText2());
- }
-
- private CharSequence formatText(String str) {
- boolean isHtml = "html".equals(getSuggestionFormat());
- if (isHtml && looksLikeHtml(str)) {
- return Html.fromHtml(str);
- } else {
- return str;
- }
- }
-
- private boolean looksLikeHtml(String str) {
- if (TextUtils.isEmpty(str)) return false;
- for (int i = str.length() - 1; i >= 0; i--) {
- char c = str.charAt(i);
- if (c == '>' || c == '&') return true;
- }
- return false;
- }
}
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.java b/src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.java
new file mode 100644
index 0000000..cf84dc7
--- /dev/null
+++ b/src/com/android/quicksearchbox/AbstractSuggestionCursorWrapper.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+/**
+ * A SuggestionCursor that delegates all suggestions-specific calls to one or more
+ * other suggestion cursors.
+ */
+public abstract class AbstractSuggestionCursorWrapper extends AbstractSuggestionCursor {
+
+ public AbstractSuggestionCursorWrapper(String userQuery) {
+ super(userQuery);
+ }
+
+ /**
+ * Gets the SuggestionCursor to use for the current suggestion.
+ */
+ protected abstract SuggestionCursor current();
+
+ public String getShortcutId() {
+ return current().getShortcutId();
+ }
+
+ public String getSuggestionDisplayQuery() {
+ return current().getSuggestionDisplayQuery();
+ }
+
+ public String getSuggestionFormat() {
+ return current().getSuggestionFormat();
+ }
+
+ public String getSuggestionIcon1() {
+ return current().getSuggestionIcon1();
+ }
+
+ public String getSuggestionIcon2() {
+ return current().getSuggestionIcon2();
+ }
+
+ public String getSuggestionIntentAction() {
+ return current().getSuggestionIntentAction();
+ }
+
+ public String getSuggestionIntentDataString() {
+ return current().getSuggestionIntentDataString();
+ }
+
+ public String getSuggestionIntentExtraData() {
+ return current().getSuggestionIntentExtraData();
+ }
+
+ public String getSuggestionKey() {
+ return current().getSuggestionKey();
+ }
+
+ public String getSuggestionLogType() {
+ return current().getSuggestionLogType();
+ }
+
+ public String getSuggestionQuery() {
+ return current().getSuggestionQuery();
+ }
+
+ public Source getSuggestionSource() {
+ return current().getSuggestionSource();
+ }
+
+ public String getSuggestionText1() {
+ return current().getSuggestionText1();
+ }
+
+ public String getSuggestionText2() {
+ return current().getSuggestionText2();
+ }
+
+ public boolean isSpinnerWhileRefreshing() {
+ return current().isSpinnerWhileRefreshing();
+ }
+}
diff --git a/src/com/android/quicksearchbox/AbstractSuggestionsProvider.java b/src/com/android/quicksearchbox/AbstractSuggestionsProvider.java
index e6e5846..322aa8d 100644
--- a/src/com/android/quicksearchbox/AbstractSuggestionsProvider.java
+++ b/src/com/android/quicksearchbox/AbstractSuggestionsProvider.java
@@ -68,7 +68,7 @@
}
}
- public abstract ArrayList<Source> getOrderedSources();
+ public abstract ArrayList<Corpus> getOrderedCorpora();
protected abstract SuggestionCursor getShortcutsForQuery(String query);
@@ -76,27 +76,27 @@
* Gets the sources that should be queried for the given query.
*
*/
- private ArrayList<Source> getSourcesToQuery(String query) {
+ private ArrayList<Corpus> getCorporaToQuery(String query) {
if (query.length() == 0) {
- return new ArrayList<Source>();
+ return new ArrayList<Corpus>(0);
}
- ArrayList<Source> orderedSources = getOrderedSources();
- ArrayList<Source> sourcesToQuery = new ArrayList<Source>(orderedSources.size());
- for (Source source : orderedSources) {
- if (shouldQuerySource(source, query)) {
- sourcesToQuery.add(source);
+ ArrayList<Corpus> orderedCorpora = getOrderedCorpora();
+ ArrayList<Corpus> corporaToQuery = new ArrayList<Corpus>(orderedCorpora.size());
+ for (Corpus corpus : orderedCorpora) {
+ if (shouldQueryCorpus(corpus, query)) {
+ corporaToQuery.add(corpus);
}
}
- return sourcesToQuery;
+ return corporaToQuery;
}
- protected boolean shouldQuerySource(Source source, String query) {
- return mShouldQueryStrategy.shouldQuerySource(source, query);
+ protected boolean shouldQueryCorpus(Corpus corpus, String query) {
+ return mShouldQueryStrategy.shouldQueryCorpus(corpus, query);
}
- private void updateShouldQueryStrategy(SuggestionCursor cursor) {
+ private void updateShouldQueryStrategy(CorpusResult cursor) {
if (cursor.getCount() == 0) {
- mShouldQueryStrategy.onZeroResults(cursor.getSourceComponentName(),
+ mShouldQueryStrategy.onZeroResults(cursor.getCorpus(),
cursor.getUserQuery());
}
}
@@ -104,18 +104,18 @@
public Suggestions getSuggestions(String query) {
if (DBG) Log.d(TAG, "getSuggestions(" + query + ")");
cancelPendingTasks();
- ArrayList<Source> sourcesToQuery = getSourcesToQuery(query);
+ ArrayList<Corpus> corporaToQuery = getCorporaToQuery(query);
final Suggestions suggestions = new Suggestions(mPromoter,
mConfig.getMaxPromotedSuggestions(),
query,
- sourcesToQuery.size());
+ corporaToQuery.size());
SuggestionCursor shortcuts = getShortcutsForQuery(query);
if (shortcuts != null) {
suggestions.setShortcuts(shortcuts);
}
// Fast path for the zero sources case
- if (sourcesToQuery.size() == 0) {
+ if (corporaToQuery.size() == 0) {
return suggestions;
}
@@ -126,8 +126,8 @@
mBatchingExecutor, suggestions);
int maxResultsPerSource = mConfig.getMaxResultsPerSource();
- for (Source source : sourcesToQuery) {
- QueryTask task = new QueryTask(query, source, maxResultsPerSource, receiver);
+ for (Corpus corpus : corporaToQuery) {
+ QueryTask task = new QueryTask(query, corpus, maxResultsPerSource, receiver);
mBatchingExecutor.execute(task);
}
@@ -144,11 +144,11 @@
mSuggestions = suggestions;
}
- public void receiveSuggestionCursor(final SuggestionCursor cursor) {
+ public void receiveSuggestionCursor(final CorpusResult cursor) {
updateShouldQueryStrategy(cursor);
mPublishThread.post(new Runnable() {
public void run() {
- mSuggestions.addSourceResult(cursor);
+ mSuggestions.addCorpusResult(cursor);
if (!mSuggestions.isClosed()) {
executeNextBatchIfNeeded();
}
@@ -172,30 +172,26 @@
*/
private static class QueryTask implements SourceTask {
private final String mQuery;
- private final Source mSource;
+ private final Corpus mCorpus;
private final int mQueryLimit;
private final SuggestionCursorReceiver mReceiver;
- public QueryTask(String query, Source source, int queryLimit,
+ public QueryTask(String query, Corpus corpus, int queryLimit,
SuggestionCursorReceiver receiver) {
mQuery = query;
- mSource = source;
+ mCorpus = corpus;
mQueryLimit = queryLimit;
mReceiver = receiver;
}
- public Source getSource() {
- return mSource;
- }
-
public void run() {
- SuggestionCursor cursor = mSource.getSuggestions(mQuery, mQueryLimit);
+ CorpusResult cursor = mCorpus.getSuggestions(mQuery, mQueryLimit);
mReceiver.receiveSuggestionCursor(cursor);
}
@Override
public String toString() {
- return mSource + "[" + mQuery + "]";
+ return mCorpus + "[" + mQuery + "]";
}
}
}
diff --git a/src/com/android/quicksearchbox/Config.java b/src/com/android/quicksearchbox/Config.java
index a0d40d6..ea27959 100644
--- a/src/com/android/quicksearchbox/Config.java
+++ b/src/com/android/quicksearchbox/Config.java
@@ -36,7 +36,7 @@
private static final long DAY_MILLIS = 86400000L;
- private static final int NUM_PROMOTED_SOURCES = 4;
+ private static final int NUM_PROMOTED_SOURCES = 3;
private static final int MAX_PROMOTED_SUGGESTIONS = 8;
private static final int MAX_RESULTS_PER_SOURCE = 50;
private static final long SOURCE_TIMEOUT_MILLIS = 10000;
@@ -55,7 +55,7 @@
private static final long THREAD_START_DELAY_MILLIS = 100;
private final Context mContext;
- private HashSet<String> mTrustedPackages;
+ private HashSet<String> mDefaultCorpora;
/**
* Creates a new config that uses hard-coded default values.
@@ -76,35 +76,29 @@
public void close() {
}
- private HashSet<String> loadTrustedPackages() {
- HashSet<String> trusted = new HashSet<String>();
-
+ private HashSet<String> loadDefaultCorpora() {
+ HashSet<String> defaultCorpora = new HashSet<String>();
try {
- // Get the list of trusted packages from a resource, which allows vendor overlays.
- String[] trustedPackages = mContext.getResources().getStringArray(
- R.array.trusted_search_providers);
- if (trustedPackages == null) {
- Log.w(TAG, "Could not load list of trusted search providers, trusting none");
- return trusted;
+ // Get the list of default corpora from a resource, which allows vendor overlays.
+ String[] corpora = mContext.getResources().getStringArray(R.array.default_corpora);
+ for (String corpus : corpora) {
+ defaultCorpora.add(corpus);
}
- for (String trustedPackage : trustedPackages) {
- trusted.add(trustedPackage);
- }
- return trusted;
+ return defaultCorpora;
} catch (Resources.NotFoundException ex) {
- Log.w(TAG, "Could not load list of trusted search providers, trusting none");
- return trusted;
+ Log.e(TAG, "Could not load default corpora", ex);
+ return defaultCorpora;
}
}
/**
* Checks if we trust the given source not to be spammy.
*/
- public synchronized boolean isTrustedSource(String packageName) {
- if (mTrustedPackages == null) {
- mTrustedPackages = loadTrustedPackages();
+ public synchronized boolean isCorpusEnabledByDefault(String corpusName) {
+ if (mDefaultCorpora == null) {
+ mDefaultCorpora = loadDefaultCorpora();
}
- return mTrustedPackages.contains(packageName);
+ return mDefaultCorpora.contains(corpusName);
}
/**
diff --git a/src/com/android/quicksearchbox/Corpora.java b/src/com/android/quicksearchbox/Corpora.java
new file mode 100644
index 0000000..e1ced04
--- /dev/null
+++ b/src/com/android/quicksearchbox/Corpora.java
@@ -0,0 +1,57 @@
+/*
+ * 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.content.ComponentName;
+
+import java.util.Collection;
+
+/**
+ * Maintains the set of available and enabled corpora.
+ */
+public interface Corpora {
+
+ boolean isCorpusEnabled(Corpus corpus);
+
+ /**
+ * Checks if a corpus should be enabled by default.
+ */
+ boolean isCorpusDefaultEnabled(Corpus corpus);
+
+ /**
+ * Gets all corpora, including the web corpus.
+ *
+ * @return Callers must not modify the returned collection.
+ */
+ Collection<Corpus> getAllCorpora();
+
+ Collection<Corpus> getEnabledCorpora();
+
+ /**
+ * Gets a corpus by name.
+ *
+ * @return A corpus, or null.
+ */
+ Corpus getCorpus(String name);
+
+ Source getSource(ComponentName name);
+
+ /**
+ * Gets the corpus that contains the given source.
+ */
+ Corpus getCorpusForSource(Source source);
+}
diff --git a/src/com/android/quicksearchbox/Corpus.java b/src/com/android/quicksearchbox/Corpus.java
new file mode 100644
index 0000000..5853a81
--- /dev/null
+++ b/src/com/android/quicksearchbox/Corpus.java
@@ -0,0 +1,78 @@
+/*
+ * 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.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * A corpus is a user-visible set of suggestions. A corpus gets suggestions from one
+ * or more sources.
+ *
+ * Objects that implement this interface should override {@link Object#equals(Object)}
+ * and {@link Object#hashCode()} so that they can be used as keys in hash maps.
+ */
+public interface Corpus {
+
+ /**
+ * Gets the localized, human-readable label for this corpus.
+ */
+ CharSequence getLabel();
+
+ /**
+ * Gets the icon for this corpus.
+ */
+ Drawable getCorpusIcon();
+
+ /**
+ * Gets the icon URI for this corpus.
+ */
+ Uri getCorpusIconUri();
+
+ /**
+ * Gets the description to use for this corpus in system search settings.
+ */
+ CharSequence getSettingsDescription();
+
+ /**
+ * Gets suggestions from this corpus.
+ *
+ * @param query The user query.
+ * @param queryLimit An advisory maximum number of results that the source should return.
+ * @return The suggestion results.
+ */
+ CorpusResult getSuggestions(String query, int queryLimit);
+
+ /**
+ * Gets the unique name for this corpus.
+ */
+ String getName();
+
+ int getQueryThreshold();
+
+ boolean queryAfterZeroResults();
+
+ boolean voiceSearchEnabled();
+
+ Intent createSearchIntent(String query, Bundle appData);
+
+ Intent createVoiceSearchIntent(Bundle appData);
+
+ boolean isWebCorpus();
+}
diff --git a/src/com/android/quicksearchbox/SourceFactory.java b/src/com/android/quicksearchbox/CorpusRanker.java
similarity index 68%
rename from src/com/android/quicksearchbox/SourceFactory.java
rename to src/com/android/quicksearchbox/CorpusRanker.java
index e661498..e9f43c3 100644
--- a/src/com/android/quicksearchbox/SourceFactory.java
+++ b/src/com/android/quicksearchbox/CorpusRanker.java
@@ -16,14 +16,14 @@
package com.android.quicksearchbox;
-import android.app.SearchableInfo;
+import java.util.ArrayList;
+import java.util.Collection;
-public interface SourceFactory {
+/**
+ * Orders corpora by importance.
+ */
+public interface CorpusRanker {
- // TODO: perhaps SearchManager.getSearchablesInGlobalSearch()
- // should return List<ComponentName>, so we could use ComponentName here?
- Source createSource(SearchableInfo searchable);
-
- Source createWebSearchSource();
+ ArrayList<Corpus> rankCorpora(Collection<Corpus> corpora);
}
diff --git a/src/com/android/quicksearchbox/SourceFactory.java b/src/com/android/quicksearchbox/CorpusResult.java
similarity index 62%
copy from src/com/android/quicksearchbox/SourceFactory.java
copy to src/com/android/quicksearchbox/CorpusResult.java
index e661498..ce2bd9d 100644
--- a/src/com/android/quicksearchbox/SourceFactory.java
+++ b/src/com/android/quicksearchbox/CorpusResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -16,14 +16,20 @@
package com.android.quicksearchbox;
-import android.app.SearchableInfo;
-public interface SourceFactory {
+/**
+ * A sequence of suggestions from a single corpus.
+ */
+public interface CorpusResult extends SuggestionCursor {
- // TODO: perhaps SearchManager.getSearchablesInGlobalSearch()
- // should return List<ComponentName>, so we could use ComponentName here?
- Source createSource(SearchableInfo searchable);
+ /**
+ * Gets the corpus that produced these suggestions.
+ */
+ Corpus getCorpus();
- Source createWebSearchSource();
+ /**
+ * The user query that returned these suggestions.
+ */
+ String getUserQuery();
}
diff --git a/src/com/android/quicksearchbox/SelectSearchSourceDialog.java b/src/com/android/quicksearchbox/CorpusSelectionDialog.java
similarity index 67%
rename from src/com/android/quicksearchbox/SelectSearchSourceDialog.java
rename to src/com/android/quicksearchbox/CorpusSelectionDialog.java
index 47655c1..3005fe2 100644
--- a/src/com/android/quicksearchbox/SelectSearchSourceDialog.java
+++ b/src/com/android/quicksearchbox/CorpusSelectionDialog.java
@@ -16,14 +16,12 @@
package com.android.quicksearchbox;
-import com.android.quicksearchbox.ui.SearchSourceSelector;
-import com.android.quicksearchbox.ui.SourcesAdapter;
+import com.android.quicksearchbox.ui.CorporaAdapter;
import com.android.quicksearchbox.ui.SuggestionViewFactory;
import android.app.Dialog;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
@@ -38,35 +36,35 @@
/**
- * Search source selection dialog.
+ * Corpus selection dialog.
*/
-public class SelectSearchSourceDialog extends Dialog {
+public class CorpusSelectionDialog extends Dialog {
private static final boolean DBG = true;
private static final String TAG = "QSB.SelectSearchSourceDialog";
- private GridView mSourceList;
+ private GridView mCorpusGrid;
- private ComponentName mSource;
+ private Corpus mCorpus;
private String mQuery;
private Bundle mAppData;
- public SelectSearchSourceDialog(Context context) {
+ public CorpusSelectionDialog(Context context) {
super(context, R.style.Theme_SelectSearchSource);
- setContentView(R.layout.select_search_source);
- mSourceList = (GridView) findViewById(R.id.source_list);
- mSourceList.setOnItemClickListener(new SourceClickListener());
+ setContentView(R.layout.corpus_selection_dialog);
+ mCorpusGrid = (GridView) findViewById(R.id.corpus_grid);
+ mCorpusGrid.setOnItemClickListener(new CorpusClickListener());
// TODO: for some reason, putting this in the XML layout instead makes
// the list items unclickable.
- mSourceList.setFocusable(true);
+ mCorpusGrid.setFocusable(true);
setCanceledOnTouchOutside(true);
positionWindow();
}
- public void setSource(ComponentName source) {
- mSource = source;
+ public void setCorpus(Corpus corpus) {
+ mCorpus = corpus;
}
public void setQuery(String query) {
@@ -79,8 +77,8 @@
private void positionWindow() {
Resources resources = getContext().getResources();
- int x = resources.getDimensionPixelSize(R.dimen.select_source_x);
- int y = resources.getDimensionPixelSize(R.dimen.select_source_y);
+ int x = resources.getDimensionPixelSize(R.dimen.corpus_selection_dialog_x);
+ int y = resources.getDimensionPixelSize(R.dimen.corpus_selection_dialog_y);
positionArrowAt(x, y);
}
@@ -104,46 +102,50 @@
@Override
protected void onStart() {
super.onStart();
- updateSources();
+ updateCorpora();
}
- private void updateSources() {
- mSourceList.setAdapter(new SourcesAdapter(getViewFactory(), getGlobalSuggestionsProvider()));
+ private void updateCorpora() {
+ mCorpusGrid.setAdapter(
+ new CorporaAdapter(getViewFactory(), getCorpora(), getCorpusRanker()));
}
private QsbApplication getQsbApplication() {
return (QsbApplication) getContext().getApplicationContext();
}
- private SuggestionsProvider getGlobalSuggestionsProvider() {
- return getQsbApplication().getGlobalSuggestionsProvider();
+ private Corpora getCorpora() {
+ return getQsbApplication().getCorpora();
+ }
+
+ private CorpusRanker getCorpusRanker() {
+ return getQsbApplication().getCorpusRanker();
}
private SuggestionViewFactory getViewFactory() {
return getQsbApplication().getSuggestionViewFactory();
}
- protected void selectSource(Source source) {
+ protected void selectCorpus(Corpus corpus) {
dismiss();
// If a new source was selected, start QSB with that source.
// If the old source was selected, just finish.
- if (!isCurrentSource(source)) {
- switchSource(source);
+ if (!isCurrentCorpus(corpus)) {
+ switchCorpus(corpus);
}
}
- private boolean isCurrentSource(Source source) {
- if (source == null) return mSource == null;
- return source.getComponentName().equals(mSource);
+ private boolean isCurrentCorpus(Corpus corpus) {
+ if (corpus == null) return mCorpus == null;
+ return corpus.equals(mCorpus);
}
- private void switchSource(Source source) {
- if (DBG) Log.d(TAG, "switchSource(" + source + ")");
+ private void switchCorpus(Corpus corpus) {
+ if (DBG) Log.d(TAG, "switchSource(" + corpus + ")");
Intent searchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
searchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ComponentName sourceName = source == null ? null : source.getComponentName();
- SearchSourceSelector.setSource(searchIntent, sourceName);
+ searchIntent.setData(SearchActivity.getCorpusUri(corpus));
searchIntent.putExtra(SearchManager.QUERY, mQuery);
searchIntent.putExtra(SearchManager.APP_DATA, mAppData);
@@ -155,10 +157,10 @@
}
}
- private class SourceClickListener implements AdapterView.OnItemClickListener {
+ private class CorpusClickListener implements AdapterView.OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- Source source = (Source) parent.getItemAtPosition(position);
- selectSource(source);
+ Corpus corpus = (Corpus) parent.getItemAtPosition(position);
+ selectCorpus(corpus);
}
}
}
diff --git a/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.java b/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.java
index 4c88594..e1c618b 100644
--- a/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.java
+++ b/src/com/android/quicksearchbox/CursorBackedSuggestionCursor.java
@@ -17,20 +17,13 @@
package com.android.quicksearchbox;
import android.app.SearchManager;
-import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
-import android.graphics.Rect;
+import android.database.DataSetObserver;
import android.net.Uri;
-import android.os.Bundle;
-import android.os.Looper;
import android.util.Log;
-import android.view.KeyEvent;
-import java.net.URISyntaxException;
-
-public abstract class CursorBackedSuggestionCursor extends AbstractSourceSuggestionCursor {
+public abstract class CursorBackedSuggestionCursor extends AbstractSuggestionCursor {
private static final boolean DBG = false;
protected static final String TAG = "QSB.CursorBackedSuggestionCursor";
@@ -38,22 +31,22 @@
/** The suggestions, or {@code null} if the suggestions query failed. */
protected final Cursor mCursor;
- /** Column index of {@link SearchManager.SUGGEST_COLUMN_FORMAT} in @{link mCursor}. */
+ /** Column index of {@link SearchManager#SUGGEST_COLUMN_FORMAT} in @{link mCursor}. */
private final int mFormatCol;
- /** Column index of {@link SearchManager.SUGGEST_COLUMN_TEXT_1} in @{link mCursor}. */
+ /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_1} in @{link mCursor}. */
private final int mText1Col;
- /** Column index of {@link SearchManager.SUGGEST_COLUMN_TEXT_2} in @{link mCursor}. */
+ /** Column index of {@link SearchManager#SUGGEST_COLUMN_TEXT_2} in @{link mCursor}. */
private final int mText2Col;
- /** Column index of {@link SearchManager.SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */
+ /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */
private final int mIcon1Col;
- /** Column index of {@link SearchManager.SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */
+ /** Column index of {@link SearchManager#SUGGEST_COLUMN_ICON_1} in @{link mCursor}. */
private final int mIcon2Col;
- /** Column index of {@link SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING}
+ /** Column index of {@link SearchManager#SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING}
* in @{link mCursor}.
**/
private final int mRefreshSpinnerCol;
@@ -72,24 +65,10 @@
mRefreshSpinnerCol = getColumnIndex(SearchManager.SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING);
}
- protected String getDefaultIntentAction() {
- return getSource().getDefaultIntentAction();
- }
+ public abstract Source getSuggestionSource();
- protected String getDefaultIntentData() {
- return getSource().getDefaultIntentData();
- }
-
- protected boolean shouldRewriteQueryFromData() {
- return getSource().shouldRewriteQueryFromData();
- }
-
- protected boolean shouldRewriteQueryFromText() {
- return getSource().shouldRewriteQueryFromText();
- }
-
- public boolean isFailed() {
- return mCursor == null;
+ public String getSuggestionLogType() {
+ return getSuggestionSource().getLogName();
}
public void close() {
@@ -126,11 +105,9 @@
throw new IllegalStateException("moveTo(" + pos + ") after close()");
}
// TODO: all operations on cross-process cursors can throw random exceptions
- if (mCursor == null || pos < 0 || pos >= mCursor.getCount()) {
- throw new IndexOutOfBoundsException(pos + ", count=" + getCount());
+ if (!mCursor.moveToPosition(pos)) {
+ throw new IllegalArgumentException("Move to " + pos + ", count=" + getCount());
}
- // TODO: all operations on cross-process cursors can throw random exceptions
- mCursor.moveToPosition(pos);
}
public int getPosition() {
@@ -140,26 +117,6 @@
return mCursor.getPosition();
}
- public String getSuggestionDisplayQuery() {
- String query = getSuggestionQuery();
- if (query != null) {
- return query;
- }
- if (shouldRewriteQueryFromData()) {
- String data = getSuggestionIntentDataString();
- if (data != null) {
- return data;
- }
- }
- if (shouldRewriteQueryFromText()) {
- String text1 = getSuggestionText1();
- if (text1 != null) {
- return text1;
- }
- }
- return null;
- }
-
public String getShortcutId() {
return getStringOrNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
}
@@ -188,67 +145,14 @@
return "true".equals(getStringOrNull(mRefreshSpinnerCol));
}
- public Intent getSuggestionIntent(Context context, Bundle appSearchData,
- int actionKey, String actionMsg) {
- String action = getSuggestionIntentAction();
- Uri data = getSuggestionIntentData();
- 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(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);
- }
- if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
- intent.putExtra(SearchManager.ACTION_KEY, actionKey);
- intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
- }
- // TODO: Use this to tell sources this comes form global search
- // The constants are currently hidden.
- // intent.putExtra(SearchManager.SEARCH_MODE,
- // SearchManager.MODE_GLOBAL_SEARCH_SUGGESTION);
- intent.setComponent(getSourceComponentName());
- return intent;
- }
-
- public String getActionKeyMsg(int keyCode) {
- String result = null;
- String column = getSource().getSuggestActionMsgColumn(keyCode);
- if (column != null) {
- result = getStringOrNull(column);
- }
- // If the cursor didn't give us a message, see if there's a single message defined
- // for the actionkey (for all suggestions)
- if (result == null) {
- result = getSource().getSuggestActionMsg(keyCode);
- }
- return result;
- }
-
/**
* Gets the intent action for the current suggestion.
*/
- protected String getSuggestionIntentAction() {
+ public String getSuggestionIntentAction() {
// use specific action if supplied, or default action if supplied, or fixed default
String action = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
if (action == null) {
- action = getDefaultIntentAction();
+ action = getSuggestionSource().getDefaultIntentAction();
if (action == null) {
action = Intent.ACTION_SEARCH;
}
@@ -259,7 +163,7 @@
/**
* Gets the query for the current suggestion.
*/
- protected String getSuggestionQuery() {
+ public String getSuggestionQuery() {
return getStringOrNull(SearchManager.SUGGEST_COLUMN_QUERY);
}
@@ -267,7 +171,7 @@
// use specific data if supplied, or default data if supplied
String data = getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
if (data == null) {
- data = getDefaultIntentData();
+ data = getSuggestionSource().getDefaultIntentData();
}
// then, if an ID was provided, append it.
if (data != null) {
@@ -280,22 +184,35 @@
}
/**
- * Gets the intent data for the current suggestion.
- */
- protected Uri getSuggestionIntentData() {
- String data = getSuggestionIntentDataString();
- return (data == null) ? null : Uri.parse(data);
- }
-
- /**
* Gets the intent extra data for the current suggestion.
*/
public String getSuggestionIntentExtraData() {
return getStringOrNull(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
}
+ public String getSuggestionDisplayQuery() {
+ String query = getSuggestionQuery();
+ if (query != null) {
+ return query;
+ }
+ Source source = getSuggestionSource();
+ if (source.shouldRewriteQueryFromData()) {
+ String data = getSuggestionIntentDataString();
+ if (data != null) {
+ return data;
+ }
+ }
+ if (source.shouldRewriteQueryFromText()) {
+ String text1 = getSuggestionText1();
+ if (text1 != null) {
+ return text1;
+ }
+ }
+ return null;
+ }
+
/**
- * Gets the index of a column in {@link mCursor} by name.
+ * Gets the index of a column in {@link #mCursor} by name.
*
* @return The index, or {@code -1} if the column was not found.
*/
@@ -306,7 +223,7 @@
}
/**
- * Gets the string value of a column in {@link mCursor} by column index.
+ * Gets the string value of a column in {@link #mCursor} by column index.
*
* @param col Column index.
* @return The string value, or {@code null}.
@@ -328,7 +245,7 @@
}
/**
- * Gets the string value of a column in {@link mCursor} by column name.
+ * Gets the string value of a column in {@link #mCursor} by column name.
*
* @param colName Column name.
* @return The string value, or {@code null}.
@@ -357,4 +274,12 @@
.append(query)
.toString();
}
+
+ public void registerDataSetObserver(DataSetObserver observer) {
+ // We don't watch Cursor-backed SuggestionCursors for changes
+ }
+
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ // We don't watch Cursor-backed SuggestionCursors for changes
+ }
}
diff --git a/src/com/android/quicksearchbox/DefaultCorpusRanker.java b/src/com/android/quicksearchbox/DefaultCorpusRanker.java
new file mode 100644
index 0000000..263abc8
--- /dev/null
+++ b/src/com/android/quicksearchbox/DefaultCorpusRanker.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.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.PriorityQueue;
+
+public class DefaultCorpusRanker implements CorpusRanker {
+
+ private static final boolean DBG = true;
+ private static final String TAG = "QSB.DefaultCorpusRanker";
+
+ private final ShortcutRepository mShortcuts;
+
+ public DefaultCorpusRanker(ShortcutRepository shortcuts) {
+ mShortcuts = shortcuts;
+ }
+
+ private static class ScoredCorpus implements Comparable<ScoredCorpus> {
+ public final Corpus mCorpus;
+ public final int mScore;
+ public ScoredCorpus(Corpus corpus, int score) {
+ mCorpus = corpus;
+ mScore = score;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (! (o instanceof ScoredCorpus)) return false;
+ ScoredCorpus sc = (ScoredCorpus) o;
+ return mCorpus.equals(sc.mCorpus) && mScore == sc.mScore;
+ }
+
+ public int compareTo(ScoredCorpus another) {
+ int scoreDiff = mScore - another.mScore;
+ if (scoreDiff != 0) {
+ return scoreDiff;
+ } else {
+ return mCorpus.getName().compareTo(another.mCorpus.getName());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mCorpus + ":" + mScore;
+ }
+ }
+
+ /**
+ * Scores a corpus. Higher score is better.
+ */
+ private int getCorpusScore(Corpus corpus, Map<String,Integer> clickScores) {
+ if (corpus.isWebCorpus()) {
+ return Integer.MAX_VALUE;
+ }
+ Integer clickScore = clickScores.get(clickScores);
+ if (clickScore != null) {
+ return clickScore;
+ }
+ return 0;
+ }
+
+ public ArrayList<Corpus> rankCorpora(Collection<Corpus> corpora) {
+ if (DBG) Log.d(TAG, "Ranking: " + corpora);
+ // For some reason, PriorityQueue throws IllegalArgumentException if given
+ // an initial capacity < 1.
+ int capacity = 1 + corpora.size();
+ // PriorityQueue returns smallest element first, so we need a reverse comparator
+ PriorityQueue<ScoredCorpus> queue =
+ new PriorityQueue<ScoredCorpus>(capacity,
+ new ReverseComparator<ScoredCorpus>());
+ Map<String,Integer> clickScores = mShortcuts.getCorpusScores();
+ for (Corpus corpus : corpora) {
+ int score = getCorpusScore(corpus, clickScores);
+ queue.add(new ScoredCorpus(corpus, score));
+ }
+ if (DBG) Log.d(TAG, "Corpus scores: " + queue);
+ ArrayList<Corpus> ordered = new ArrayList<Corpus>(queue.size());
+ for (ScoredCorpus scoredCorpus : queue) {
+ ordered.add(scoredCorpus.mCorpus);
+ }
+ return ordered;
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/DelayingSourceTaskExecutor.java b/src/com/android/quicksearchbox/DelayingSourceTaskExecutor.java
index df57d75..1e813ae 100644
--- a/src/com/android/quicksearchbox/DelayingSourceTaskExecutor.java
+++ b/src/com/android/quicksearchbox/DelayingSourceTaskExecutor.java
@@ -67,7 +67,7 @@
}
/**
- * Thread that moves tasks from {@link #mDelayedTasks} to {@link mExecutor}
+ * Thread that moves tasks from {@link #mDelayedTasks} to {@link #mExecutor}
* then the executor is running no tasks, or after a fixed delay.
*/
private static class TaskDelayer extends Thread implements SourceTaskExecutor {
@@ -210,7 +210,7 @@
/**
* Blocks until {@code mRunningTaskCount <= 0}, waiting for at most
- * {@link timeoutNanos}.
+ * {@code timeoutNanos}.
*
* @param timeoutNanos The maximum time to wait, in nanoseconds.
* @return {@code false} if the timeout has expired upon return, else {@code true}.
diff --git a/src/com/android/quicksearchbox/EventLogLogger.java b/src/com/android/quicksearchbox/EventLogLogger.java
index 427a4d1..f1240a1 100644
--- a/src/com/android/quicksearchbox/EventLogLogger.java
+++ b/src/com/android/quicksearchbox/EventLogLogger.java
@@ -16,7 +16,6 @@
package com.android.quicksearchbox;
-import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -61,52 +60,52 @@
return mVersionCode;
}
- public void logStart(int latency, String intentSource, Source currentSearchSource,
- ArrayList<Source> orderedSources) {
+ public void logStart(int latency, String intentSource, Corpus corpus,
+ ArrayList<Corpus> orderedCorpora) {
String packageName = mContext.getPackageName();
int version = mVersionCode;
// TODO: Add more info to startMethod
String startMethod = intentSource;
- String currentSource = getSourceLogName(currentSearchSource);
- String enabledSources = getSourceLogNames(orderedSources);
+ String currentCorpus = getCorpusLogName(corpus);
+ String enabledCorpora = getCorpusLogNames(orderedCorpora);
if (DBG){
debug("qsb_start", packageName, version, startMethod, latency,
- currentSource, enabledSources);
+ currentCorpus, enabledCorpora);
}
EventLogTags.writeQsbStart(packageName, version, startMethod,
- latency, currentSource, enabledSources);
+ latency, currentCorpus, enabledCorpora);
}
public void logSuggestionClick(int position,
- SuggestionCursor suggestionCursor, ArrayList<Source> queriedSources) {
+ SuggestionCursor suggestionCursor, ArrayList<Corpus> queriedCorpora) {
String suggestions = getSuggestions(suggestionCursor);
- String sources = getSourceLogNames(queriedSources);
+ String corpora = getCorpusLogNames(queriedCorpora);
int numChars = suggestionCursor.getUserQuery().length();
- EventLogTags.writeQsbClick(position, suggestions, sources, numChars);
+ EventLogTags.writeQsbClick(position, suggestions, corpora, numChars);
}
- public void logSearch(Source searchSource, int startMethod, int numChars) {
- String sourceName = getSourceLogName(searchSource);
- EventLogTags.writeQsbSearch(sourceName, startMethod, numChars);
+ public void logSearch(Corpus corpus, int startMethod, int numChars) {
+ String corpusName = getCorpusLogName(corpus);
+ EventLogTags.writeQsbSearch(corpusName, startMethod, numChars);
}
- public void logVoiceSearch(Source searchSource) {
- String sourceName = getSourceLogName(searchSource);
- EventLogTags.writeQsbVoiceSearch(sourceName);
+ public void logVoiceSearch(Corpus corpus) {
+ String corpusName = getCorpusLogName(corpus);
+ EventLogTags.writeQsbVoiceSearch(corpusName);
}
- public void logExit(SuggestionCursor suggestionCursor) {
+ public void logExit(SuggestionCursor suggestionCursor, int numChars) {
String suggestions = getSuggestions(suggestionCursor);
- EventLogTags.writeQsbExit(suggestions);
+ EventLogTags.writeQsbExit(suggestions, numChars);
}
public void logWebLatency() {
}
- private String getSourceLogName(Source source) {
- if (source == null) return null;
- return source.getLogName();
+ private String getCorpusLogName(Corpus corpus) {
+ if (corpus == null) return null;
+ return corpus.getName();
}
private String getSuggestions(SuggestionCursor suggestionCursor) {
@@ -115,17 +114,17 @@
for (int i = 0; i < count; i++) {
if (i > 0) sb.append(LIST_SEPARATOR);
suggestionCursor.moveTo(i);
- sb.append(suggestionCursor.getLogName());
+ sb.append(suggestionCursor.getSuggestionLogType());
}
return sb.toString();
}
- private String getSourceLogNames(ArrayList<Source> sources) {
+ private String getCorpusLogNames(ArrayList<Corpus> corpora) {
StringBuilder sb = new StringBuilder();
- final int count = sources.size();
+ final int count = corpora.size();
for (int i = 0; i < count; i++) {
if (i > 0) sb.append(LIST_SEPARATOR);
- sb.append(getSourceLogName(sources.get(i)));
+ sb.append(getCorpusLogName(corpora.get(i)));
}
return sb.toString();
}
diff --git a/src/com/android/quicksearchbox/EventLogTags.logtags b/src/com/android/quicksearchbox/EventLogTags.logtags
index 42f18ba..cb5a3da 100644
--- a/src/com/android/quicksearchbox/EventLogTags.logtags
+++ b/src/com/android/quicksearchbox/EventLogTags.logtags
@@ -2,34 +2,55 @@
option java_package com.android.quicksearchbox
-# TODO: Include start method:
-# - home screen widget
-# - through source selector
-# - by touching text field
-# - search hard key on home screen
-# - menu -> search on home screen
-# - source selector in in-app search dialog
-# - search hardkey in in-app search dialog
-# - search hardkey in non-searchable app
-# - app called SearchManager.startSearch()
+# QSB started
# @param name Package name of the QSB app.
# @param version QSB app versionCode value.
-# @param sources A string containing a pipe-separated list of source names,
-# ordered by source ranking.
+# @param start_method
+# TODO: Define values for start_method:
+# - home screen widget
+# - through source selector
+# - by touching text field
+# - search hard key on home screen
+# - menu -> search on home screen
+# - source selector in in-app search dialog
+# - search hardkey in in-app search dialog
+# - search hardkey in non-searchable app
+# - app called SearchManager.startSearch()
+# @param latency start-up latency as seen by QSB
+# @param search_source name of the initially selected search source
+# @param enabled_sources A pipe-separated list of source names, ordered by source ranking.
# TODO: Which are promoted?
-80000 qsb_start (name|3),(version|1),(start_method|3),(latency|1|3),(current_source|3),(enabled_sources|3)
+80000 qsb_start (name|3),(version|1),(start_method|3),(latency|1|3),(search_source|3),(enabled_sources|3)
+# User clicked on a suggestion
+# @param position 0-based index of the clicked suggestion
+# @param A pipe-separated list of suggestion log names.
+# TODO: define format of suggestion log names
+# @param queried_sources A pipe-separated list of the sources that were queried to produce
+# the list of suggestions shown.
+# @param Number of characters in the query typed by the user
# TODO: action key?
-# TODO: number of chars in typed query?
# TODO: latency?
80100 qsb_click (position|1),(suggestions|3),(queried_sources|3),(num_chars|1)
-# TODO: define method values (search button, enter key)
-80102 qsb_search (search_source|3),(method|1),(num_chars|1)
-80103 qsb_voice_search (source|3)
-# User left QSB without clicking / searching
-# TODO: number of chars typed?
-80104 qsb_exit (suggestions|3)
+# User launched a typed search
+# @param search_source Name of the selected search source
+# @param method
+# SEARCH_METHOD_BUTTON = 0
+# SEARCH_METHOD_KEYBOARD = 1
+# @param num_chars The number of characters in the search query
+80102 qsb_search (search_source|3),(method|1),(num_chars|1)
+
+# User launched a Voice Search
+# @param search_source Name of the selected search source
+80103 qsb_voice_search (search_source|3)
+
+# User left QSB without clicking / searching
+# @param suggestions The suggestions shown when the user left QSB. See qsb_click above.
+# @param num_chars The number of characters in the query text field when the user left
+80104 qsb_exit (suggestions|3),(num_chars|1)
+
+# Suggestion latency of the web suggestion source
# This is total latency from typing a character to having all results.
# Log only for N% of queries?
# TODO: blended vs single-app
diff --git a/src/com/android/quicksearchbox/GlobalSuggestionsProvider.java b/src/com/android/quicksearchbox/GlobalSuggestionsProvider.java
index 4d571eb..268c18c 100644
--- a/src/com/android/quicksearchbox/GlobalSuggestionsProvider.java
+++ b/src/com/android/quicksearchbox/GlobalSuggestionsProvider.java
@@ -16,12 +16,9 @@
package com.android.quicksearchbox;
-import android.content.ComponentName;
import android.os.Handler;
-import android.util.Log;
import java.util.ArrayList;
-import java.util.LinkedHashSet;
/**
* A suggestions provider that gets suggestions from all enabled sources that
@@ -29,50 +26,28 @@
*/
public class GlobalSuggestionsProvider extends AbstractSuggestionsProvider {
- private static final boolean DBG = true;
- private static final String TAG = "QSB.GlobalSuggestionsProvider";
+ private final Corpora mCorpora;
- private final SourceLookup mSources;
+ private final CorpusRanker mCorpusRanker;
private final ShortcutRepository mShortcutRepo;
- public GlobalSuggestionsProvider(Config config, SourceLookup sources,
+ public GlobalSuggestionsProvider(Config config, Corpora corpora,
SourceTaskExecutor queryExecutor,
Handler publishThread,
Promoter promoter,
+ CorpusRanker corpusRanker,
ShortcutRepository shortcutRepo) {
super(config, queryExecutor, publishThread, promoter);
- mSources = sources;
+ mCorpora = corpora;
+ mCorpusRanker = corpusRanker;
mShortcutRepo = shortcutRepo;
}
// TODO: Cache this list?
@Override
- public ArrayList<Source> getOrderedSources() {
- // Using a LinkedHashSet to get the sources in the order added while
- // avoiding duplicates.
- LinkedHashSet<Source> orderedSources = new LinkedHashSet<Source>();
- if (mSources.areWebSuggestionsEnabled()) {
- // Add web search source first, so that it's always queried first,
- // to do network traffic while the rest are using the CPU.
- Source webSource = mSources.getWebSearchSource();
- if (webSource != null) {
- orderedSources.add(webSource);
- }
- }
- // Then add all ranked sources
- ArrayList<ComponentName> rankedSources = mShortcutRepo.getSourceRanking();
- if (DBG) Log.d(TAG, "Ranked sources: " + rankedSources);
- for (ComponentName sourceName : rankedSources) {
- Source source = mSources.getSourceByComponentName(sourceName);
- if (source != null && mSources.isEnabledSource(source)) {
- orderedSources.add(source);
- }
- }
- // Last, add all unranked enabled sources.
- orderedSources.addAll(mSources.getEnabledSources());
- if (DBG) Log.d(TAG, "All sources ordered " + orderedSources);
- return new ArrayList<Source>(orderedSources);
+ public ArrayList<Corpus> getOrderedCorpora() {
+ return mCorpusRanker.rankCorpora(mCorpora.getEnabledCorpora());
}
@Override
diff --git a/src/com/android/quicksearchbox/Launcher.java b/src/com/android/quicksearchbox/Launcher.java
index b280e38..cc43ced 100644
--- a/src/com/android/quicksearchbox/Launcher.java
+++ b/src/com/android/quicksearchbox/Launcher.java
@@ -24,8 +24,6 @@
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.util.Log;
-import android.webkit.URLUtil;
-import com.android.common.Patterns;
/**
* Launches suggestions and searches.
@@ -42,8 +40,6 @@
/**
* Data sent by the app that launched QSB.
- *
- * @param appSearchData
*/
public Launcher(Context context) {
mContext = context;
@@ -53,106 +49,75 @@
mAppSearchData = appSearchData;
}
- public boolean isVoiceSearchAvailable() {
- Intent intent = createVoiceSearchIntent();
- ResolveInfo ri = mContext.getPackageManager().
+ public static boolean shouldShowVoiceSearch(Context context, Corpus corpus) {
+ if (corpus != null && !corpus.voiceSearchEnabled()) {
+ return false;
+ }
+ Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ ResolveInfo ri = context.getPackageManager().
resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
return ri != null;
}
- public void startVoiceSearch() {
- launchIntent(createVoiceSearchIntent());
- }
-
- public void startSearch(Source source, String query) {
- if (source == null) {
- startWebSearch(query);
+ public void startVoiceSearch(Corpus corpus) {
+ if (corpus == null) {
+ launchIntent(WebCorpus.createVoiceWebSearchIntent(mAppSearchData));
} else {
- Intent intent = createSourceSearchIntent(source, query);
- launchIntent(intent);
+ launchIntent(corpus.createVoiceSearchIntent(mAppSearchData));
}
}
- /**
- * Launches a web search.
- */
- public void startWebSearch(String query) {
- Intent intent = Patterns.WEB_URL.matcher(query).matches()
- ? createBrowseIntent(query)
- : createWebSearchIntent(query);
- if (intent != null) {
- launchIntent(intent);
+ public void startSearch(Corpus corpus, String query) {
+ if (corpus == null) {
+ launchIntent(WebCorpus.createWebIntent(query, mAppSearchData));
+ } else {
+ launchIntent(corpus.createSearchIntent(query, mAppSearchData));
}
}
- private Intent createVoiceSearchIntent() {
- 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);
- // TODO: Should we include SearchManager.APP_DATA in the voice search intent?
- // SearchDialog doesn't seem to, but it would make sense.
- return intent;
- }
-
- // TODO: not all apps handle ACTION_SEARCH properly, e.g. ApplicationsProvider.
- // Maybe we should add a flag to searchable, so that QSB can hide the search button?
- private Intent createSourceSearchIntent(Source source, String query) {
- Intent intent = new Intent(Intent.ACTION_SEARCH);
- intent.setComponent(source.getComponentName());
- 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 (mAppSearchData != null) {
- intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
- }
- return intent;
- }
-
- private Intent createWebSearchIntent(String query) {
- 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 (mAppSearchData != null) {
- intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
- }
- // 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 Intent createBrowseIntent(String url) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.addCategory(Intent.CATEGORY_BROWSABLE);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- url = URLUtil.guessUrl(url);
- intent.setData(Uri.parse(url));
- return intent;
- }
-
/**
* Launches a suggestion.
*/
- public void launchSuggestion(SuggestionPosition suggestionPos,
- int actionKey, String actionMsg) {
- SuggestionCursor suggestion = suggestionPos.getSuggestion();
- Intent intent = suggestion.getSuggestionIntent(mContext, mAppSearchData,
- actionKey, actionMsg);
- if (intent != null) {
- launchIntent(intent);
+ public void launchSuggestion(SuggestionCursor cursor, int position) {
+ launchIntent(getSuggestionIntent(cursor, position));
+ }
+
+ public Intent getSuggestionIntent(SuggestionCursor cursor, int position) {
+ cursor.moveTo(position);
+ String action = cursor.getSuggestionIntentAction();
+ 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 (mAppSearchData != null) {
+ intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
+ }
+
+ intent.setComponent(cursor.getSuggestionSource().getComponentName());
+ return intent;
}
private void launchIntent(Intent intent) {
+ if (intent == null) {
+ return;
+ }
try {
mContext.startActivity(intent);
} catch (RuntimeException ex) {
diff --git a/src/com/android/quicksearchbox/ListSuggestionCursor.java b/src/com/android/quicksearchbox/ListSuggestionCursor.java
index 55d7d64..db9dd42 100644
--- a/src/com/android/quicksearchbox/ListSuggestionCursor.java
+++ b/src/com/android/quicksearchbox/ListSuggestionCursor.java
@@ -16,13 +16,8 @@
package com.android.quicksearchbox;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
import java.util.ArrayList;
@@ -34,6 +29,8 @@
*/
public class ListSuggestionCursor extends AbstractSuggestionCursor {
+ private final DataSetObservable mDataSetObservable = new DataSetObservable();
+
private final ArrayList<SuggestionPosition> mSuggestions;
private int mPos;
@@ -57,11 +54,6 @@
public void close() {
mSuggestions.clear();
- super.close();
- }
-
- public boolean isFailed() {
- return false;
}
public int getPosition() {
@@ -84,42 +76,37 @@
return mSuggestions.size();
}
- private SuggestionCursor current() {
- return mSuggestions.get(mPos).getSuggestion();
+ protected SuggestionCursor current() {
+ return mSuggestions.get(mPos).current();
}
- public Drawable getIcon(String iconId) {
- return current().getIcon(iconId);
+ /**
+ * Register an observer that is called when changes happen to this data set.
+ *
+ * @param observer gets notified when the data set changes.
+ */
+ public void registerDataSetObserver(DataSetObserver observer) {
+ mDataSetObservable.registerObserver(observer);
}
- public Uri getIconUri(String iconId) {
- return current().getIconUri(iconId);
+ /**
+ * Unregister an observer that has previously been registered with
+ * {@link #registerDataSetObserver(DataSetObserver)}
+ *
+ * @param observer the observer to unregister.
+ */
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ mDataSetObservable.unregisterObserver(observer);
+ }
+
+ protected void notifyDataSetChanged() {
+ mDataSetObservable.notifyChanged();
}
public String getShortcutId() {
return current().getShortcutId();
}
- public ComponentName getSourceComponentName() {
- return current().getSourceComponentName();
- }
-
- public String getLogName() {
- return current().getLogName();
- }
-
- public Drawable getSourceIcon() {
- return current().getSourceIcon();
- }
-
- public Uri getSourceIconUri() {
- return current().getSourceIconUri();
- }
-
- public CharSequence getSourceLabel() {
- return current().getSourceLabel();
- }
-
public String getSuggestionDisplayQuery() {
return current().getSuggestionDisplayQuery();
}
@@ -136,21 +123,32 @@
return current().getSuggestionIcon2();
}
- public boolean isSpinnerWhileRefreshing() {
- return current().isSpinnerWhileRefreshing();
+ public String getSuggestionIntentAction() {
+ return current().getSuggestionIntentAction();
}
- public Intent getSuggestionIntent(Context context, Bundle appSearchData, int actionKey,
- String actionMsg) {
- return current().getSuggestionIntent(context, appSearchData, actionKey, actionMsg);
+ public String getSuggestionIntentDataString() {
+ return current().getSuggestionIntentDataString();
}
public String getSuggestionIntentExtraData() {
return current().getSuggestionIntentExtraData();
}
- public String getSuggestionIntentDataString() {
- return current().getSuggestionIntentDataString();
+ public String getSuggestionKey() {
+ return current().getSuggestionKey();
+ }
+
+ public String getSuggestionLogType() {
+ return current().getSuggestionLogType();
+ }
+
+ public String getSuggestionQuery() {
+ return current().getSuggestionQuery();
+ }
+
+ public Source getSuggestionSource() {
+ return current().getSuggestionSource();
}
public String getSuggestionText1() {
@@ -161,12 +159,7 @@
return current().getSuggestionText2();
}
- public String getSuggestionKey() {
- return current().getSuggestionKey();
+ public boolean isSpinnerWhileRefreshing() {
+ return current().isSpinnerWhileRefreshing();
}
-
- public String getActionKeyMsg(int keyCode) {
- return current().getActionKeyMsg(keyCode);
- }
-
}
diff --git a/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.java b/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.java
index 37fb58e..7925766 100644
--- a/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.java
+++ b/src/com/android/quicksearchbox/ListSuggestionCursorNoDuplicates.java
@@ -23,14 +23,13 @@
/**
* A SuggestionCursor that is backed by a list of SuggestionPosition objects
* and doesn't allow duplicate suggestions.
- *
*/
public class ListSuggestionCursorNoDuplicates extends ListSuggestionCursor {
private static final boolean DBG = true;
private static final String TAG = "QSB.ListSuggestionCursorNoDuplicates";
- private HashSet<String> mSuggestionKeys;
+ private final HashSet<String> mSuggestionKeys;
public ListSuggestionCursorNoDuplicates(String userQuery) {
super(userQuery);
@@ -39,8 +38,7 @@
@Override
public boolean add(SuggestionPosition suggestionPos) {
- SuggestionCursor suggestion = suggestionPos.getSuggestion();
- String key = suggestion.getSuggestionKey();
+ String key = suggestionPos.current().getSuggestionKey();
if (mSuggestionKeys.add(key)) {
return super.add(suggestionPos);
} else {
diff --git a/src/com/android/quicksearchbox/Logger.java b/src/com/android/quicksearchbox/Logger.java
index 47798b8..8716768 100644
--- a/src/com/android/quicksearchbox/Logger.java
+++ b/src/com/android/quicksearchbox/Logger.java
@@ -32,42 +32,40 @@
*
* @param latency User-visible start-up latency in milliseconds.
*/
- void logStart(int latency, String intentSource, Source currentSearchSource,
- ArrayList<Source> orderedSources);
+ void logStart(int latency, String intentSource, Corpus corpus,
+ ArrayList<Corpus> orderedCorpora);
/**
* Called when a suggestion is clicked.
*
* @param position 0-based position of the suggestion in the UI.
* @param suggestionCursor all the suggestions shown in the UI.
- * @param queriedSources all sources that were queried to produce the suggestions in
+ * @param queriedCorpora all corpora that were queried to produce the suggestions in
* {@code suggestionCursor}, ordered by rank.
*/
void logSuggestionClick(int position, SuggestionCursor suggestionCursor,
- ArrayList<Source> queriedSources);
+ ArrayList<Corpus> queriedCorpora);
/**
* The user launched a search.
*
- * @param searchSource The search source. {@code null} means web search.
* @param startMethod One of {@link #SEARCH_METHOD_BUTTON} or {@link #SEARCH_METHOD_KEYBOARD}.
* @param numChars The number of characters in the query.
*/
- void logSearch(Source searchSource, int startMethod, int numChars);
+ void logSearch(Corpus corpus, int startMethod, int numChars);
/**
* The user launched a voice search.
- *
- * @param searchSource The search source. {@code null} means web search.
*/
- void logVoiceSearch(Source searchSource);
+ void logVoiceSearch(Corpus corpus);
/**
* The user left QSB without performing any action (click suggestions, search or voice search).
*
* @param suggestionCursor all the suggestions shown in the UI when the user left
+ * @param numChars The number of characters in the query typed when the user left.
*/
- void logExit(SuggestionCursor suggestionCursor);
+ void logExit(SuggestionCursor suggestionCursor, int numChars);
void logWebLatency();
diff --git a/src/com/android/quicksearchbox/PackageIconLoader.java b/src/com/android/quicksearchbox/PackageIconLoader.java
index bc78daa..28facd7 100644
--- a/src/com/android/quicksearchbox/PackageIconLoader.java
+++ b/src/com/android/quicksearchbox/PackageIconLoader.java
@@ -34,9 +34,8 @@
/**
* Loads icons from other packages.
*
- * Code partly stolen from {@link ContentResolver} and {@link android.app.SuggestionsAdapter}.
- *
- */
+ * Code partly stolen from {@link ContentResolver} and android.app.SuggestionsAdapter.
+ */
public class PackageIconLoader implements IconLoader {
private static final boolean DBG = false;
diff --git a/src/com/android/quicksearchbox/Promoter.java b/src/com/android/quicksearchbox/Promoter.java
index 4200f4e..ea25dd3 100644
--- a/src/com/android/quicksearchbox/Promoter.java
+++ b/src/com/android/quicksearchbox/Promoter.java
@@ -33,7 +33,7 @@
* @param promoted List to add the promoted suggestions to.
*/
void pickPromoted(SuggestionCursor shortcuts,
- ArrayList<SuggestionCursor> suggestions, int maxPromoted,
+ ArrayList<CorpusResult> suggestions, int maxPromoted,
ListSuggestionCursor promoted);
}
diff --git a/src/com/android/quicksearchbox/QsbApplication.java b/src/com/android/quicksearchbox/QsbApplication.java
index 444cb96..0c2ff9e 100644
--- a/src/com/android/quicksearchbox/QsbApplication.java
+++ b/src/com/android/quicksearchbox/QsbApplication.java
@@ -29,17 +29,15 @@
public class QsbApplication extends Application {
- private static final String TAG ="QSB.QsbApplication";
-
private Handler mUiThreadHandler;
private Config mConfig;
- private Sources mSources;
+ private SearchableCorpora mCorpora;
+ private CorpusRanker mCorpusRanker;
private ShortcutRepository mShortcutRepository;
private ShortcutRefresher mShortcutRefresher;
private SourceTaskExecutor mSourceTaskExecutor;
private SuggestionsProvider mGlobalSuggestionsProvider;
private SuggestionViewFactory mSuggestionViewFactory;
- private SourceFactory mSourceFactory;
private Logger mLogger;
@Override
@@ -48,14 +46,22 @@
super.onTerminate();
}
+ protected void checkThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new IllegalStateException("Accessed Application object from thread "
+ + Thread.currentThread().getName());
+ }
+ }
+
protected void close() {
+ checkThread();
if (mConfig != null) {
mConfig.close();
mConfig = null;
}
- if (mSources != null) {
- mSources.close();
- mSources = null;
+ if (mCorpora != null) {
+ mCorpora.close();
+ mCorpora = null;
}
if (mShortcutRepository != null) {
mShortcutRepository.close();
@@ -71,18 +77,18 @@
}
}
- public Handler getUiThreadHandler() {
+ public synchronized Handler getMainThreadHandler() {
if (mUiThreadHandler == null) {
- mUiThreadHandler = createUiThreadHandler();
+ mUiThreadHandler = new Handler(Looper.getMainLooper());
}
return mUiThreadHandler;
}
- protected Handler createUiThreadHandler() {
- return new Handler(Looper.myLooper());
- }
-
- public Config getConfig() {
+ /**
+ * Gets the QSB configuration object.
+ * May be called from any thread.
+ */
+ public synchronized Config getConfig() {
if (mConfig == null) {
mConfig = createConfig();
}
@@ -93,27 +99,63 @@
return new Config(this);
}
- public SourceLookup getSources() {
- if (mSources == null) {
- mSources = createSources();
+ /**
+ * Gets the corpora.
+ * May only be called from the main thread.
+ */
+ public Corpora getCorpora() {
+ checkThread();
+ if (mCorpora == null) {
+ mCorpora = createCorpora();
}
- return mSources;
+ return mCorpora;
}
- protected Sources createSources() {
- Sources sources = new Sources(this, getConfig(), getSourceFactory());
- sources.load();
- return sources;
+ protected SearchableCorpora createCorpora() {
+ SearchableCorpora corpora = new SearchableCorpora(this, getConfig(), getMainThreadHandler());
+ corpora.load();
+ return corpora;
}
+ /**
+ * Gets the corpus ranker.
+ * May only be called from the main thread.
+ */
+ public CorpusRanker getCorpusRanker() {
+ checkThread();
+ if (mCorpusRanker == null) {
+ mCorpusRanker = createCorpusRanker();
+ }
+ return mCorpusRanker;
+ }
+
+ protected CorpusRanker createCorpusRanker() {
+ return new DefaultCorpusRanker(getShortcutRepository());
+ }
+
+ /**
+ * Gets the shortcut repository.
+ * May only be called from the main thread.
+ */
public ShortcutRepository getShortcutRepository() {
+ checkThread();
if (mShortcutRepository == null) {
mShortcutRepository = createShortcutRepository();
}
return mShortcutRepository;
}
+ protected ShortcutRepository createShortcutRepository() {
+ return ShortcutRepositoryImplLog.create(this, getConfig(), getCorpora(),
+ getShortcutRefresher(), getMainThreadHandler());
+ }
+
+ /**
+ * Gets the shortcut refresher.
+ * May only be called from the main thread.
+ */
public ShortcutRefresher getShortcutRefresher() {
+ checkThread();
if (mShortcutRefresher == null) {
mShortcutRefresher = createShortcutRefresher();
}
@@ -122,15 +164,15 @@
protected ShortcutRefresher createShortcutRefresher() {
// For now, ShortcutRefresher gets its own SourceTaskExecutor
- return new ShortcutRefresher(createSourceTaskExecutor(), getSources());
+ return new ShortcutRefresher(createSourceTaskExecutor());
}
- protected ShortcutRepository createShortcutRepository() {
- return ShortcutRepositoryImplLog.create(this, getConfig(), getSources(),
- getShortcutRefresher(), getUiThreadHandler());
- }
-
+ /**
+ * Gets the source task executor.
+ * May only be called from the main thread.
+ */
public SourceTaskExecutor getSourceTaskExecutor() {
+ checkThread();
if (mSourceTaskExecutor == null) {
mSourceTaskExecutor = createSourceTaskExecutor();
}
@@ -144,28 +186,33 @@
return new DelayingSourceTaskExecutor(config, queryThreadFactory);
}
-
- public SuggestionsProvider getSuggestionsProvider(Source source) {
- if (source == null) {
+ /**
+ * Gets the suggestion provider for a corpus.
+ * May only be called from the main thread.
+ */
+ public SuggestionsProvider getSuggestionsProvider(Corpus corpus) {
+ checkThread();
+ if (corpus == null) {
return getGlobalSuggestionsProvider();
}
// TODO: Cache this to avoid creating a new one for each key press
- return createSuggestionsProvider(source);
+ return createSuggestionsProvider(corpus);
}
- protected SuggestionsProvider createSuggestionsProvider(Source source) {
+ protected SuggestionsProvider createSuggestionsProvider(Corpus corpus) {
// TODO: We could use simpler promoter here
Promoter promoter = new ShortcutPromoter(new RoundRobinPromoter());
- SingleSourceSuggestionsProvider provider = new SingleSourceSuggestionsProvider(getConfig(),
- source,
+ SingleCorpusSuggestionsProvider provider = new SingleCorpusSuggestionsProvider(getConfig(),
+ corpus,
getSourceTaskExecutor(),
- getUiThreadHandler(),
+ getMainThreadHandler(),
promoter,
getShortcutRepository());
return provider;
}
- public SuggestionsProvider getGlobalSuggestionsProvider() {
+ protected SuggestionsProvider getGlobalSuggestionsProvider() {
+ checkThread();
if (mGlobalSuggestionsProvider == null) {
mGlobalSuggestionsProvider = createGlobalSuggestionsProvider();
}
@@ -173,18 +220,23 @@
}
protected SuggestionsProvider createGlobalSuggestionsProvider() {
- Handler uiThread = new Handler(Looper.myLooper());
Promoter promoter = new ShortcutPromoter(new RoundRobinPromoter());
GlobalSuggestionsProvider provider = new GlobalSuggestionsProvider(getConfig(),
- getSources(),
+ getCorpora(),
getSourceTaskExecutor(),
- uiThread,
+ getMainThreadHandler(),
promoter,
+ getCorpusRanker(),
getShortcutRepository());
return provider;
}
+ /**
+ * Gets the suggestion view factory.
+ * May only be called from the main thread.
+ */
public SuggestionViewFactory getSuggestionViewFactory() {
+ checkThread();
if (mSuggestionViewFactory == null) {
mSuggestionViewFactory = createSuggestionViewFactory();
}
@@ -195,6 +247,10 @@
return new SuggestionViewInflater(this);
}
+ /**
+ * Creates a suggestions adapter.
+ * May only be called from the main thread.
+ */
public SuggestionsAdapter createSuggestionsAdapter() {
Config config = getConfig();
SuggestionViewFactory viewFactory = getSuggestionViewFactory();
@@ -202,18 +258,12 @@
return adapter;
}
- public SourceFactory getSourceFactory() {
- if (mSourceFactory == null) {
- mSourceFactory = createSourceFactory();
- }
- return mSourceFactory;
- }
-
- protected SourceFactory createSourceFactory() {
- return new SearchableSourceFactory(this);
- }
-
+ /**
+ * Gets the event logger.
+ * May only be called from the main thread.
+ */
public Logger getLogger() {
+ checkThread();
if (mLogger == null) {
mLogger = createLogger();
}
diff --git a/src/com/android/quicksearchbox/SourceFactory.java b/src/com/android/quicksearchbox/ReverseComparator.java
similarity index 68%
copy from src/com/android/quicksearchbox/SourceFactory.java
copy to src/com/android/quicksearchbox/ReverseComparator.java
index e661498..4b611f7 100644
--- a/src/com/android/quicksearchbox/SourceFactory.java
+++ b/src/com/android/quicksearchbox/ReverseComparator.java
@@ -16,14 +16,12 @@
package com.android.quicksearchbox;
-import android.app.SearchableInfo;
+import java.util.Comparator;
-public interface SourceFactory {
+public class ReverseComparator<T extends Comparable<T>> implements Comparator<T> {
- // TODO: perhaps SearchManager.getSearchablesInGlobalSearch()
- // should return List<ComponentName>, so we could use ComponentName here?
- Source createSource(SearchableInfo searchable);
-
- Source createWebSearchSource();
+ public int compare(T o1, T o2) {
+ return o2.compareTo(o1);
+ }
}
diff --git a/src/com/android/quicksearchbox/RoundRobinPromoter.java b/src/com/android/quicksearchbox/RoundRobinPromoter.java
index 1ad4cf7..3728144 100644
--- a/src/com/android/quicksearchbox/RoundRobinPromoter.java
+++ b/src/com/android/quicksearchbox/RoundRobinPromoter.java
@@ -36,7 +36,7 @@
}
public void pickPromoted(SuggestionCursor shortcuts,
- ArrayList<SuggestionCursor> suggestions, int maxPromoted,
+ ArrayList<CorpusResult> suggestions, int maxPromoted,
ListSuggestionCursor promoted) {
if (DBG) Log.d(TAG, "pickPromoted(maxPromoted = " + maxPromoted + ")");
final int sourceCount = suggestions.size();
diff --git a/src/com/android/quicksearchbox/SearchActivity.java b/src/com/android/quicksearchbox/SearchActivity.java
index 315b659..c309851 100644
--- a/src/com/android/quicksearchbox/SearchActivity.java
+++ b/src/com/android/quicksearchbox/SearchActivity.java
@@ -17,7 +17,7 @@
package com.android.quicksearchbox;
import com.android.common.Search;
-import com.android.quicksearchbox.ui.SearchSourceSelector;
+import com.android.quicksearchbox.ui.CorpusIndicator;
import com.android.quicksearchbox.ui.SuggestionClickListener;
import com.android.quicksearchbox.ui.SuggestionSelectionListener;
import com.android.quicksearchbox.ui.SuggestionViewFactory;
@@ -27,11 +27,11 @@
import android.app.Activity;
import android.app.Dialog;
import android.app.SearchManager;
-import android.content.ComponentName;
import android.content.Intent;
import android.database.DataSetObserver;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.text.Editable;
@@ -57,15 +57,17 @@
private static final boolean DBG = true;
private static final String TAG = "QSB.SearchActivity";
- public static final String INTENT_ACTION_QSB_AND_SELECT_SEARCH_SOURCE
- = "com.android.quicksearchbox.action.QSB_AND_SELECT_SEARCH_SOURCE";
+ private static final String SCHEME_CORPUS = "qsb.corpus";
+
+ public static final String INTENT_ACTION_QSB_AND_SELECT_CORPUS
+ = "com.android.quicksearchbox.action.QSB_AND_SELECT_CORPUS";
// Keys for the saved instance state.
- private static final String INSTANCE_KEY_SOURCE = "source";
+ private static final String INSTANCE_KEY_CORPUS = "corpus";
private static final String INSTANCE_KEY_USER_QUERY = "query";
// Dialog IDs
- private static final int DIALOG_SOURCE_SELECTOR = 0;
+ private static final int CORPUS_SELECTION_DIALOG = 0;
// Timestamp for last onCreate()/onNewIntent() call, as returned by SystemClock.uptimeMillis().
private long mStartTime;
@@ -83,11 +85,11 @@
protected ImageButton mSearchGoButton;
protected ImageButton mVoiceSearchButton;
- protected SearchSourceSelector mSourceSelector;
+ protected CorpusIndicator mCorpusIndicator;
private Launcher mLauncher;
- private Source mSource;
+ private Corpus mCorpus;
private Bundle mAppSearchData;
private boolean mUpdateSuggestions;
private String mUserQuery;
@@ -114,18 +116,15 @@
mSearchGoButton = (ImageButton) findViewById(R.id.search_go_btn);
mVoiceSearchButton = (ImageButton) findViewById(R.id.search_voice_btn);
- mSourceSelector = new SearchSourceSelector(findViewById(R.id.search_source_selector));
+ mCorpusIndicator = new CorpusIndicator(findViewById(R.id.corpus_indicator));
mLauncher = new Launcher(this);
- // TODO: should this check for voice search in the current source?
- mVoiceSearchButton.setVisibility(
- mLauncher.isVoiceSearchAvailable() ? View.VISIBLE : View.GONE);
mQueryTextView.addTextChangedListener(new SearchTextWatcher());
mQueryTextView.setOnKeyListener(new QueryTextViewKeyListener());
mQueryTextView.setOnFocusChangeListener(new QueryTextViewFocusListener());
- mSourceSelector.setOnClickListener(new SourceSelectorClickListener());
+ mCorpusIndicator.setOnClickListener(new CorpusIndicatorClickListener());
mSearchGoButton.setOnClickListener(new SearchGoButtonClickListener());
@@ -134,7 +133,7 @@
ButtonsKeyListener buttonsKeyListener = new ButtonsKeyListener();
mSearchGoButton.setOnKeyListener(buttonsKeyListener);
mVoiceSearchButton.setOnKeyListener(buttonsKeyListener);
- mSourceSelector.setOnKeyListener(buttonsKeyListener);
+ mCorpusIndicator.setOnKeyListener(buttonsKeyListener);
mUpdateSuggestions = true;
@@ -164,9 +163,9 @@
protected void restoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState == null) return;
- ComponentName sourceName = savedInstanceState.getParcelable(INSTANCE_KEY_SOURCE);
+ String corpusName = savedInstanceState.getString(INSTANCE_KEY_CORPUS);
String query = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
- setSource(getSourceByComponentName(sourceName));
+ setCorpus(getCorpus(corpusName));
setUserQuery(query);
}
@@ -175,61 +174,79 @@
super.onSaveInstanceState(outState);
// We don't save appSearchData, since we always get the value
// from the intent and the user can't change it.
- outState.putParcelable(INSTANCE_KEY_SOURCE, getSourceName());
+
+ String corpusName = mCorpus == null ? null : mCorpus.getName();
+ outState.putString(INSTANCE_KEY_CORPUS, corpusName);
outState.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
}
private void setupFromIntent(Intent intent) {
if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")");
- ComponentName sourceName = SearchSourceSelector.getSource(intent);
+ Corpus corpus = getCorpusFromUri(intent.getData());
String query = intent.getStringExtra(SearchManager.QUERY);
Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA);
- Source source = getSourceByComponentName(sourceName);
- setSource(source);
+ setCorpus(corpus);
setUserQuery(query);
mSelectAll = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false);
setAppSearchData(appSearchData);
- if (INTENT_ACTION_QSB_AND_SELECT_SEARCH_SOURCE.equals(intent.getAction())) {
+ if (INTENT_ACTION_QSB_AND_SELECT_CORPUS.equals(intent.getAction())) {
showSourceSelectorDialog();
}
}
- private Source getSourceByComponentName(ComponentName sourceName) {
+ public static Uri getCorpusUri(Corpus corpus) {
+ if (corpus == null) return null;
+ return new Uri.Builder()
+ .scheme(SCHEME_CORPUS)
+ .authority(corpus.getName())
+ .build();
+ }
+
+ private Corpus getCorpusFromUri(Uri uri) {
+ if (uri == null) return null;
+ if (!SCHEME_CORPUS.equals(uri.getScheme())) return null;
+ String name = uri.getAuthority();
+ return getCorpus(name);
+ }
+
+ private Corpus getCorpus(String sourceName) {
if (sourceName == null) return null;
- Source source = getSources().getSourceByComponentName(sourceName);
- if (source == null) {
- Log.w(TAG, "Unknown source " + sourceName);
+ Corpus corpus = getCorpora().getCorpus(sourceName);
+ if (corpus == null) {
+ Log.w(TAG, "Unknown corpus " + sourceName);
return null;
}
- return source;
+ return corpus;
}
- private void setSource(Source source) {
- if (DBG) Log.d(TAG, "setSource(" + source + ")");
- mSource = source;
+ private void setCorpus(Corpus corpus) {
+ if (DBG) Log.d(TAG, "setCorpus(" + corpus + ")");
+ mCorpus = corpus;
Drawable sourceIcon;
- if (source == null) {
+ if (corpus == null) {
sourceIcon = getSuggestionViewFactory().getGlobalSearchIcon();
} else {
- sourceIcon = source.getSourceIcon();
+ sourceIcon = corpus.getCorpusIcon();
}
- ComponentName sourceName = getSourceName();
- mSuggestionsAdapter.setSource(sourceName);
- mSourceSelector.setSourceIcon(sourceIcon);
- }
+ mSuggestionsAdapter.setCorpus(corpus);
+ mCorpusIndicator.setSourceIcon(sourceIcon);
- private ComponentName getSourceName() {
- return mSource == null ? null : mSource.getComponentName();
+ boolean enableVoiceSearch = Launcher.shouldShowVoiceSearch(this, mCorpus);
+ mVoiceSearchButton.setVisibility(enableVoiceSearch ? View.VISIBLE : View.GONE);
}
private QsbApplication getQsbApplication() {
return (QsbApplication) getApplication();
}
- private SourceLookup getSources() {
- return getQsbApplication().getSources();
+ private Corpora getCorpora() {
+ return getQsbApplication().getCorpora();
+ }
+
+ private CorpusRanker getCorpusRanker() {
+ return getQsbApplication().getCorpusRanker();
}
private ShortcutRepository getShortcutRepository() {
@@ -237,7 +254,7 @@
}
private SuggestionsProvider getSuggestionsProvider() {
- return getQsbApplication().getSuggestionsProvider(mSource);
+ return getQsbApplication().getSuggestionsProvider(mCorpus);
}
private SuggestionViewFactory getSuggestionViewFactory() {
@@ -280,8 +297,8 @@
// SystemClock.uptimeMillis() does not advance during deep sleep.
int latency = (int) (SystemClock.uptimeMillis() - mStartTime);
String source = getIntent().getStringExtra(Search.SOURCE);
- getLogger().logStart(latency, source, mSource,
- getSuggestionsProvider().getOrderedSources());
+ getLogger().logStart(latency, source, mCorpus,
+ getSuggestionsProvider().getOrderedCorpora());
}
}
@@ -357,15 +374,15 @@
}
protected void showSourceSelectorDialog() {
- showDialog(DIALOG_SOURCE_SELECTOR);
+ showDialog(CORPUS_SELECTION_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id, Bundle args) {
switch (id) {
- case DIALOG_SOURCE_SELECTOR:
- return createSourceSelectorDialog();
+ case CORPUS_SELECTION_DIALOG:
+ return createCorpusSelectionDialog();
default:
throw new IllegalArgumentException("Unknown dialog: " + id);
}
@@ -374,22 +391,22 @@
@Override
protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
switch (id) {
- case DIALOG_SOURCE_SELECTOR:
- prepareSourceSelectorDialog((SelectSearchSourceDialog) dialog);
+ case CORPUS_SELECTION_DIALOG:
+ prepareCorpusSelectionDialog((CorpusSelectionDialog) dialog);
break;
default:
throw new IllegalArgumentException("Unknown dialog: " + id);
}
}
- protected SelectSearchSourceDialog createSourceSelectorDialog() {
- SelectSearchSourceDialog dialog = new SelectSearchSourceDialog(this);
+ protected CorpusSelectionDialog createCorpusSelectionDialog() {
+ CorpusSelectionDialog dialog = new CorpusSelectionDialog(this);
dialog.setOwnerActivity(this);
return dialog;
}
- protected void prepareSourceSelectorDialog(SelectSearchSourceDialog dialog) {
- dialog.setSource(getSourceName());
+ protected void prepareCorpusSelectionDialog(CorpusSelectionDialog dialog) {
+ dialog.setCorpus(mCorpus);
dialog.setQuery(getQuery());
dialog.setAppData(mAppSearchData);
}
@@ -398,70 +415,46 @@
String query = getQuery();
if (DBG) Log.d(TAG, "Search clicked, query=" + query);
mTookAction = true;
- getLogger().logSearch(mSource, method, query.length());
- mLauncher.startSearch(mSource, query);
+ getLogger().logSearch(mCorpus, method, query.length());
+ mLauncher.startSearch(mCorpus, query);
}
protected void onVoiceSearchClicked() {
if (DBG) Log.d(TAG, "Voice Search clicked");
mTookAction = true;
- getLogger().logVoiceSearch(mSource);
+ getLogger().logVoiceSearch(mCorpus);
// TODO: should this start voice search in the current source?
- mLauncher.startVoiceSearch();
+ mLauncher.startVoiceSearch(mCorpus);
}
- protected boolean launchSuggestion(SuggestionPosition suggestion) {
- return launchSuggestion(suggestion, KeyEvent.KEYCODE_UNKNOWN, null);
+ protected SuggestionCursor getSuggestions() {
+ return mSuggestionsAdapter.getCurrentSuggestions();
}
- protected boolean launchSuggestion(SuggestionPosition suggestion,
- int actionKey, String actionMsg) {
- if (DBG) Log.d(TAG, "Launching suggestion " + suggestion);
+ protected boolean launchSuggestion(int position) {
+ if (DBG) Log.d(TAG, "Launching suggestion " + position);
mTookAction = true;
- SuggestionCursor suggestions = mSuggestionsAdapter.getCurrentSuggestions();
+ SuggestionCursor suggestions = getSuggestions();
// TODO: This should be just the queried sources, but currently
// all sources are queried
- ArrayList<Source> sources = getSuggestionsProvider().getOrderedSources();
- getLogger().logSuggestionClick(suggestion.getPosition(), suggestions, sources);
+ ArrayList<Corpus> corpora = getCorpusRanker().rankCorpora(getCorpora().getEnabledCorpora());
+ getLogger().logSuggestionClick(position, suggestions, corpora);
- mLauncher.launchSuggestion(suggestion, actionKey, actionMsg);
- getShortcutRepository().reportClick(suggestion);
+ mLauncher.launchSuggestion(suggestions, position);
+ getShortcutRepository().reportClick(suggestions, position);
return true;
}
- protected boolean onSuggestionLongClicked(SuggestionPosition suggestion) {
- SuggestionCursor sourceResult = suggestion.getSuggestion();
- if (DBG) Log.d(TAG, "Long clicked on suggestion " + sourceResult.getSuggestionText1());
+ protected boolean onSuggestionLongClicked(int position) {
+ if (DBG) Log.d(TAG, "Long clicked on suggestion " + position);
return false;
}
- protected void onSuggestionSelected(SuggestionPosition suggestion) {
- if (suggestion == null) {
- // This happens when a suggestion has been selected with the
- // dpad / trackball and then a different UI element is touched.
- // Do nothing, since we want to keep the query of the selection
- // in the search box.
- return;
- }
- SuggestionCursor sourceResult = suggestion.getSuggestion();
- String displayQuery = sourceResult.getSuggestionDisplayQuery();
- if (DBG) {
- Log.d(TAG, "Selected suggestion " + sourceResult.getSuggestionText1()
- + ",displayQuery="+ displayQuery);
- }
- if (TextUtils.isEmpty(displayQuery)) {
- restoreUserQuery();
- } else {
- setQuery(displayQuery, false);
- }
- }
-
- protected boolean onSuggestionKeyDown(SuggestionPosition suggestion,
- int keyCode, KeyEvent event) {
+ protected boolean onSuggestionKeyDown(int position, int keyCode, KeyEvent event) {
// Treat enter or search as a click
if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
- return launchSuggestion(suggestion);
+ return launchSuggestion(position);
}
if (keyCode == KeyEvent.KEYCODE_DPAD_UP
@@ -484,12 +477,6 @@
return true;
}
- // Handle source-specified action keys
- String actionMsg = suggestion.getSuggestion().getActionKeyMsg(keyCode);
- if (actionMsg != null) {
- return launchSuggestion(suggestion, keyCode, actionMsg);
- }
-
return false;
}
@@ -502,10 +489,6 @@
return mSuggestionsView.getSelectedPosition();
}
- protected SuggestionPosition getSelectedSuggestion() {
- return mSuggestionsView.getSelectedSuggestion();
- }
-
/**
* Hides the input method.
*/
@@ -654,11 +637,9 @@
private class SuggestionsViewKeyListener implements View.OnKeyListener {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
- SuggestionPosition suggestion = getSelectedSuggestion();
- if (suggestion != null) {
- if (onSuggestionKeyDown(suggestion, keyCode, event)) {
+ int position = getSelectedPosition();
+ if (onSuggestionKeyDown(position, keyCode, event)) {
return true;
- }
}
}
return forwardKeyToQueryTextView(keyCode, event);
@@ -672,18 +653,32 @@
}
private class ClickHandler implements SuggestionClickListener {
- public void onSuggestionClicked(SuggestionPosition suggestion) {
- launchSuggestion(suggestion);
+ public void onSuggestionClicked(int position) {
+ launchSuggestion(position);
}
- public boolean onSuggestionLongClicked(SuggestionPosition suggestion) {
- return SearchActivity.this.onSuggestionLongClicked(suggestion);
+ public boolean onSuggestionLongClicked(int position) {
+ return SearchActivity.this.onSuggestionLongClicked(position);
}
}
private class SelectionHandler implements SuggestionSelectionListener {
- public void onSelectionChanged(SuggestionPosition suggestion) {
- onSuggestionSelected(suggestion);
+ public void onSuggestionSelected(int position) {
+ SuggestionCursor suggestions = getSuggestions();
+ suggestions.moveTo(position);
+ String displayQuery = suggestions.getSuggestionDisplayQuery();
+ if (TextUtils.isEmpty(displayQuery)) {
+ restoreUserQuery();
+ } else {
+ setQuery(displayQuery, false);
+ }
+ }
+
+ public void onNothingSelected() {
+ // This happens when a suggestion has been selected with the
+ // dpad / trackball and then a different UI element is touched.
+ // Do nothing, since we want to keep the query of the selection
+ // in the search box.
}
}
@@ -699,7 +694,7 @@
/**
* Listens for clicks on the search button.
*/
- private class SourceSelectorClickListener implements View.OnClickListener {
+ private class CorpusIndicatorClickListener implements View.OnClickListener {
public void onClick(View view) {
showSourceSelectorDialog();
}
diff --git a/src/com/android/quicksearchbox/SearchSettings.java b/src/com/android/quicksearchbox/SearchSettings.java
index f9f7978..ffec0a4 100644
--- a/src/com/android/quicksearchbox/SearchSettings.java
+++ b/src/com/android/quicksearchbox/SearchSettings.java
@@ -19,10 +19,13 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.SearchManager;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
@@ -31,6 +34,7 @@
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
+import android.provider.Settings;
import android.util.Log;
import java.util.List;
@@ -45,13 +49,16 @@
private static final boolean DBG = false;
private static final String TAG = "SearchSettings";
+ // Name of the preferences file used to store search preference
+ public static final String PREFERENCES_NAME = "SearchSettings";
+
// 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";
- private static final String SEARCH_SOURCES_PREF = "search_sources";
+ private static final String SEARCH_CORPORA_PREF = "search_corpora";
- private SourceLookup mSources;
- private ShortcutRepository mShortcuts;
+ // Preifx of per-corpus enable preference
+ private static final String CORPUS_ENABLED_PREF_PREFIX = "enable_corpus_";
// References to the top-level preference objects
private Preference mClearShortcutsPreference;
@@ -65,9 +72,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mSources = getSources();
- mShortcuts = getQSBApplication().getShortcutRepository();
- getPreferenceManager().setSharedPreferencesName(Sources.PREFERENCES_NAME);
+ getPreferenceManager().setSharedPreferencesName(PREFERENCES_NAME);
addPreferencesFromResource(R.xml.preferences);
@@ -76,7 +81,7 @@
mSearchEngineSettingsPreference = (PreferenceScreen) preferenceScreen.findPreference(
SEARCH_ENGINE_SETTINGS_PREF);
mSourcePreferences = (PreferenceGroup) getPreferenceScreen().findPreference(
- SEARCH_SOURCES_PREF);
+ SEARCH_CORPORA_PREF);
mClearShortcutsPreference.setOnPreferenceClickListener(this);
@@ -85,22 +90,35 @@
populateSearchEnginePreference();
}
- @Override
- protected void onDestroy() {
- mShortcuts.close();
- super.onDestroy();
+ public static boolean areWebSuggestionsEnabled(Context context) {
+ return (Settings.System.getInt(context.getContentResolver(),
+ Settings.System.SHOW_WEB_SUGGESTIONS,
+ 1 /* default on until user actually changes it */) == 1);
}
- private QsbApplication getQSBApplication() {
+ /**
+ * Gets the preference key of the preference for whether the given corpus
+ * is enabled. The preference is stored in the {@link #PREFERENCES_NAME}
+ * preferences file.
+ */
+ public static String getCorpusEnabledPreference(Corpus corpus) {
+ return CORPUS_ENABLED_PREF_PREFIX + corpus.getName();
+ }
+
+ public static SharedPreferences getSearchPreferences(Context context) {
+ return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
+ }
+
+ private QsbApplication getQsbApplication() {
return (QsbApplication) getApplication();
}
- private Config getConfig() {
- return getQSBApplication().getConfig();
+ private Corpora getCorpora() {
+ return getQsbApplication().getCorpora();
}
- private SourceLookup getSources() {
- return getQSBApplication().getSources();
+ private ShortcutRepository getShortcuts() {
+ return getQsbApplication().getShortcutRepository();
}
/**
@@ -108,7 +126,7 @@
* on whether there is any search history.
*/
private void updateClearShortcutsPreference() {
- boolean hasHistory = mShortcuts.hasHistory();
+ boolean hasHistory = getShortcuts().hasHistory();
if (DBG) Log.d(TAG, "hasHistory()=" + hasHistory);
mClearShortcutsPreference.setEnabled(hasHistory);
}
@@ -144,10 +162,10 @@
* Fills the suggestion source list.
*/
private void populateSourcePreference() {
- for (Source source : mSources.getSources()) {
- Preference pref = createSourcePreference(source);
+ for (Corpus corpus : getCorpora().getAllCorpora()) {
+ Preference pref = createCorpusPreference(corpus);
if (pref != null) {
- if (DBG) Log.d(TAG, "Adding search source: " + source);
+ if (DBG) Log.d(TAG, "Adding corpus: " + corpus);
mSourcePreferences.addPreference(pref);
}
}
@@ -156,14 +174,14 @@
/**
* Adds a suggestion source to the list of suggestion source checkbox preferences.
*/
- private Preference createSourcePreference(Source source) {
+ private Preference createCorpusPreference(Corpus corpus) {
CheckBoxPreference sourcePref = new CheckBoxPreference(this);
- sourcePref.setKey(Sources.getSourceEnabledPreference(source));
- sourcePref.setDefaultValue(mSources.isTrustedSource(source));
+ sourcePref.setKey(getCorpusEnabledPreference(corpus));
+ sourcePref.setDefaultValue(getCorpora().isCorpusDefaultEnabled(corpus));
sourcePref.setOnPreferenceChangeListener(this);
- CharSequence label = source.getLabel();
+ CharSequence label = corpus.getLabel();
sourcePref.setTitle(label);
- CharSequence description = source.getSettingsDescription();
+ CharSequence description = corpus.getSettingsDescription();
sourcePref.setSummaryOn(description);
sourcePref.setSummaryOff(description);
return sourcePref;
@@ -181,7 +199,7 @@
}
@Override
- protected Dialog onCreateDialog(int id) {
+ protected Dialog onCreateDialog(int id, Bundle args) {
switch (id) {
case CLEAR_SHORTCUTS_CONFIRM_DIALOG:
return new AlertDialog.Builder(this)
@@ -190,7 +208,7 @@
.setPositiveButton(R.string.agree, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
if (DBG) Log.d(TAG, "Clearing history...");
- mShortcuts.clearHistory();
+ getShortcuts().clearHistory();
updateClearShortcutsPreference();
}
})
@@ -200,9 +218,9 @@
return null;
}
}
-
+
/**
- * Informs our listeners (SuggestionSources objects) about the updated settings data.
+ * Informs our listeners about the updated settings data.
*/
private void broadcastSettingsChanged() {
// We use a message broadcast since the listeners could be in multiple processes.
@@ -216,4 +234,15 @@
return true;
}
+ public static void registerShowWebSuggestionsSettingObserver(
+ Context context, ContentObserver observer) {
+ context.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SHOW_WEB_SUGGESTIONS),
+ false, observer);
+ }
+
+ public static void unregisterShowWebSuggestionsSettingObserver(
+ Context context, ContentObserver observer) {
+ context.getContentResolver().unregisterContentObserver(observer);
+ }
}
diff --git a/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java b/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
index 8a67ff5..3d38bbd 100644
--- a/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
+++ b/src/com/android/quicksearchbox/SearchWidgetConfigActivity.java
@@ -16,12 +16,11 @@
package com.android.quicksearchbox;
-import com.android.quicksearchbox.ui.SourcesAdapter;
+import com.android.quicksearchbox.ui.CorporaAdapter;
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;
@@ -37,11 +36,11 @@
static final String TAG = "QSB.SearchWidgetConfigActivity";
private static final String PREFS_NAME = "SearchWidgetConfig";
- private static final String WIDGET_SOURCE_PREF_PREFIX = "widget_source_";
+ private static final String WIDGET_CORPUS_PREF_PREFIX = "widget_corpus_";
private int mAppWidgetId;
- private GridView mSourceList;
+ private GridView mCorpusList;
@Override
public void onCreate(Bundle icicle) {
@@ -49,13 +48,13 @@
setContentView(R.layout.widget_config);
- mSourceList = (GridView) findViewById(R.id.widget_source_list);
- mSourceList.setOnItemClickListener(new SourceClickListener());
+ mCorpusList = (GridView) findViewById(R.id.widget_corpus_list);
+ mCorpusList.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()));
+ mCorpusList.setFocusable(true);
+ mCorpusList.setAdapter(new CorporaAdapter(getViewFactory(), getCorpora(),
+ getCorpusRanker()));
Intent intent = getIntent();
mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
@@ -65,9 +64,9 @@
}
}
- protected void selectSource(Source source) {
- writeWidgetSourcePref(mAppWidgetId, source);
- updateWidget(source);
+ protected void selectCorpus(Corpus corpus) {
+ writeWidgetCorpusPref(mAppWidgetId, corpus);
+ updateWidget(corpus);
Intent result = new Intent();
result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
@@ -75,39 +74,42 @@
finish();
}
- private void updateWidget(Source source) {
+ private void updateWidget(Corpus corpus) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
SearchWidgetProvider.setupSearchWidget(this, appWidgetManager,
- mAppWidgetId, source);
+ mAppWidgetId, corpus);
}
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 static String getCorpusPrefKey(int appWidgetId) {
+ return WIDGET_CORPUS_PREF_PREFIX + appWidgetId;
}
- private void writeWidgetSourcePref(int appWidgetId, Source source) {
- String sourceName = source == null ? null : source.getFlattenedComponentName();
+ private void writeWidgetCorpusPref(int appWidgetId, Corpus corpus) {
+ String corpusName = corpus == null ? null : corpus.getName();
SharedPreferences.Editor prefs = getWidgetPreferences(this).edit();
- prefs.putString(getSourcePrefKey(appWidgetId), sourceName);
+ prefs.putString(getCorpusPrefKey(appWidgetId), corpusName);
prefs.commit();
}
- public static ComponentName readWidgetSourcePref(Context context, int appWidgetId) {
+ public static String readWidgetCorpusPref(Context context, int appWidgetId) {
SharedPreferences prefs = getWidgetPreferences(context);
- String sourceName = prefs.getString(getSourcePrefKey(appWidgetId), null);
- return sourceName == null ? null : ComponentName.unflattenFromString(sourceName);
+ return prefs.getString(getCorpusPrefKey(appWidgetId), null);
}
private QsbApplication getQsbApplication() {
return (QsbApplication) getApplication();
}
- private SuggestionsProvider getGlobalSuggestionsProvider() {
- return getQsbApplication().getGlobalSuggestionsProvider();
+ private Corpora getCorpora() {
+ return getQsbApplication().getCorpora();
+ }
+
+ private CorpusRanker getCorpusRanker() {
+ return getQsbApplication().getCorpusRanker();
}
private SuggestionViewFactory getViewFactory() {
@@ -116,11 +118,8 @@
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);
+ Corpus corpus = (Corpus) parent.getItemAtPosition(position);
+ selectCorpus(corpus);
}
}
}
-
-
-
diff --git a/src/com/android/quicksearchbox/SearchWidgetProvider.java b/src/com/android/quicksearchbox/SearchWidgetProvider.java
index 7cf692d..54813a6 100644
--- a/src/com/android/quicksearchbox/SearchWidgetProvider.java
+++ b/src/com/android/quicksearchbox/SearchWidgetProvider.java
@@ -16,14 +16,13 @@
package com.android.quicksearchbox;
-import com.android.quicksearchbox.ui.SearchSourceSelector;
+import com.android.quicksearchbox.ui.CorpusIndicator;
import com.android.quicksearchbox.ui.SuggestionViewFactory;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -57,24 +56,21 @@
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);
+ String corpusName = SearchWidgetConfigActivity.readWidgetCorpusPref(context, appWidgetId);
+ Corpus corpus = getCorpora(context).getCorpus(corpusName);
+ setupSearchWidget(context, appWidgetManager, appWidgetId, corpus);
}
public static void setupSearchWidget(Context context, AppWidgetManager appWidgetManager,
- int appWidgetId, Source source) {
+ int appWidgetId, Corpus corpus) {
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, source);
+ // Corpus indicator
+ bindCorpusIndicator(context, views, widgetAppData, corpus);
// Text field
Intent qsbIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
@@ -82,7 +78,7 @@
| Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
qsbIntent.putExtra(SearchManager.APP_DATA, widgetAppData);
- SearchSourceSelector.setSource(qsbIntent, sourceName);
+ qsbIntent.setData(SearchActivity.getCorpusUri(corpus));
setOnClickIntent(context, views, R.id.search_widget_text, qsbIntent);
// Voice search button. Only shown if voice search is available.
@@ -106,19 +102,18 @@
appWidgetManager.updateAppWidget(appWidgetId, views);
}
- private static void bindSourceSelector(Context context, RemoteViews views,
- Bundle widgetAppData, Source source) {
- Uri sourceIconUri = getSourceIconUri(context, source);
- views.setImageViewUri(SearchSourceSelector.ICON_VIEW_ID, sourceIconUri);
- ComponentName sourceName = source == null ? null : source.getComponentName();
+ private static void bindCorpusIndicator(Context context, RemoteViews views,
+ Bundle widgetAppData, Corpus corpus) {
+ Uri sourceIconUri = getCorpusIconUri(context, corpus);
+ views.setImageViewUri(CorpusIndicator.ICON_VIEW_ID, sourceIconUri);
- Intent intent = new Intent(SearchActivity.INTENT_ACTION_QSB_AND_SELECT_SEARCH_SOURCE);
+ Intent intent = new Intent(SearchActivity.INTENT_ACTION_QSB_AND_SELECT_CORPUS);
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);
- SearchSourceSelector.setSource(intent, sourceName);
- setOnClickIntent(context, views, SearchSourceSelector.ICON_VIEW_ID, intent);
+ intent.setData(SearchActivity.getCorpusUri(corpus));
+ setOnClickIntent(context, views, CorpusIndicator.ICON_VIEW_ID, intent);
}
private static void setOnClickIntent(Context context, RemoteViews views,
@@ -127,19 +122,19 @@
views.setOnClickPendingIntent(viewId, pendingIntent);
}
- private static Uri getSourceIconUri(Context context, Source source) {
- if (source == null) {
+ private static Uri getCorpusIconUri(Context context, Corpus corpus) {
+ if (corpus == null) {
return getSuggestionViewFactory(context).getGlobalSearchIconUri();
}
- return source.getSourceIconUri();
+ return corpus.getCorpusIconUri();
}
private static QsbApplication getQsbApplication(Context context) {
return (QsbApplication) context.getApplicationContext();
}
- private static SourceLookup getSources(Context context) {
- return getQsbApplication(context).getSources();
+ private static Corpora getCorpora(Context context) {
+ return getQsbApplication(context).getCorpora();
}
private static SuggestionViewFactory getSuggestionViewFactory(Context context) {
diff --git a/src/com/android/quicksearchbox/SearchableCorpora.java b/src/com/android/quicksearchbox/SearchableCorpora.java
new file mode 100644
index 0000000..d3405e3
--- /dev/null
+++ b/src/com/android/quicksearchbox/SearchableCorpora.java
@@ -0,0 +1,225 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Maintains the list of all suggestion sources.
+ */
+public class SearchableCorpora implements Corpora {
+
+ // set to true to enable the more verbose debug logging for this file
+ private static final boolean DBG = true;
+ private static final String TAG = "QSB.DefaultCorpora";
+
+ private final Context mContext;
+ private final Config mConfig;
+ private final Handler mUiThread;
+ private final SharedPreferences mPreferences;
+
+ private boolean mLoaded = false;
+
+ private SearchableSources mSources;
+ // Maps corpus names to corpora
+ private HashMap<String,Corpus> mCorporaByName;
+ // Maps sources to the corpus that contains them
+ private HashMap<Source,Corpus> mCorporaBySource;
+ // Enabled corpora
+ private List<Corpus> mEnabledCorpora;
+ private Corpus mWebCorpus;
+
+ private boolean mShowWebSuggestions;
+
+ // Updates the inclusion of the web search provider.
+ private ShowWebSuggestionsSettingObserver mShowWebSuggestionsSettingObserver;
+
+ /**
+ *
+ * @param context Used for looking up source information etc.
+ */
+ public SearchableCorpora(Context context, Config config, Handler uiThread) {
+ mContext = context;
+ mConfig = config;
+ mUiThread = uiThread;
+ mPreferences = SearchSettings.getSearchPreferences(context);
+
+ mSources = new SearchableSources(context, uiThread);
+ }
+
+ 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);
+ }
+
+ public Corpus getCorpusForSource(Source source) {
+ checkLoaded();
+ return mCorporaBySource.get(source);
+ }
+
+ public Source getSource(ComponentName name) {
+ checkLoaded();
+ 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.");
+ }
+
+ // Listen for web suggestion setting changes
+ mShowWebSuggestionsSettingObserver =
+ new ShowWebSuggestionsSettingObserver(mUiThread);
+ SearchSettings.registerShowWebSuggestionsSettingObserver(mContext,
+ mShowWebSuggestionsSettingObserver);
+ updateWebSuggestionsSetting();
+
+ 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();
+
+ SearchSettings.unregisterShowWebSuggestionsSettingObserver(mContext,
+ mShowWebSuggestionsSettingObserver);
+
+ mSources.close();
+ mSources = null;
+ mLoaded = false;
+ }
+
+ private void updateCorpora() {
+ mCorporaByName = new HashMap<String,Corpus>();
+ mCorporaBySource = new HashMap<Source,Corpus>();
+ mEnabledCorpora = new ArrayList<Corpus>();
+
+ Source webSource = mSources.getWebSearchSource();
+ Source browserSource = mSources.getSource(getBrowserSearchComponent());
+ mWebCorpus = new WebCorpus(mContext, webSource, browserSource);
+ addCorpus(mWebCorpus);
+ mCorporaBySource.put(webSource, mWebCorpus);
+ mCorporaBySource.put(browserSource, mWebCorpus);
+
+ // Create corpora for all unclaimed sources
+ for (Source source : mSources.getSources()) {
+ if (!mCorporaBySource.containsKey(source)) {
+ Corpus corpus = new SingleSourceCorpus(source);
+ addCorpus(corpus);
+ mCorporaBySource.put(source, corpus);
+ }
+ }
+
+ if (DBG) Log.d(TAG, "Updated corpora: " + mCorporaBySource.values());
+
+ mEnabledCorpora = Collections.unmodifiableList(mEnabledCorpora);
+ }
+
+ private void addCorpus(Corpus corpus) {
+ mCorporaByName.put(corpus.getName(), corpus);
+ if (isCorpusEnabled(corpus)) {
+ mEnabledCorpora.add(corpus);
+ }
+ }
+
+ private ComponentName getBrowserSearchComponent() {
+ String name = mContext.getString(R.string.browser_search_component);
+ return TextUtils.isEmpty(name) ? null : ComponentName.unflattenFromString(name);
+ }
+
+ public boolean isCorpusEnabled(Corpus corpus) {
+ if (corpus == null) return false;
+ boolean defaultEnabled = isCorpusDefaultEnabled(corpus);
+ String sourceEnabledPref = SearchSettings.getCorpusEnabledPreference(corpus);
+ return mPreferences.getBoolean(sourceEnabledPref, defaultEnabled);
+ }
+
+ public boolean isCorpusDefaultEnabled(Corpus corpus) {
+ String name = corpus.getName();
+ return mConfig.isCorpusEnabledByDefault(name);
+ }
+
+ public boolean shouldShowWebSuggestions() {
+ return mShowWebSuggestions;
+ }
+
+ private void updateWebSuggestionsSetting() {
+ mShowWebSuggestions = SearchSettings.areWebSuggestionsEnabled(mContext);
+ }
+
+ /**
+ * ContentObserver which updates the list of enabled sources to include or exclude
+ * the web search provider depending on the state of the
+ * {@link Settings.System#SHOW_WEB_SUGGESTIONS} setting.
+ */
+ private class ShowWebSuggestionsSettingObserver extends ContentObserver {
+ public ShowWebSuggestionsSettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ updateWebSuggestionsSetting();
+ }
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/SearchableSource.java b/src/com/android/quicksearchbox/SearchableSource.java
index a9bcf86..27bfaf9 100644
--- a/src/com/android/quicksearchbox/SearchableSource.java
+++ b/src/com/android/quicksearchbox/SearchableSource.java
@@ -16,17 +16,21 @@
package com.android.quicksearchbox;
+import android.app.PendingIntent;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
+import android.speech.RecognizerIntent;
import android.util.Log;
import java.util.Arrays;
@@ -40,6 +44,10 @@
private static final boolean DBG = true;
private static final String TAG = "QSB.SearchableSource";
+ // TODO: This should be exposed or moved to android-common, see http://b/issue?id=2440614
+ // The extra key used in an intent to the speech recognizer for in-app voice search.
+ private static final String EXTRA_CALLING_PACKAGE = "calling_package";
+
private final Context mContext;
private final SearchableInfo mSearchable;
@@ -64,6 +72,14 @@
mIconLoader = createIconLoader(context, searchable.getSuggestPackage());
}
+ protected Context getContext() {
+ return mContext;
+ }
+
+ protected SearchableInfo getSearchableInfo() {
+ return mSearchable;
+ }
+
private IconLoader createIconLoader(Context context, String providerPackage) {
if (providerPackage == null) return null;
try {
@@ -107,12 +123,7 @@
}
public CharSequence getSettingsDescription() {
- int res = mSearchable.getSettingsDescriptionId();
- if (res == 0) {
- return null;
- }
- return mContext.getPackageManager().getText(mActivityInfo.packageName, res,
- mActivityInfo.applicationInfo);
+ return getText(mSearchable.getSettingsDescriptionId());
}
public Drawable getSourceIcon() {
@@ -144,14 +155,104 @@
return (icon != 0) ? icon : android.R.drawable.sym_def_app_icon;
}
- public SuggestionCursor getSuggestions(String query, int queryLimit) {
+ public boolean voiceSearchEnabled() {
+ return mSearchable.getVoiceSearchEnabled();
+ }
+
+ // TODO: not all apps handle ACTION_SEARCH properly, e.g. ApplicationsProvider.
+ // Maybe we should add a flag to searchable, so that QSB can hide the search button?
+ public Intent createSearchIntent(String query, Bundle appData) {
+ Intent intent = new Intent(Intent.ACTION_SEARCH);
+ intent.setComponent(getComponentName());
+ 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);
+ }
+ return intent;
+ }
+
+ public Intent createVoiceSearchIntent(Bundle appData) {
+ if (mSearchable.getVoiceSearchLaunchWebSearch()) {
+ return WebCorpus.createVoiceWebSearchIntent(appData);
+ } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
+ return createVoiceAppSearchIntent(appData);
+ }
+ return null;
+ }
+
+ /**
+ * Create and return an Intent that can launch the voice search activity, perform a specific
+ * voice transcription, and forward the results to the searchable activity.
+ *
+ * This code is copied from SearchDialog
+ *
+ * @return A completely-configured intent ready to send to the voice search activity
+ */
+ private Intent createVoiceAppSearchIntent(Bundle appData) {
+ ComponentName searchActivity = mSearchable.getSearchActivity();
+
+ // create the necessary intent to set up a search-and-forward operation
+ // in the voice search system. We have to keep the bundle separate,
+ // because it becomes immutable once it enters the PendingIntent
+ Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
+ queryIntent.setComponent(searchActivity);
+ PendingIntent pending = PendingIntent.getActivity(
+ getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT);
+
+ // Now set up the bundle that will be inserted into the pending intent
+ // when it's time to do the search. We always build it here (even if empty)
+ // because the voice search activity will always need to insert "QUERY" into
+ // it anyway.
+ Bundle queryExtras = new Bundle();
+ if (appData != null) {
+ queryExtras.putBundle(SearchManager.APP_DATA, appData);
+ }
+
+ // Now build the intent to launch the voice search. Add all necessary
+ // extras to launch the voice recognizer, and then all the necessary extras
+ // to forward the results to the searchable activity
+ Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ voiceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Add all of the configuration options supplied by the searchable's metadata
+ String languageModel = getString(mSearchable.getVoiceLanguageModeId());
+ if (languageModel == null) {
+ languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
+ }
+ String prompt = getString(mSearchable.getVoicePromptTextId());
+ String language = getString(mSearchable.getVoiceLanguageId());
+ int maxResults = mSearchable.getVoiceMaxResults();
+ if (maxResults <= 0) {
+ maxResults = 1;
+ }
+
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
+ voiceIntent.putExtra(EXTRA_CALLING_PACKAGE,
+ searchActivity == null ? null : searchActivity.toShortString());
+
+ // Add the values that configure forwarding the results
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
+
+ return voiceIntent;
+ }
+
+ public SourceResult getSuggestions(String query, int queryLimit) {
try {
Cursor cursor = getSuggestions(mContext, mSearchable, query, queryLimit);
if (DBG) Log.d(TAG, toString() + "[" + query + "] returned.");
- return new SourceResult(this, query, cursor);
+ return new CursorBackedSourceResult(query, cursor);
} catch (RuntimeException ex) {
Log.e(TAG, toString() + "[" + query + "] failed", ex);
- return new SourceResult(this, query);
+ return new CursorBackedSourceResult(query);
}
}
@@ -163,7 +264,7 @@
if (cursor != null && cursor.getCount() > 0) {
cursor.moveToFirst();
}
- return new SourceResult(this, null, cursor);
+ return new CursorBackedSourceResult(null, cursor);
} catch (RuntimeException ex) {
Log.e(TAG, toString() + "[" + shortcutId + "] failed", ex);
if (cursor != null) {
@@ -174,6 +275,28 @@
}
}
+ private class CursorBackedSourceResult extends CursorBackedSuggestionCursor
+ implements SourceResult {
+
+ public CursorBackedSourceResult(String userQuery) {
+ this(userQuery, null);
+ }
+
+ public CursorBackedSourceResult(String userQuery, Cursor cursor) {
+ super(userQuery, cursor);
+ }
+
+ public Source getSource() {
+ return SearchableSource.this;
+ }
+
+ @Override
+ public Source getSuggestionSource() {
+ return SearchableSource.this;
+ }
+
+ }
+
/**
* This is a copy of {@link SearchManager#getSuggestions(SearchableInfo, String)}.
*/
@@ -273,14 +396,14 @@
public boolean equals(Object o) {
if (o != null && o.getClass().equals(this.getClass())) {
SearchableSource s = (SearchableSource) o;
- return s.mSearchable.getSearchActivity().equals(mSearchable.getSearchActivity());
+ return s.getComponentName().equals(getComponentName());
}
return false;
}
@Override
public int hashCode() {
- return mSearchable.getSearchActivity().hashCode();
+ return getComponentName().hashCode();
}
@Override
@@ -308,4 +431,14 @@
return actionKey.getSuggestActionMsgColumn();
}
+ private CharSequence getText(int id) {
+ if (id == 0) return null;
+ return mContext.getPackageManager().getText(mActivityInfo.packageName, id,
+ mActivityInfo.applicationInfo);
+ }
+
+ private String getString(int id) {
+ CharSequence text = getText(id);
+ return text == null ? null : text.toString();
+ }
}
diff --git a/src/com/android/quicksearchbox/SearchableSourceFactory.java b/src/com/android/quicksearchbox/SearchableSourceFactory.java
deleted file mode 100644
index 61d4450..0000000
--- a/src/com/android/quicksearchbox/SearchableSourceFactory.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.app.SearchableInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Log;
-
-public class SearchableSourceFactory implements SourceFactory {
-
- private static final String TAG = "QSB.SearchableSourceFactory";
-
- private final Context mContext;
-
- private final SearchManager mSearchManager;
-
- public SearchableSourceFactory(Context context) {
- mContext = context;
- mSearchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
- }
-
- protected Context getContext() {
- return mContext;
- }
-
- protected ComponentName getWebSearchComponent() {
- // Looks for an activity in the current package that handles ACTION_WEB_SEARCH.
- // This indirect method is used to allow easy replacement of the web
- // search activity when extending this package.
- Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
- webSearchIntent.setPackage(mContext.getPackageName());
- PackageManager pm = mContext.getPackageManager();
- return webSearchIntent.resolveActivity(pm);
- }
-
- public Source createSource(SearchableInfo searchable) {
- if (searchable == null) return null;
- try {
- return new SearchableSource(mContext, searchable);
- } catch (NameNotFoundException ex) {
- Log.e(TAG, "Source not found: " + ex);
- return null;
- }
- }
-
- // TODO: Create a special Source subclass that bypasses the ContentProvider interface
- public Source createWebSearchSource() {
- ComponentName sourceName = getWebSearchComponent();
- SearchableInfo searchable = mSearchManager.getSearchableInfo(sourceName);
- if (searchable == null) {
- Log.e(TAG, "Web search source " + sourceName + " is not searchable.");
- return null;
- }
- try {
- return new WebSource(mContext, searchable);
- } catch (NameNotFoundException ex) {
- Log.e(TAG, "Web search source not found: " + sourceName);
- return null;
- }
- }
-
-}
diff --git a/src/com/android/quicksearchbox/SearchableSources.java b/src/com/android/quicksearchbox/SearchableSources.java
new file mode 100644
index 0000000..e3c881b
--- /dev/null
+++ b/src/com/android/quicksearchbox/SearchableSources.java
@@ -0,0 +1,230 @@
+/*
+ * 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.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;
+import java.util.HashMap;
+
+/**
+ * Maintains a list of search sources.
+ */
+public class SearchableSources {
+
+ // set to true to enable the more verbose debug logging for this file
+ 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.
+ private HashMap<ComponentName, Source> mSources;
+
+ // 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) {
+ 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();
+ }
+
+ public Source getSource(ComponentName name) {
+ return mSources.get(name);
+ }
+
+ 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);
+ }
+ }
+ };
+
+ /**
+ * After calling, clients must call {@link #close()} when done with this object.
+ */
+ 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();
+ }
+
+ /**
+ * Releases all resources used by this object. It is possible to call
+ * {@link #load()} again after calling this method.
+ */
+ public void close() {
+ if (!mLoaded) {
+ throw new IllegalStateException("close(): Not loaded.");
+ }
+
+ mContext.unregisterReceiver(mBroadcastReceiver);
+
+ mDataSetObservable.unregisterAll();
+
+ mSources = null;
+ mLoaded = false;
+ }
+
+ /**
+ * Loads the list of suggestion sources.
+ */
+ private void updateSources() {
+ if (DBG) Log.d(TAG, "updateSources()");
+ mSources = new HashMap<ComponentName,Source>();
+ for (SearchableInfo searchable : mSearchManager.getSearchablesInGlobalSearch()) {
+ Source source = createSearchableSource(searchable);
+ if (source != null) {
+ if (DBG) Log.d(TAG, "Created source " + source);
+ addSource(source);
+ }
+ }
+
+ mWebSearchSource = createWebSearchSource();
+ addSource(mWebSearchSource);
+ }
+
+ private void addSource(Source source) {
+ mSources.put(source.getComponentName(), source);
+ }
+
+ private Source createWebSearchSource() {
+ ComponentName name = getWebSearchComponent();
+ SearchableInfo webSearchable = mSearchManager.getSearchableInfo(name);
+ if (webSearchable == null) {
+ Log.e(TAG, "Web search source " + name + " is not searchable.");
+ return null;
+ }
+ return createSearchableSource(webSearchable);
+ }
+
+ private ComponentName getWebSearchComponent() {
+ // Looks for an activity in the current package that handles ACTION_WEB_SEARCH.
+ // This indirect method is used to allow easy replacement of the web
+ // search activity when extending this package.
+ Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
+ webSearchIntent.setPackage(mContext.getPackageName());
+ PackageManager pm = mContext.getPackageManager();
+ return webSearchIntent.resolveActivity(pm);
+ }
+
+ private Source createSearchableSource(SearchableInfo searchable) {
+ if (searchable == null) return null;
+ try {
+ return new SearchableSource(mContext, searchable);
+ } catch (NameNotFoundException ex) {
+ Log.e(TAG, "Source not found: " + ex);
+ return null;
+ }
+ }
+
+ /**
+ * Register an observer that is called when changes happen to this data set.
+ *
+ * @param observer gets notified when the data set changes.
+ */
+ public void registerDataSetObserver(DataSetObserver observer) {
+ mDataSetObservable.registerObserver(observer);
+ }
+
+ /**
+ * Unregister an observer that has previously been registered with
+ * {@link #registerDataSetObserver(DataSetObserver)}
+ *
+ * @param observer the observer to unregister.
+ */
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ mDataSetObservable.unregisterObserver(observer);
+ }
+
+ protected void notifyDataSetChanged() {
+ mDataSetObservable.notifyChanged();
+ }
+}
diff --git a/src/com/android/quicksearchbox/ShortcutCursor.java b/src/com/android/quicksearchbox/ShortcutCursor.java
index 9cc1b1d..4aac6a3 100644
--- a/src/com/android/quicksearchbox/ShortcutCursor.java
+++ b/src/com/android/quicksearchbox/ShortcutCursor.java
@@ -16,7 +16,6 @@
package com.android.quicksearchbox;
-import android.content.ComponentName;
import android.util.Log;
import java.util.HashSet;
@@ -44,7 +43,7 @@
int count = shortcuts.getCount();
for (int i = 0; i < count; i++) {
shortcuts.moveTo(i);
- if (shortcuts.getSource() != null) {
+ if (shortcuts.getSuggestionSource() != null) {
add(new SuggestionPosition(shortcuts));
}
}
@@ -55,7 +54,7 @@
* Since this modifies the cursor, it should be called on the UI thread.
* This class assumes responsibility for closing refreshed.
*/
- public void refresh(ComponentName source, String shortcutId, SuggestionCursor refreshed) {
+ public void refresh(Source source, String shortcutId, SuggestionCursor refreshed) {
if (DBG) Log.d(TAG, "refresh " + shortcutId);
if (mClosed) {
if (refreshed != null) {
@@ -69,7 +68,7 @@
int count = getCount();
for (int i = 0; i < count; i++) {
moveTo(i);
- if (shortcutId.equals(getShortcutId()) && source.equals(getSourceComponentName())) {
+ if (shortcutId.equals(getShortcutId()) && source.equals(getSuggestionSource())) {
if (refreshed != null && refreshed.getCount() > 0) {
replaceRow(new SuggestionPosition(refreshed));
} else {
diff --git a/src/com/android/quicksearchbox/ShortcutPromoter.java b/src/com/android/quicksearchbox/ShortcutPromoter.java
index a0a37e5..ebef712 100644
--- a/src/com/android/quicksearchbox/ShortcutPromoter.java
+++ b/src/com/android/quicksearchbox/ShortcutPromoter.java
@@ -44,7 +44,7 @@
}
public void pickPromoted(SuggestionCursor shortcuts,
- ArrayList<SuggestionCursor> suggestions, int maxPromoted,
+ ArrayList<CorpusResult> suggestions, int maxPromoted,
ListSuggestionCursor promoted) {
int shortcutCount = shortcuts == null ? 0 : shortcuts.getCount();
int promotedShortcutCount = Math.min(shortcutCount, maxPromoted);
diff --git a/src/com/android/quicksearchbox/ShortcutRefresher.java b/src/com/android/quicksearchbox/ShortcutRefresher.java
index 1ede007..e1fa613 100644
--- a/src/com/android/quicksearchbox/ShortcutRefresher.java
+++ b/src/com/android/quicksearchbox/ShortcutRefresher.java
@@ -16,8 +16,6 @@
package com.android.quicksearchbox;
-import android.content.ComponentName;
-
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -32,17 +30,16 @@
/**
* Called by the ShortcutRefresher when a shortcut has been refreshed.
*
- * @param componentName of the source of this shortcut.
+ * @param source source of this shortcut.
* @param shortcutId the id of the shortcut.
* @param refreshed the updated shortcut, or {@code null} if the shortcut
* is no longer valid and should be deleted.
*/
- void onShortcutRefreshed(ComponentName componentName, String shortcutId,
+ void onShortcutRefreshed(Source source, String shortcutId,
SuggestionCursor refreshed);
}
private final SourceTaskExecutor mExecutor;
- private final SourceLookup mSourceLookup;
private final Set<String> mRefreshed = Collections.synchronizedSet(new HashSet<String>());
@@ -50,11 +47,9 @@
* Create a ShortcutRefresher that will refresh shortcuts using the given executor.
*
* @param executor Used to execute the tasks.
- * @param sourceLookup Used to lookup suggestion sources by component name.
*/
- public ShortcutRefresher(SourceTaskExecutor executor, SourceLookup sourceLookup) {
+ public ShortcutRefresher(SourceTaskExecutor executor) {
mExecutor = executor;
- mSourceLookup = sourceLookup;
}
/**
@@ -69,13 +64,12 @@
shortcuts.moveTo(i);
if (shouldRefresh(shortcuts)) {
String shortcutId = shortcuts.getShortcutId();
- ComponentName componentName = shortcuts.getSourceComponentName();
- Source source = mSourceLookup.getSourceByComponentName(componentName);
+ Source source = shortcuts.getSuggestionSource();
// If we can't find the source then invalidate the shortcut.
// Otherwise, send off the refresh task.
if (source == null) {
- listener.onShortcutRefreshed(componentName, shortcutId, null);
+ listener.onShortcutRefreshed(source, shortcutId, null);
} else {
String extraData = shortcuts.getSuggestionIntentExtraData();
ShortcutRefreshTask refreshTask = new ShortcutRefreshTask(
@@ -116,7 +110,7 @@
}
private static String makeKey(SuggestionCursor shortcut) {
- return shortcut.getSourceComponentName().flattenToShortString() + "#"
+ return shortcut.getSuggestionSource().getFlattenedComponentName() + "#"
+ shortcut.getShortcutId();
}
@@ -146,11 +140,8 @@
// TODO: Add latency tracking and logging.
SuggestionCursor refreshed = mSource.refreshShortcut(mShortcutId, mExtraData);
onShortcutRefreshed(refreshed);
- mListener.onShortcutRefreshed(mSource.getComponentName(), mShortcutId, refreshed);
+ mListener.onShortcutRefreshed(mSource, mShortcutId, refreshed);
}
- public Source getSource() {
- return mSource;
- }
}
}
diff --git a/src/com/android/quicksearchbox/ShortcutRepository.java b/src/com/android/quicksearchbox/ShortcutRepository.java
index b89e59f..2aba629 100644
--- a/src/com/android/quicksearchbox/ShortcutRepository.java
+++ b/src/com/android/quicksearchbox/ShortcutRepository.java
@@ -16,10 +16,7 @@
package com.android.quicksearchbox;
-import android.content.ComponentName;
-
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
/**
* Holds information about shortcuts (results the user has clicked on before), and returns
@@ -45,7 +42,7 @@
/**
* Reports a click on a suggestion.
*/
- void reportClick(SuggestionPosition clicked);
+ void reportClick(SuggestionCursor suggestions, int position);
/**
* @param query The query.
@@ -54,7 +51,8 @@
SuggestionCursor getShortcutsForQuery(String query);
/**
- * @return A ranking of suggestion sources based on clicks and impressions.
+ * @return A map for corpus name to score. A higher score means that the corpus
+ * is more important.
*/
- ArrayList<ComponentName> getSourceRanking();
+ Map<String,Integer> getCorpusScores();
}
diff --git a/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java b/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java
index 778aca6..870b9f4 100644
--- a/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java
+++ b/src/com/android/quicksearchbox/ShortcutRepositoryImplLog.java
@@ -17,19 +17,21 @@
package com.android.quicksearchbox;
import android.app.SearchManager;
-import android.content.*;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.os.Handler;
import android.util.Log;
-import android.view.KeyEvent;
import java.io.File;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Map;
/**
* A shortcut repository implementation that uses a log of every click.
@@ -45,7 +47,7 @@
private static final String TAG = "QSB.ShortcutRepositoryImplLog";
private static final String DB_NAME = "qsb-log.db";
- private static final int DB_VERSION = 25;
+ private static final int DB_VERSION = 26;
private static final String HAS_HISTORY_QUERY =
"SELECT " + Shortcuts.intent_key.fullName + " FROM " + Shortcuts.TABLE_NAME;
@@ -59,7 +61,7 @@
private final Context mContext;
private final Config mConfig;
- private final SourceLookup mSources;
+ private final Corpora mCorpora;
private final ShortcutRefresher mRefresher;
private final Handler mUiThread;
private final DbOpenHelper mOpenHelper;
@@ -68,7 +70,7 @@
* Create an instance to the repo.
*/
public static ShortcutRepository create(Context context, Config config,
- SourceLookup sources, ShortcutRefresher refresher, Handler uiThread) {
+ Corpora sources, ShortcutRefresher refresher, Handler uiThread) {
return new ShortcutRepositoryImplLog(context, config, sources, refresher,
uiThread, DB_NAME);
}
@@ -77,11 +79,11 @@
* @param context Used to create / open db
* @param name The name of the database to create.
*/
- ShortcutRepositoryImplLog(Context context, Config config, SourceLookup sources,
+ ShortcutRepositoryImplLog(Context context, Config config, Corpora corpora,
ShortcutRefresher refresher, Handler uiThread, String name) {
mContext = context;
mConfig = config;
- mSources = sources;
+ mCorpora = corpora;
mRefresher = refresher;
mUiThread = uiThread;
mOpenHelper = new DbOpenHelper(context, name, DB_VERSION, config);
@@ -185,8 +187,9 @@
getOpenHelper().close();
}
- public void reportClick(SuggestionPosition clicked) {
- logClick(clicked, System.currentTimeMillis());
+ public void reportClick(SuggestionCursor suggestions, int position) {
+ suggestions.moveTo(position);
+ logClick(suggestions, System.currentTimeMillis());
}
public SuggestionCursor getShortcutsForQuery(String query) {
@@ -197,8 +200,8 @@
return shortcuts;
}
- public ArrayList<ComponentName> getSourceRanking() {
- return getSourceRanking(mConfig.getMinClicksForSourceRanking());
+ public Map<String,Integer> getCorpusScores() {
+ return getCorpusScores(mConfig.getMinClicksForSourceRanking());
}
// -------------------------- end ShortcutRepository --------------------------
@@ -222,30 +225,30 @@
cursor.close();
return null;
}
- return new ShortcutCursor(new SuggestionCursorImpl(mSources, query, cursor));
+ return new ShortcutCursor(new SuggestionCursorImpl(query, cursor));
}
private void startRefresh(final ShortcutCursor shortcuts) {
mRefresher.refresh(shortcuts, new ShortcutRefresher.Listener() {
- public void onShortcutRefreshed(final ComponentName componentName,
+ public void onShortcutRefreshed(final Source source,
final String shortcutId, final SuggestionCursor refreshed) {
- refreshShortcut(componentName, shortcutId, refreshed);
+ refreshShortcut(source, shortcutId, refreshed);
mUiThread.post(new Runnable() {
public void run() {
- shortcuts.refresh(componentName, shortcutId, refreshed);
+ shortcuts.refresh(source, shortcutId, refreshed);
}
});
}
});
}
- private void refreshShortcut(ComponentName source, String shortcutId, SuggestionCursor refreshed) {
+ private void refreshShortcut(Source source, String shortcutId, SuggestionCursor refreshed) {
if (source == null) throw new NullPointerException("source");
if (shortcutId == null) throw new NullPointerException("shortcutId");
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- String[] whereArgs = { shortcutId, source.flattenToShortString() };
+ String[] whereArgs = { shortcutId, source.getFlattenedComponentName() };
if (refreshed == null || refreshed.getCount() == 0) {
if (DBG) Log.d(TAG, "Deleting shortcut: " + shortcutId);
db.delete(Shortcuts.TABLE_NAME, SHORTCUT_BY_ID_WHERE, whereArgs);
@@ -262,17 +265,15 @@
private final String mSearchSpinner = ContentResolver.SCHEME_ANDROID_RESOURCE
+ "://" + mContext.getPackageName() + "/" + R.drawable.search_spinner;
- private final SourceLookup mSources;
private final HashMap<String, Source> mSourceCache;
- public SuggestionCursorImpl(SourceLookup sources, String userQuery, Cursor cursor) {
+ public SuggestionCursorImpl(String userQuery, Cursor cursor) {
super(userQuery, cursor);
- mSources = sources;
mSourceCache = new HashMap<String, Source>();
}
@Override
- protected Source getSource() {
+ public Source getSuggestionSource() {
// TODO: Using ordinal() is hacky, look up the column instead
String srcStr = mCursor.getString(Shortcuts.source.ordinal());
if (srcStr == null) {
@@ -281,7 +282,7 @@
Source source = mSourceCache.get(srcStr);
if (source == null) {
ComponentName srcName = ComponentName.unflattenFromString(srcStr);
- source = mSources.getSourceByComponentName(srcName);
+ source = mCorpora.getSource(srcName);
// We cache the source so that it can be found quickly, and so
// that it doesn't disappear over the lifetime of this cursor.
mSourceCache.put(srcStr, source);
@@ -340,34 +341,30 @@
* @param minClicks The minimum number of clicks a source must have.
* @return The list of sources, ranked by total clicks.
*/
- ArrayList<ComponentName> getSourceRanking(int minClicks) {
+ Map<String,Integer> getCorpusScores(int minClicks) {
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
final Cursor cursor = db.rawQuery(
SOURCE_RANKING_SQL, new String[] { String.valueOf(minClicks) });
try {
- final ArrayList<ComponentName> sources =
- new ArrayList<ComponentName>(cursor.getCount());
+ Map<String,Integer> corpora = new HashMap<String,Integer>(cursor.getCount());
while (cursor.moveToNext()) {
- sources.add(sourceFromCursor(cursor));
+ String name = cursor.getString(SourceStats.corpus.ordinal());
+ int clicks = cursor.getInt(SourceStats.total_clicks.ordinal());
+ corpora.put(name, clicks);
}
- return sources;
+ return corpora;
} finally {
cursor.close();
}
}
- private ComponentName sourceFromCursor(Cursor cursor) {
- return ComponentName.unflattenFromString(cursor.getString(SourceStats.component.ordinal()));
- }
-
private ContentValues makeShortcutRow(SuggestionCursor suggestion) {
- ComponentName source = suggestion.getSourceComponentName();
- Intent intent = suggestion.getSuggestionIntent(mContext, null, KeyEvent.KEYCODE_UNKNOWN, null);
- String intentAction = intent.getAction();
- String intentData = intent.getDataString();
- String intentQuery = intent.getStringExtra(SearchManager.QUERY);
- String intentExtraData = intent.getStringExtra(SearchManager.EXTRA_DATA_KEY);
+ String intentAction = suggestion.getSuggestionIntentAction();
+ String intentData = suggestion.getSuggestionIntentDataString();
+ String intentQuery = suggestion.getSuggestionQuery();
+ String intentExtraData = suggestion.getSuggestionIntentExtraData();
+ ComponentName source = suggestion.getSuggestionSource().getComponentName();
StringBuilder key = new StringBuilder(source.flattenToShortString());
key.append("#");
if (intentData != null) {
@@ -405,9 +402,7 @@
return cv;
}
- private void logClick(SuggestionPosition clicked, long now) {
- SuggestionCursor suggestion = clicked.getSuggestion();
-
+ private void logClick(SuggestionCursor suggestion, long now) {
if (DBG) {
Log.d(TAG, "logClicked(" + suggestion + ")");
}
@@ -419,7 +414,7 @@
// Once the user has clicked on a shortcut, don't bother refreshing
// (especially if this is a new shortcut)
- mRefresher.onShortcutRefreshed(clicked.getSuggestion());
+ mRefresher.onShortcutRefreshed(suggestion);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -427,7 +422,7 @@
// Since intent_key is the primary key, any existing
// suggestion with the same source+data+action will be replaced
if (DBG) Log.d(TAG, "Adding shortcut: " + suggestion);
- ContentValues shortcut = makeShortcutRow(clicked.getSuggestion());
+ ContentValues shortcut = makeShortcutRow(suggestion);
String intentKey = shortcut.getAsString(Shortcuts.intent_key.name());
db.replaceOrThrow(Shortcuts.TABLE_NAME, null, shortcut);
@@ -440,19 +435,22 @@
db.insertOrThrow(ClickLog.TABLE_NAME, null, cv);
}
- // Log click for source
- {
- final ContentValues cv = new ContentValues();
- ComponentName name = suggestion.getSourceComponentName();
- cv.put(SourceLog.component.name(), name.flattenToString());
- cv.put(SourceLog.time.name(), now);
- cv.put(SourceLog.click_count.name(), 1);
- db.insertOrThrow(SourceLog.TABLE_NAME, null, cv);
- }
+ // Log click for corpus
+ Corpus corpus = mCorpora.getCorpusForSource(suggestion.getSuggestionSource());
+ logCorpusClick(db, corpus, now);
postSourceEventCleanup(now);
}
+ private void logCorpusClick(SQLiteDatabase db, Corpus corpus, long now) {
+ if (corpus == null) return;
+ ContentValues cv = new ContentValues();
+ cv.put(SourceLog.corpus.name(), corpus.getName());
+ cv.put(SourceLog.time.name(), now);
+ cv.put(SourceLog.click_count.name(), 1);
+ db.insertOrThrow(SourceLog.TABLE_NAME, null, cv);
+ }
+
/**
* Execute queries necessary to keep things up to date after inserting into {@link SourceLog}.
*
@@ -469,12 +467,12 @@
+ now + " - " + mConfig.getMaxSourceEventAgeMillis() + ";");
// update the source stats
- final String columns = SourceLog.component + "," +
+ final String columns = SourceLog.corpus + "," +
"SUM(" + SourceLog.click_count.fullName + ")";
db.execSQL("DELETE FROM " + SourceStats.TABLE_NAME);
db.execSQL("INSERT INTO " + SourceStats.TABLE_NAME + " "
+ "SELECT " + columns + " FROM " + SourceLog.TABLE_NAME + " GROUP BY "
- + SourceLog.component.name());
+ + SourceLog.corpus.name());
}
// -------------------------- TABLES --------------------------
@@ -541,7 +539,7 @@
*/
enum SourceLog {
_id,
- component,
+ corpus,
time,
click_count,
impression_count;
@@ -573,7 +571,7 @@
* are reported.
*/
enum SourceStats {
- component,
+ corpus,
total_clicks;
static final String TABLE_NAME = "sourcetotals";
@@ -753,13 +751,13 @@
db.execSQL("CREATE TABLE " + SourceLog.TABLE_NAME + " ( " +
SourceLog._id.name() + " INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " +
- SourceLog.component.name() + " TEXT NOT NULL COLLATE UNICODE, " +
+ SourceLog.corpus.name() + " TEXT NOT NULL COLLATE UNICODE, " +
SourceLog.time.name() + " INTEGER, " +
SourceLog.click_count + " INTEGER);"
);
db.execSQL("CREATE TABLE " + SourceStats.TABLE_NAME + " ( " +
- SourceStats.component.name() + " TEXT NOT NULL COLLATE UNICODE PRIMARY KEY, " +
+ SourceStats.corpus.name() + " TEXT NOT NULL COLLATE UNICODE PRIMARY KEY, " +
SourceStats.total_clicks + " INTEGER);"
);
}
diff --git a/src/com/android/quicksearchbox/ShouldQueryStrategy.java b/src/com/android/quicksearchbox/ShouldQueryStrategy.java
index 1de96af..5f09b24 100644
--- a/src/com/android/quicksearchbox/ShouldQueryStrategy.java
+++ b/src/com/android/quicksearchbox/ShouldQueryStrategy.java
@@ -16,7 +16,6 @@
package com.android.quicksearchbox;
-import android.content.ComponentName;
import android.util.Log;
import java.util.HashMap;
@@ -36,24 +35,23 @@
// The last query we've seen
private String mLastQuery = "";
- // The current implementation keeps a record of those sources that have
- // returned zero results for some prefix of the current query. mEmptySources
- // maps from source component name to the length of the query which returned
+ // The current implementation keeps a record of those corpora that have
+ // returned zero results for some prefix of the current query. mEmptyCorpora
+ // maps from corpus to the length of the query which returned
// zero results. When a query is shortened (e.g., by deleting characters)
- // or changed entirely, mEmptySources is pruned (in updateQuery)
- private final HashMap<ComponentName, Integer> mEmptySources
- = new HashMap<ComponentName, Integer>();
+ // or changed entirely, mEmptyCorpora is pruned (in updateQuery)
+ private final HashMap<Corpus, Integer> mEmptyCorpora
+ = new HashMap<Corpus, Integer>();
/**
* Returns whether we should query the given source for the given query.
*/
- public synchronized boolean shouldQuerySource(Source source, String query) {
+ public synchronized boolean shouldQueryCorpus(Corpus corpus, String query) {
updateQuery(query);
- if (query.length() >= source.getQueryThreshold()) {
- ComponentName sourceName = source.getComponentName();
- if (!source.queryAfterZeroResults() && mEmptySources.containsKey(sourceName)) {
- if (DBG) Log.i(TAG, "Not querying " + sourceName + ", returned 0 after "
- + mEmptySources.get(sourceName));
+ if (query.length() >= corpus.getQueryThreshold()) {
+ if (!corpus.queryAfterZeroResults() && mEmptyCorpora.containsKey(corpus)) {
+ if (DBG) Log.i(TAG, "Not querying " + corpus + ", returned 0 after "
+ + mEmptyCorpora.get(corpus));
return false;
}
return true;
@@ -64,12 +62,12 @@
/**
* Called to notify ShouldQueryStrategy when a source reports no results for a query.
*/
- public synchronized void onZeroResults(ComponentName source, String query) {
- if (DBG) Log.i(TAG, source + " returned 0 results for " + query);
+ public synchronized void onZeroResults(Corpus corpus, String query) {
+ if (DBG) Log.i(TAG, corpus + " returned 0 results for " + query);
// Make sure this result is actually for a prefix of the current query.
if (mLastQuery.startsWith(query)) {
// TODO: Don't bother if queryAfterZeroResults is true
- mEmptySources.put(source, query.length());
+ mEmptyCorpora.put(corpus, query.length());
}
}
@@ -80,7 +78,7 @@
} else if (mLastQuery.startsWith(query)) {
// This is a widening of the last query: clear out any sources
// that reported zero results after this query.
- Iterator<Map.Entry<ComponentName, Integer>> iter = mEmptySources.entrySet().iterator();
+ Iterator<Map.Entry<Corpus, Integer>> iter = mEmptyCorpora.entrySet().iterator();
while (iter.hasNext()) {
if (iter.next().getValue() > query.length()) {
iter.remove();
@@ -88,7 +86,7 @@
}
} else {
// This is a completely different query, clear everything.
- mEmptySources.clear();
+ mEmptyCorpora.clear();
}
}
}
diff --git a/src/com/android/quicksearchbox/SingleSourceSuggestionsProvider.java b/src/com/android/quicksearchbox/SingleCorpusSuggestionsProvider.java
similarity index 74%
rename from src/com/android/quicksearchbox/SingleSourceSuggestionsProvider.java
rename to src/com/android/quicksearchbox/SingleCorpusSuggestionsProvider.java
index 5da2f28..c2c235c 100644
--- a/src/com/android/quicksearchbox/SingleSourceSuggestionsProvider.java
+++ b/src/com/android/quicksearchbox/SingleCorpusSuggestionsProvider.java
@@ -21,30 +21,28 @@
import java.util.ArrayList;
/**
- * A suggestions provider that gets suggestions from a single source.
+ * A suggestions provider that gets suggestions from a single corpus.
*/
-public class SingleSourceSuggestionsProvider extends AbstractSuggestionsProvider {
+public class SingleCorpusSuggestionsProvider extends AbstractSuggestionsProvider {
- private final Source mSource;
-
- private final ArrayList<Source> mSources;
+ private final ArrayList<Corpus> mCorpora;
private final ShortcutRepository mShortcutRepo;
- public SingleSourceSuggestionsProvider(Config config, Source source,
+ public SingleCorpusSuggestionsProvider(Config config, Corpus corpus,
SourceTaskExecutor queryExecutor,
Handler publishThread,
Promoter promoter,
ShortcutRepository shortcutRepo) {
super(config, queryExecutor, publishThread, promoter);
- mSource = source;
- mSources = new ArrayList<Source>(1);
- mSources.add(source);
+ mCorpora = new ArrayList<Corpus>(1);
+ mCorpora.add(corpus);
mShortcutRepo = shortcutRepo;
}
- public ArrayList<Source> getOrderedSources() {
- return mSources;
+ @Override
+ public ArrayList<Corpus> getOrderedCorpora() {
+ return mCorpora;
}
@Override
diff --git a/src/com/android/quicksearchbox/SingleSourceCorpus.java b/src/com/android/quicksearchbox/SingleSourceCorpus.java
new file mode 100644
index 0000000..fd5d353
--- /dev/null
+++ b/src/com/android/quicksearchbox/SingleSourceCorpus.java
@@ -0,0 +1,84 @@
+/*
+ * 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.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * A corpus that uses a single source.
+ */
+public class SingleSourceCorpus extends AbstractCorpus {
+
+ private final Source mSource;
+
+ public SingleSourceCorpus(Source source) {
+ mSource = source;
+ }
+
+ public Drawable getCorpusIcon() {
+ return mSource.getSourceIcon();
+ }
+
+ public Uri getCorpusIconUri() {
+ return mSource.getSourceIconUri();
+ }
+
+ public CharSequence getLabel() {
+ return mSource.getLabel();
+ }
+
+ public CharSequence getSettingsDescription() {
+ return mSource.getSettingsDescription();
+ }
+
+ public CorpusResult getSuggestions(String query, int queryLimit) {
+ SourceResult sourceResult = mSource.getSuggestions(query, queryLimit);
+ return new SingleSourceCorpusResult(this, query, sourceResult);
+ }
+
+ public String getName() {
+ return mSource.getFlattenedComponentName();
+ }
+
+ public boolean queryAfterZeroResults() {
+ return mSource.queryAfterZeroResults();
+ }
+
+ public int getQueryThreshold() {
+ return mSource.getQueryThreshold();
+ }
+
+ public boolean voiceSearchEnabled() {
+ return mSource.voiceSearchEnabled();
+ }
+
+ public Intent createSearchIntent(String query, Bundle appData) {
+ return mSource.createSearchIntent(query, appData);
+ }
+
+ public Intent createVoiceSearchIntent(Bundle appData) {
+ return mSource.createVoiceSearchIntent(appData);
+ }
+
+ public boolean isWebCorpus() {
+ return false;
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/SingleSourceCorpusResult.java b/src/com/android/quicksearchbox/SingleSourceCorpusResult.java
new file mode 100644
index 0000000..28bc4c7
--- /dev/null
+++ b/src/com/android/quicksearchbox/SingleSourceCorpusResult.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+
+/**
+ * A CorpusResult backed by a single SourceResult.
+ */
+public class SingleSourceCorpusResult extends SuggestionCursorWrapper implements CorpusResult {
+
+ private final Corpus mCorpus;
+
+ public SingleSourceCorpusResult(Corpus corpus, String userQuery, SuggestionCursor cursor) {
+ super(userQuery, cursor);
+ mCorpus = corpus;
+ }
+
+ public Corpus getCorpus() {
+ return mCorpus;
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/Source.java b/src/com/android/quicksearchbox/Source.java
index 3a1056c..c322187 100644
--- a/src/com/android/quicksearchbox/Source.java
+++ b/src/com/android/quicksearchbox/Source.java
@@ -17,8 +17,10 @@
package com.android.quicksearchbox;
import android.content.ComponentName;
+import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
/**
* Interface for suggestion sources.
@@ -97,6 +99,12 @@
*/
boolean queryAfterZeroResults();
+ boolean voiceSearchEnabled();
+
+ Intent createSearchIntent(String query, Bundle appData);
+
+ Intent createVoiceSearchIntent(Bundle appData);
+
/**
* Gets suggestions from this source.
*
@@ -104,7 +112,7 @@
* @param queryLimit An advisory maximum number of results that the source should return.
* @return The suggestion results.
*/
- SuggestionCursor getSuggestions(String query, int queryLimit);
+ SourceResult getSuggestions(String query, int queryLimit);
/**
* Updates a shorcut.
diff --git a/src/com/android/quicksearchbox/SourceLookup.java b/src/com/android/quicksearchbox/SourceLookup.java
deleted file mode 100644
index 2a5f31a..0000000
--- a/src/com/android/quicksearchbox/SourceLookup.java
+++ /dev/null
@@ -1,69 +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.content.ComponentName;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-/**
- * Defines operations for looking up information about a {@link Source}.
- */
-public interface SourceLookup {
-
- /**
- * Gets a suggestion source (or the current web search source) by component name.
- *
- * @return A source, or {@code null} if the source was not found or
- * {@code componentName} was null.
- */
- Source getSourceByComponentName(ComponentName componentName);
-
- /**
- * Returns the web search source.
- *
- * @return <code>null</code> only if there is no web search source available.
- */
- Source getWebSearchSource();
-
- /**
- * Checks whether web suggestions are enabled.
- */
- boolean areWebSuggestionsEnabled();
-
- /**
- * Checks if we trust the given source not to be spammy.
- */
- boolean isTrustedSource(Source source);
-
- /**
- * Checks if the source is enabled.
- */
- boolean isEnabledSource(Source source);
-
- /**
- * Gets all suggestion sources. This does not include any web search sources.
- *
- * @return A list of suggestion sources, including sources that are not enabled.
- * Callers must not modify the returned collection.
- */
- Collection<Source> getSources();
-
- ArrayList<Source> getEnabledSources();
-
-}
diff --git a/src/com/android/quicksearchbox/SourceResult.java b/src/com/android/quicksearchbox/SourceResult.java
index fce01fe..20ea48f 100644
--- a/src/com/android/quicksearchbox/SourceResult.java
+++ b/src/com/android/quicksearchbox/SourceResult.java
@@ -16,47 +16,11 @@
package com.android.quicksearchbox;
-import android.database.Cursor;
-
/**
* The result of getting suggestions from a single source.
- *
- * This class is similar to a cursor, in that it is moved to a suggestion, and then
- * the suggestion info can be read out.
- *
*/
-public class SourceResult extends CursorBackedSuggestionCursor {
+public interface SourceResult extends SuggestionCursor {
- /** The suggestion source. */
- private final Source mSource;
+ Source getSource();
- /**
- * Creates a result for a failed or canceled query.
- */
- public SourceResult(Source source, String userQuery) {
- this(source, userQuery, null);
- }
-
- /**
- * Creates a new source result.
- *
- * @param source The suggestion source. Must be non-null.
- * @param cursor The cursor containing the suggestions. May be null.
- */
- public SourceResult(Source source, String userQuery, Cursor cursor) {
- super(userQuery, cursor);
- if (source == null) {
- throw new NullPointerException("source is null");
- }
- mSource = source;
- }
-
- protected Source getSource() {
- return mSource;
- }
-
- @Override
- public String toString() {
- return "SourceResult{source=" + mSource + ",query=" + getUserQuery() + "}";
- }
}
diff --git a/src/com/android/quicksearchbox/SourceTask.java b/src/com/android/quicksearchbox/SourceTask.java
index 86325ba..cb8b507 100644
--- a/src/com/android/quicksearchbox/SourceTask.java
+++ b/src/com/android/quicksearchbox/SourceTask.java
@@ -22,6 +22,4 @@
*/
public interface SourceTask extends Runnable {
- Source getSource();
-
}
diff --git a/src/com/android/quicksearchbox/Sources.java b/src/com/android/quicksearchbox/Sources.java
deleted file mode 100644
index 34a8a13..0000000
--- a/src/com/android/quicksearchbox/Sources.java
+++ /dev/null
@@ -1,347 +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.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.SharedPreferences;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-
-/**
- * Maintains the list of all suggestion sources.
- */
-public class Sources implements SourceLookup {
-
- // set to true to enable the more verbose debug logging for this file
- private static final boolean DBG = false;
- private static final String TAG = "QSB.Sources";
-
- // Name of the preferences file used to store suggestion source preferences
- public static final String PREFERENCES_NAME = "SuggestionSources";
-
- // The key for the preference that holds the selected web search source
- public static final String WEB_SEARCH_SOURCE_PREF = "web_search_source";
-
- private static final int MSG_UPDATE_SOURCES = 0;
-
- // 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 Context mContext;
- private final Config mConfig;
- private final SourceFactory mSourceFactory;
- private final SearchManager mSearchManager;
- private final SharedPreferences mPreferences;
- private boolean mLoaded;
-
- // Runs source updates
- private final UpdateHandler mHandler;
-
- // All available suggestion sources.
- private HashMap<ComponentName,Source> mSources;
-
- // The web search source to use. This is the source selected in the preferences,
- // or the default source if no source has been selected.
- private Source mSelectedWebSearchSource;
-
- // All enabled suggestion sources. This does not include the web search source.
- private ArrayList<Source> mEnabledSources;
-
- // Updates the inclusion of the web search provider.
- private ShowWebSuggestionsSettingChangeObserver mShowWebSuggestionsSettingChangeObserver;
-
- /**
- *
- * @param context Used for looking up source information etc.
- */
- public Sources(Context context, Config config, SourceFactory sourceFactory) {
- mContext = context;
- mConfig = config;
- mSourceFactory = sourceFactory;
- mSearchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE);
- mPreferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
- mLoaded = false;
- HandlerThread t = new HandlerThread("Sources.UpdateThread",
- android.os.Process.THREAD_PRIORITY_BACKGROUND);
- t.start();
- mHandler = new UpdateHandler(t.getLooper());
- }
-
- /**
- * Gets all suggestion sources. This does not include any web search sources.
- *
- * @return A list of suggestion sources, including sources that are not enabled.
- * Callers must not modify the returned list.
- */
- public synchronized Collection<Source> getSources() {
- if (!mLoaded) {
- throw new IllegalStateException("getSources(): sources not loaded.");
- }
- return mSources.values();
- }
-
- /** {@inheritDoc} */
- public synchronized Source getSourceByComponentName(ComponentName componentName) {
- Source source = mSources.get(componentName);
-
- // If the source was not found, back off to check the web source in case it's that.
- if (source == null) {
- if (mSelectedWebSearchSource != null &&
- mSelectedWebSearchSource.getComponentName().equals(componentName)) {
- source = mSelectedWebSearchSource;
- }
- }
- return source;
- }
-
- /**
- * Gets all enabled suggestion sources.
- *
- * @return All enabled suggestion sources (does not include the web search source).
- * Callers must not modify the returned list.
- */
- public synchronized ArrayList<Source> getEnabledSources() {
- if (!mLoaded) {
- throw new IllegalStateException("getEnabledSources(): sources not loaded.");
- }
- return mEnabledSources;
- }
-
- /** {@inheritDoc} */
- public synchronized Source getWebSearchSource() {
- if (!mLoaded) {
- throw new IllegalStateException("getSelectedWebSearchSource(): sources not loaded.");
- }
- return mSelectedWebSearchSource;
- }
-
- public boolean areWebSuggestionsEnabled() {
- return (Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.SHOW_WEB_SUGGESTIONS,
- 1 /* default on until user actually changes it */) == 1);
- }
-
- /**
- * Gets the preference key of the preference for whether the given source
- * is enabled. The preference is stored in the {@link #PREFERENCES_NAME}
- * preferences file.
- */
- public static String getSourceEnabledPreference(Source source) {
- return "enable_source_" + source.getFlattenedComponentName();
- }
-
- // 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.
- scheduleUpdateSources();
- }
- }
- };
-
- /* package */ void scheduleUpdateSources() {
- if (DBG) Log.d(TAG, "scheduleUpdateSources()");
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SOURCES, UPDATE_SOURCES_DELAY_MILLIS);
- }
-
- private class UpdateHandler extends Handler {
-
- public UpdateHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_SOURCES:
- // Remove any duplicate update messages
- removeMessages(MSG_UPDATE_SOURCES);
- updateSources();
- break;
- }
- }
- }
-
- /**
- * After calling, clients must call {@link #close()} when done with this object.
- */
- public synchronized void load() {
- if (mLoaded) {
- throw new IllegalStateException("load(): Already loaded.");
- }
-
- // Listen for searchables changes.
- mContext.registerReceiver(mBroadcastReceiver,
- new IntentFilter(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));
-
- // Listen for search preference changes.
- mContext.registerReceiver(mBroadcastReceiver,
- new IntentFilter(SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED));
-
- mShowWebSuggestionsSettingChangeObserver =
- new ShowWebSuggestionsSettingChangeObserver(mHandler);
- mContext.getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.System.SHOW_WEB_SUGGESTIONS),
- true,
- mShowWebSuggestionsSettingChangeObserver);
-
- // update list of sources
- updateSources();
-
- mLoaded = true;
- }
-
- /**
- * Releases all resources used by this object. It is possible to call
- * {@link #load()} again after calling this method.
- */
- public synchronized void close() {
- if (!mLoaded) {
- throw new IllegalStateException("close(): Not loaded.");
- }
- mContext.unregisterReceiver(mBroadcastReceiver);
- mContext.getContentResolver().unregisterContentObserver(
- mShowWebSuggestionsSettingChangeObserver);
-
- mSources = null;
- mSelectedWebSearchSource = null;
- mEnabledSources = null;
- mLoaded = false;
- }
-
- /**
- * Loads the list of suggestion sources. This method is package private so that
- * it can be called efficiently from inner classes.
- */
- /* package */ synchronized void updateSources() {
- if (DBG) Log.d(TAG, "updateSources()");
- mSources = new HashMap<ComponentName,Source>();
- addExternalSources();
-
- mEnabledSources = findEnabledSources();
- mSelectedWebSearchSource = findWebSearchSource();
- }
-
- private void addExternalSources() {
- ArrayList<Source> trusted = new ArrayList<Source>();
- ArrayList<Source> untrusted = new ArrayList<Source>();
- for (SearchableInfo searchable : mSearchManager.getSearchablesInGlobalSearch()) {
- Source source = mSourceFactory.createSource(searchable);
- if (source != null) {
- if (DBG) Log.d(TAG, "Created source " + source);
- if (isTrustedSource(source)) {
- trusted.add(source);
- } else {
- untrusted.add(source);
- }
- }
- }
- for (Source s : trusted) {
- addSource(s);
- }
- for (Source s : untrusted) {
- addSource(s);
- }
- }
-
- private void addSource(Source source) {
- if (DBG) Log.d(TAG, "Adding source: " + source);
- Source old = mSources.put(source.getComponentName(), source);
- if (old != null) {
- Log.w(TAG, "Replaced source " + old + " for " + source.getComponentName());
- }
- }
-
- /**
- * Computes the list of enabled suggestion sources.
- */
- private ArrayList<Source> findEnabledSources() {
- ArrayList<Source> enabledSources = new ArrayList<Source>();
- for (Source source : mSources.values()) {
- if (isEnabledSource(source)) {
- if (DBG) Log.d(TAG, "Adding enabled source " + source);
- enabledSources.add(source);
- }
- }
- return enabledSources;
- }
-
- public synchronized boolean isEnabledSource(Source source) {
- if (source == null) return false;
- boolean defaultEnabled = isTrustedSource(source);
- if (mPreferences == null) {
- Log.w(TAG, "Search preferences " + PREFERENCES_NAME + " not found.");
- return true;
- }
- String sourceEnabledPref = getSourceEnabledPreference(source);
- return mPreferences.getBoolean(sourceEnabledPref, defaultEnabled);
- }
-
- public synchronized boolean isTrustedSource(Source source) {
- if (source == null) return false;
- String packageName = source.getComponentName().getPackageName();
- return mConfig.isTrustedSource(packageName);
- }
-
- /**
- * Finds the selected web search source.
- */
- private Source findWebSearchSource() {
- return mSourceFactory.createWebSearchSource();
- }
-
- /**
- * ContentObserver which updates the list of enabled sources to include or exclude
- * the web search provider depending on the state of the
- * {@link Settings.System#SHOW_WEB_SUGGESTIONS} setting.
- */
- private class ShowWebSuggestionsSettingChangeObserver extends ContentObserver {
- public ShowWebSuggestionsSettingChangeObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- scheduleUpdateSources();
- }
- }
-}
diff --git a/src/com/android/quicksearchbox/SuggestionCursor.java b/src/com/android/quicksearchbox/SuggestionCursor.java
index 9c8740e..27912b7 100644
--- a/src/com/android/quicksearchbox/SuggestionCursor.java
+++ b/src/com/android/quicksearchbox/SuggestionCursor.java
@@ -16,85 +16,15 @@
package com.android.quicksearchbox;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
+
+/**
+ * A sequence of suggestions, with a current position.
+ */
public interface SuggestionCursor {
/**
- * The user query that returned these suggestions.
- */
- String getUserQuery();
-
- /**
- * Gets the component name of the source that produced this result.
- */
- ComponentName getSourceComponentName();
-
- /**
- * Gets the string that will be logged for this suggestion when logging
- * suggestion clicks etc.
- */
- String getLogName();
-
- /**
- * Gets the localized, human-readable label for the source that produced this result.
- */
- CharSequence getSourceLabel();
-
- /**
- * Gets the icon for the source that produced this result.
- */
- Drawable getSourceIcon();
-
- /**
- * Gets the icon URI for the source that produced this result.
- */
- Uri getSourceIconUri();
-
- /**
- * Gets an icon by ID. Used for getting the icons returned by {@link #getSuggestionIcon1()}
- * and {@link #getSuggestionIcon2()}.
- */
- Drawable getIcon(String iconId);
-
- /**
- * Gets the URI for an icon.
- */
- Uri getIconUri(String iconId);
-
- /**
- * Checks whether this result represents a failed suggestion query.
- */
- boolean isFailed();
-
- /**
- * Closes this suggestion result.
- */
- void close();
-
- /**
- * Register an observer that is called when changes happen to the contents
- * of this cursor's data set.
- *
- * @param observer the object that gets notified when the data set changes.
- */
- public void registerDataSetObserver(DataSetObserver observer);
-
- /**
- * Unregister an observer that has previously been registered with this cursor
- * via {@link #registerDataSetObserver(DataSetObserver)}
- *
- * @param observer the object to unregister.
- */
- public void unregisterDataSetObserver(DataSetObserver observer);
-
- /**
* Gets the number of suggestions in this result.
*
* @return The number of suggestions, or {@code 0} if this result represents a failed query.
@@ -115,9 +45,34 @@
int getPosition();
/**
- * Gets the text to put in the search box when the current suggestion is selected.
+ * Frees any resources used by this cursor.
*/
- String getSuggestionDisplayQuery();
+ 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);
+
+ /**
+ * Gets the source that produced the current suggestion.
+ */
+ Source getSuggestionSource();
+
+ /**
+ * Gets the query that the user typed to get this suggestion.
+ */
+ String getUserQuery();
/**
* Gets the shortcut ID of the current suggestion.
@@ -150,27 +105,21 @@
/**
* Gets the left-hand-side icon for the current suggestion.
*
- * @return A string that can be passed to {@link #getIcon()}.
+ * @return A string that can be passed to {@link Source#getIcon(String)}.
*/
String getSuggestionIcon1();
/**
* Gets the right-hand-side icon for the current suggestion.
*
- * @return A string that can be passed to {@link #getIcon()}.
+ * @return A string that can be passed to {@link Source#getIcon(String)}.
*/
String getSuggestionIcon2();
/**
- * Gets the intent that this suggestion launches.
- *
- * @param context Used for resolving the intent target.
- * @param actionKey
- * @param actionMsg
- * @return
+ * Gets the intent action for the current suggestion.
*/
- Intent getSuggestionIntent(Context context, Bundle appSearchData,
- int actionKey, String actionMsg);
+ String getSuggestionIntentAction();
/**
* Gets the extra data associated with this suggestion's intent.
@@ -183,6 +132,13 @@
String getSuggestionIntentDataString();
/**
+ * Gets the data associated with this suggestion's intent.
+ */
+ String getSuggestionQuery();
+
+ String getSuggestionDisplayQuery();
+
+ /**
* Gets a unique key that identifies this suggestion. This is used to avoid
* duplicate suggestions in the promoted list. This key should be based on
* the intent of the suggestion.
@@ -190,30 +146,7 @@
String getSuggestionKey();
/**
- * Gets the first suggestion text line as styled text.
+ * Gets the suggestion log type for the current suggestion.
*/
- CharSequence getSuggestionFormattedText1();
-
- /**
- * Gets the second suggestion text line as styled text.
- */
- CharSequence getSuggestionFormattedText2();
-
- /**
- * Gets the first suggestion icon.
- */
- Drawable getSuggestionDrawableIcon1();
-
- /**
- * Gets the second suggestion icon.
- */
- Drawable getSuggestionDrawableIcon2();
-
- /**
- * Gets the action message for a key code for the current suggestion.
- *
- * @param keyCode Key code, see {@link android.view.KeyEvent}.
- * @return The action message for the key, or {@code null} if there is none.
- */
- String getActionKeyMsg(int keyCode);
+ String getSuggestionLogType();
}
diff --git a/src/com/android/quicksearchbox/SuggestionCursorWrapper.java b/src/com/android/quicksearchbox/SuggestionCursorWrapper.java
new file mode 100644
index 0000000..8abeca0
--- /dev/null
+++ b/src/com/android/quicksearchbox/SuggestionCursorWrapper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.database.DataSetObserver;
+
+/**
+ * A suggestion cursor that delegates all methods to another SuggestionCursor.
+ */
+public class SuggestionCursorWrapper extends AbstractSuggestionCursorWrapper {
+
+ private final SuggestionCursor mCursor;
+
+ public SuggestionCursorWrapper(String userQuery, SuggestionCursor cursor) {
+ super(userQuery);
+ mCursor = cursor;
+ }
+
+ public void close() {
+ if (mCursor != null) {
+ mCursor.close();
+ }
+ }
+
+ public int getCount() {
+ return mCursor == null ? 0 : mCursor.getCount();
+ }
+
+ public int getPosition() {
+ return mCursor == null ? 0 : mCursor.getPosition();
+ }
+
+ public void moveTo(int pos) {
+ if (mCursor != null) {
+ mCursor.moveTo(pos);
+ }
+ }
+
+ public void registerDataSetObserver(DataSetObserver observer) {
+ if (mCursor != null) {
+ mCursor.registerDataSetObserver(observer);
+ }
+ }
+
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ if (mCursor != null) {
+ mCursor.unregisterDataSetObserver(observer);
+ }
+ }
+
+ @Override
+ protected SuggestionCursor current() {
+ return mCursor;
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/SuggestionPosition.java b/src/com/android/quicksearchbox/SuggestionPosition.java
index 44e56a5..3f514b2 100644
--- a/src/com/android/quicksearchbox/SuggestionPosition.java
+++ b/src/com/android/quicksearchbox/SuggestionPosition.java
@@ -39,7 +39,7 @@
/**
* Gets the suggestion cursor, moved to point to the right suggestion.
*/
- public SuggestionCursor getSuggestion() {
+ protected SuggestionCursor current() {
mCursor.moveTo(mPosition);
return mCursor;
}
diff --git a/src/com/android/quicksearchbox/Suggestions.java b/src/com/android/quicksearchbox/Suggestions.java
index 535eeb9..5fa4759 100644
--- a/src/com/android/quicksearchbox/Suggestions.java
+++ b/src/com/android/quicksearchbox/Suggestions.java
@@ -16,7 +16,6 @@
package com.android.quicksearchbox;
-import android.content.ComponentName;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.util.Log;
@@ -37,7 +36,7 @@
private final String mQuery;
/** The number of sources that are expected to report. */
- private final int mExpectedSourceCount;
+ private final int mExpectedCorpusCount;
/**
* The observers that want notifications of changes to the published suggestions.
@@ -50,13 +49,13 @@
* in the order that they were published.
* This object may only be accessed on the UI thread.
* */
- private final ArrayList<SuggestionCursor> mSourceResults;
+ private final ArrayList<CorpusResult> mCorpusResults;
/**
* All {@link SuggestionCursor} objects that have been published so far.
* This object may only be accessed on the UI thread.
* */
- private final HashMap<ComponentName,SuggestionCursor> mSourceResultsBySource;
+ private final HashMap<Corpus,CorpusResult> mResultsByCorpus;
private SuggestionCursor mShortcuts;
@@ -72,16 +71,16 @@
/**
* Creates a new empty Suggestions.
*
- * @param expectedSourceCount The number of sources that are expected to report.
+ * @param expectedCorpusCount The number of sources that are expected to report.
*/
public Suggestions(Promoter promoter, int maxPromoted,
- String query, int expectedSourceCount) {
+ String query, int expectedCorpusCount) {
mPromoter = promoter;
mMaxPromoted = maxPromoted;
mQuery = query;
- mExpectedSourceCount = expectedSourceCount;
- mSourceResults = new ArrayList<SuggestionCursor>(mExpectedSourceCount);
- mSourceResultsBySource = new HashMap<ComponentName,SuggestionCursor>(mExpectedSourceCount);
+ mExpectedCorpusCount = expectedCorpusCount;
+ mCorpusResults = new ArrayList<CorpusResult>(mExpectedCorpusCount);
+ mResultsByCorpus = new HashMap<Corpus,CorpusResult>(mExpectedCorpusCount);
mPromoted = null; // will be set by updatePromoted()
}
@@ -93,7 +92,7 @@
* Gets the number of sources that are expected to report.
*/
public int getExpectedSourceCount() {
- return mExpectedSourceCount;
+ return mExpectedCorpusCount;
}
/**
@@ -143,11 +142,11 @@
mShortcuts.close();
mShortcuts = null;
}
- for (SuggestionCursor result : mSourceResults) {
+ for (CorpusResult result : mCorpusResults) {
result.close();
}
- mSourceResults.clear();
- mSourceResultsBySource.clear();
+ mCorpusResults.clear();
+ mResultsByCorpus.clear();
}
public boolean isClosed() {
@@ -168,7 +167,7 @@
*/
public boolean isDone() {
// TODO: Handle early completion because we have all the results we want.
- return mSourceResults.size() >= mExpectedSourceCount;
+ return mCorpusResults.size() >= mExpectedCorpusCount;
}
/**
@@ -186,22 +185,20 @@
}
/**
- * Adds a source result. Must be called on the UI thread, or before this
+ * Adds a corpus result. Must be called on the UI thread, or before this
* object is seen by the UI thread.
- *
- * @param sourceResult The source result.
*/
- public void addSourceResult(SuggestionCursor sourceResult) {
+ public void addCorpusResult(CorpusResult corpusResult) {
if (mClosed) {
- sourceResult.close();
+ corpusResult.close();
return;
}
- if (!mQuery.equals(sourceResult.getUserQuery())) {
+ if (!mQuery.equals(corpusResult.getUserQuery())) {
throw new IllegalArgumentException("Got result for wrong query: "
- + mQuery + " != " + sourceResult.getUserQuery());
+ + mQuery + " != " + corpusResult.getUserQuery());
}
- mSourceResults.add(sourceResult);
- mSourceResultsBySource.put(sourceResult.getSourceComponentName(), sourceResult);
+ mCorpusResults.add(corpusResult);
+ mResultsByCorpus.put(corpusResult.getCorpus(), corpusResult);
mPromoted = null;
notifyDataSetChanged();
}
@@ -211,40 +208,22 @@
if (mPromoter == null) {
return;
}
- mPromoter.pickPromoted(mShortcuts, mSourceResults, mMaxPromoted, mPromoted);
+ mPromoter.pickPromoted(mShortcuts, mCorpusResults, mMaxPromoted, mPromoted);
}
/**
- * Gets a given source result.
+ * Gets a given corpus result.
* Must be called on the UI thread, or before this object is seen by the UI thread.
*
- * @param sourcePos
- * @return The source result at the given position.
- * @throws IndexOutOfBoundsException If {@code sourcePos < 0} or
- * {@code sourcePos >= getSourceCount()}.
- */
- public SuggestionCursor getSourceResult(int sourcePos) {
- if (mClosed) {
- throw new IllegalStateException("Called getSourceResult(" + sourcePos
- + ") when closed.");
- }
- return mSourceResults.get(sourcePos);
- }
-
- /**
- * Gets a given source result.
- * Must be called on the UI thread, or before this object is seen by the UI thread.
- *
- * @param source Source name.
+ * @param corpus corpus
* @return The source result for the given source. {@code null} if the source has not
* yet returned.
*/
- public SuggestionCursor getSourceResult(ComponentName source) {
+ public CorpusResult getCorpusResult(Corpus corpus) {
if (mClosed) {
- throw new IllegalStateException("Called getSourceResult(" + source
- + ") when closed.");
+ throw new IllegalStateException("getCorpusResult(" + corpus + ") when closed.");
}
- return mSourceResultsBySource.get(source);
+ return mResultsByCorpus.get(corpus);
}
/**
@@ -255,7 +234,7 @@
if (mClosed) {
throw new IllegalStateException("Called getSourceCount() when closed.");
}
- return mSourceResults == null ? 0 : mSourceResults.size();
+ return mCorpusResults == null ? 0 : mCorpusResults.size();
}
private class MyShortcutsObserver extends DataSetObserver {
diff --git a/src/com/android/quicksearchbox/SuggestionsProvider.java b/src/com/android/quicksearchbox/SuggestionsProvider.java
index 588b3ca..55eefd9 100644
--- a/src/com/android/quicksearchbox/SuggestionsProvider.java
+++ b/src/com/android/quicksearchbox/SuggestionsProvider.java
@@ -28,7 +28,7 @@
*/
Suggestions getSuggestions(String query);
- ArrayList<Source> getOrderedSources();
+ ArrayList<Corpus> getOrderedCorpora();
void close();
}
diff --git a/src/com/android/quicksearchbox/WebCorpus.java b/src/com/android/quicksearchbox/WebCorpus.java
new file mode 100644
index 0000000..8f72bbc
--- /dev/null
+++ b/src/com/android/quicksearchbox/WebCorpus.java
@@ -0,0 +1,211 @@
+/*
+ * 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.common.Patterns;
+
+import android.app.SearchManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.speech.RecognizerIntent;
+import android.util.Log;
+import android.webkit.URLUtil;
+
+/**
+ * The web search source.
+ */
+public class WebCorpus extends AbstractCorpus {
+
+ private static final boolean DBG = true;
+ private static final String TAG = "QSB.WebSource";
+
+ private static final String WEB_CORPUS_NAME = "web";
+
+ private final Context mContext;
+
+ private final Source mWebSearchSource;
+ private final Source mBrowserSource;
+
+ public WebCorpus(Context context, Source webSearchSource, Source browserSource) {
+ mContext = context;
+ mWebSearchSource = webSearchSource;
+ mBrowserSource = browserSource;
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ public CharSequence getLabel() {
+ return getContext().getText(R.string.corpus_label_web);
+ }
+
+ public Intent createSearchIntent(String query, Bundle appData) {
+ return createWebIntent(query, appData);
+ }
+
+ public static Intent createWebIntent(String query, Bundle appData) {
+ return Patterns.WEB_URL.matcher(query).matches()
+ ? 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);
+ }
+ // 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 url) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ url = URLUtil.guessUrl(url);
+ intent.setData(Uri.parse(url));
+ return intent;
+ }
+
+ 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;
+ }
+
+ public Drawable getCorpusIcon() {
+ return getContext().getResources().getDrawable(R.drawable.corpus_icon_web);
+ }
+
+ public Uri getCorpusIconUri() {
+ int resourceId = R.drawable.corpus_icon_web;
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(getContext().getPackageName())
+ .appendEncodedPath(String.valueOf(resourceId))
+ .build();
+ }
+
+ public String getName() {
+ return WEB_CORPUS_NAME;
+ }
+
+ public int getQueryThreshold() {
+ return 0;
+ }
+
+ public boolean queryAfterZeroResults() {
+ return true;
+ }
+
+ public boolean voiceSearchEnabled() {
+ return true;
+ }
+
+ public boolean isWebCorpus() {
+ return true;
+ }
+
+ public CharSequence getSettingsDescription() {
+ return getContext().getText(R.string.corpus_description_web);
+ }
+
+ public CorpusResult getSuggestions(String query, int queryLimit) {
+ // TODO: Should run web and browser queries in parallel
+ SuggestionCursor webCursor = null;
+ try {
+ webCursor = mWebSearchSource.getSuggestions(query, queryLimit);
+ } catch (RuntimeException ex) {
+ Log.e(TAG, "Error querying web search source", ex);
+ }
+ SuggestionCursor browserCursor = null;
+ try {
+ browserCursor = mBrowserSource.getSuggestions(query, queryLimit);
+ } catch (RuntimeException ex) {
+ Log.e(TAG, "Error querying browser search source", ex);
+ }
+
+ WebResult c = new WebResult(query, webCursor, browserCursor);
+ if (DBG) Log.d(TAG, "Returning " + c.getCount() + " suggestions");
+ return c;
+ }
+
+ private class WebResult extends ListSuggestionCursor implements CorpusResult {
+
+ private SuggestionCursor mWebCursor;
+
+ private SuggestionCursor mBrowserCursor;
+
+ public WebResult(String userQuery, SuggestionCursor webCursor,
+ SuggestionCursor browserCursor) {
+ super(userQuery);
+ mWebCursor = webCursor;
+ mBrowserCursor = browserCursor;
+
+ if (mBrowserCursor != null && mBrowserCursor.getCount() > 0) {
+ if (DBG) Log.d(TAG, "Adding browser suggestion");
+ add(new SuggestionPosition(mBrowserCursor, 0));
+ }
+
+ if (mWebCursor != null) {
+ int count = mWebCursor.getCount();
+ for (int i = 0; i < count; i++) {
+ if (DBG) Log.d(TAG, "Adding web suggestion");
+ add(new SuggestionPosition(mWebCursor, i));
+ }
+ }
+ }
+
+ public Corpus getCorpus() {
+ return WebCorpus.this;
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ if (mWebCursor != null) {
+ mWebCursor.close();
+ }
+ if (mBrowserCursor != null) {
+ mBrowserCursor.close();
+ }
+ }
+ }
+}
diff --git a/src/com/android/quicksearchbox/WebSource.java b/src/com/android/quicksearchbox/WebSource.java
deleted file mode 100644
index 71b0e40..0000000
--- a/src/com/android/quicksearchbox/WebSource.java
+++ /dev/null
@@ -1,47 +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.SearchableInfo;
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-
-/**
- * The web search source.
- */
-public class WebSource extends SearchableSource {
-
- private static final boolean DBG = true;
- private static final String TAG = "QSB.WebSource";
-
- private static final String WEB_SOURCE_LOG_NAME = "web";
-
- public WebSource(Context context, SearchableInfo searchable) throws NameNotFoundException {
- super(context, searchable);
- }
-
- @Override
- public String getLogName() {
- return WEB_SOURCE_LOG_NAME;
- }
-
- @Override
- public boolean isWebSuggestionSource() {
- return true;
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SourcesAdapter.java b/src/com/android/quicksearchbox/ui/CorporaAdapter.java
similarity index 64%
rename from src/com/android/quicksearchbox/ui/SourcesAdapter.java
rename to src/com/android/quicksearchbox/ui/CorporaAdapter.java
index 2286826..26e5dea 100644
--- a/src/com/android/quicksearchbox/ui/SourcesAdapter.java
+++ b/src/com/android/quicksearchbox/ui/CorporaAdapter.java
@@ -16,8 +16,9 @@
package com.android.quicksearchbox.ui;
-import com.android.quicksearchbox.Source;
-import com.android.quicksearchbox.SuggestionsProvider;
+import com.android.quicksearchbox.Corpora;
+import com.android.quicksearchbox.Corpus;
+import com.android.quicksearchbox.CorpusRanker;
import android.view.View;
import android.view.ViewGroup;
@@ -28,33 +29,27 @@
/**
* Adapter for showing a list of sources in the source selection activity.
*/
-public class SourcesAdapter extends BaseAdapter {
+public class CorporaAdapter extends BaseAdapter {
private final SuggestionViewFactory mViewFactory;
- private final SuggestionsProvider mProvider;
+ private ArrayList<Corpus> mRankedEnabledCorpora;
- private ArrayList<Source> mEnabledSources;
-
- public SourcesAdapter(SuggestionViewFactory viewFactory, SuggestionsProvider provider) {
+ public CorporaAdapter(SuggestionViewFactory viewFactory, Corpora corpora,
+ CorpusRanker ranker) {
mViewFactory = viewFactory;
- mProvider = provider;
- updateSources();
- }
-
- private void updateSources() {
- mEnabledSources = mProvider.getOrderedSources();
+ mRankedEnabledCorpora = ranker.rankCorpora(corpora.getEnabledCorpora());
}
public int getCount() {
- return 1 + mEnabledSources.size();
+ return 1 + mRankedEnabledCorpora.size();
}
- public Source getItem(int position) {
+ public Corpus getItem(int position) {
if (position == 0) {
return null;
} else {
- return mEnabledSources.get(position - 1);
+ return mRankedEnabledCorpora.get(position - 1);
}
}
@@ -63,17 +58,17 @@
}
public View getView(int position, View convertView, ViewGroup parent) {
- SourceView view = (SourceView) convertView;
+ CorpusView view = (CorpusView) convertView;
if (view == null) {
view = mViewFactory.createSourceView(parent);
}
- Source source = getItem(position);
- if (source == null) {
+ Corpus corpus = getItem(position);
+ if (corpus == null) {
view.setIcon(mViewFactory.getGlobalSearchIcon());
view.setLabel(mViewFactory.getGlobalSearchLabel());
} else {
- view.setIcon(source.getSourceIcon());
- view.setLabel(source.getLabel());
+ view.setIcon(corpus.getCorpusIcon());
+ view.setLabel(corpus.getLabel());
}
return view;
}
diff --git a/src/com/android/quicksearchbox/ui/CorpusIndicator.java b/src/com/android/quicksearchbox/ui/CorpusIndicator.java
new file mode 100644
index 0000000..bababe9
--- /dev/null
+++ b/src/com/android/quicksearchbox/ui/CorpusIndicator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.ui;
+
+import com.android.quicksearchbox.R;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageButton;
+
+/**
+ * Utilities for setting up the corpus indicator.
+ */
+public class CorpusIndicator {
+
+ public static final int ICON_VIEW_ID = R.id.search_source_selector_icon;
+
+ private final View mView;
+
+ private final ImageButton mIconView;
+
+ public CorpusIndicator(View view) {
+ mView = view;
+ mIconView = (ImageButton) view.findViewById(ICON_VIEW_ID);
+ }
+
+ /**
+ * Sets the icon displayed in the search source selector.
+ */
+ public void setSourceIcon(Drawable icon) {
+ mIconView.setImageDrawable(icon);
+ }
+
+ public void setVisibility(int visibility) {
+ mView.setVisibility(visibility);
+ }
+
+ public void setOnKeyListener(View.OnKeyListener listener) {
+ mIconView.setOnKeyListener(listener);
+ }
+
+ public void setOnClickListener(View.OnClickListener listener) {
+ mIconView.setOnClickListener(listener);
+ }
+
+}
diff --git a/src/com/android/quicksearchbox/ui/SourceView.java b/src/com/android/quicksearchbox/ui/CorpusView.java
similarity index 82%
rename from src/com/android/quicksearchbox/ui/SourceView.java
rename to src/com/android/quicksearchbox/ui/CorpusView.java
index e9a8128..0a291af 100644
--- a/src/com/android/quicksearchbox/ui/SourceView.java
+++ b/src/com/android/quicksearchbox/ui/CorpusView.java
@@ -27,21 +27,18 @@
/**
- * A source in the source selection list.
+ * A corpus in the corpus selection list.
*/
-public class SourceView extends RelativeLayout {
-
- private static final boolean DBG = true;
- private static final String TAG = "QSB.SourceView";
+public class CorpusView extends RelativeLayout {
private ImageView mIcon;
private TextView mLabel;
- public SourceView(Context context, AttributeSet attrs) {
+ public CorpusView(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public SourceView(Context context) {
+ public CorpusView(Context context) {
super(context);
}
diff --git a/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java b/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java
index 977b744..f7a6d1a 100644
--- a/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java
+++ b/src/com/android/quicksearchbox/ui/DefaultSuggestionView.java
@@ -17,11 +17,12 @@
package com.android.quicksearchbox.ui;
import com.android.quicksearchbox.R;
+import com.android.quicksearchbox.Source;
import com.android.quicksearchbox.SuggestionCursor;
-import com.android.quicksearchbox.SuggestionPosition;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.text.Html;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -40,15 +41,6 @@
private static final boolean DBG = false;
private static final String TAG = "QSB.SuggestionView";
- /**
- * The cursor that contains the current suggestion.
- */
- private SuggestionCursor mCursor;
- /**
- * The position within the cursor of the current suggestion.
- */
- private int mPos;
-
private TextView mText1;
private TextView mText2;
private ImageView mIcon1;
@@ -75,23 +67,12 @@
mIcon2 = (ImageView) findViewById(R.id.icon2);
}
- /**
- * Gets the suggestion that this view is showing.
- */
- public SuggestionPosition getSuggestionPosition() {
- if (mCursor == null) {
- throw new IllegalStateException("No cursor in SuggestionView");
- }
- return new SuggestionPosition(mCursor, mPos);
- }
-
public void bindAsSuggestion(SuggestionCursor suggestion) {
- mCursor = suggestion;
- mPos = suggestion.getPosition();
- CharSequence text1 = suggestion.getSuggestionFormattedText1();
- CharSequence text2 = suggestion.getSuggestionFormattedText2();
- Drawable icon1 = suggestion.getSuggestionDrawableIcon1();
- Drawable icon2 = suggestion.getSuggestionDrawableIcon2();
+ String format = suggestion.getSuggestionFormat();
+ CharSequence text1 = formatText(suggestion.getSuggestionText1(), format);
+ CharSequence text2 = formatText(suggestion.getSuggestionText2(), format);
+ Drawable icon1 = getSuggestionDrawableIcon1(suggestion);
+ Drawable icon2 = getSuggestionDrawableIcon2(suggestion);
if (DBG) {
Log.d(TAG, "bindAsSuggestion(), text1=" + text1 + ",text2=" + text2
+ ",icon1=" + icon1 + ",icon2=" + icon2);
@@ -102,6 +83,36 @@
setIcon2(icon2);
}
+ public Drawable getSuggestionDrawableIcon1(SuggestionCursor suggestion) {
+ Source source = suggestion.getSuggestionSource();
+ String icon1Id = suggestion.getSuggestionIcon1();
+ Drawable icon1 = source.getIcon(icon1Id);
+ return icon1 == null ? source.getSourceIcon() : icon1;
+ }
+
+ public Drawable getSuggestionDrawableIcon2(SuggestionCursor suggestion) {
+ Source source = suggestion.getSuggestionSource();
+ return source.getIcon(suggestion.getSuggestionIcon2());
+ }
+
+ private CharSequence formatText(String str, String format) {
+ boolean isHtml = "html".equals(format);
+ if (isHtml && looksLikeHtml(str)) {
+ return Html.fromHtml(str);
+ } else {
+ return str;
+ }
+ }
+
+ private boolean looksLikeHtml(String str) {
+ if (TextUtils.isEmpty(str)) return false;
+ for (int i = str.length() - 1; i >= 0; i--) {
+ char c = str.charAt(i);
+ if (c == '>' || c == '&') return true;
+ }
+ return false;
+ }
+
/**
* Sets the first text line.
*/
diff --git a/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.java b/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.java
index 29c01f7..161c9de 100644
--- a/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.java
+++ b/src/com/android/quicksearchbox/ui/DelayingSuggestionsAdapter.java
@@ -39,11 +39,13 @@
super(viewFactory);
}
+ @Override
public void close() {
setPendingSuggestions(null);
super.close();
}
+ @Override
public void setSuggestions(Suggestions suggestions) {
if (suggestions == null) {
super.setSuggestions(null);
@@ -66,7 +68,7 @@
*/
private boolean shouldPublish(Suggestions suggestions) {
if (suggestions.isDone()) return true;
- SuggestionCursor cursor = getSourceCursor(suggestions, getSource());
+ SuggestionCursor cursor = getCorpusCursor(suggestions, getCorpus());
return cursor != null && cursor.getCount() > 0;
}
diff --git a/src/com/android/quicksearchbox/ui/SearchSourceSelector.java b/src/com/android/quicksearchbox/ui/SearchSourceSelector.java
deleted file mode 100644
index c99b8fc..0000000
--- a/src/com/android/quicksearchbox/ui/SearchSourceSelector.java
+++ /dev/null
@@ -1,110 +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.ui;
-
-import com.android.quicksearchbox.R;
-
-import android.app.SearchManager;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageButton;
-
-import java.util.List;
-
-/**
- * Utilities for setting up the search source selector.
- *
- * They should keep the same look and feel as much as possible,
- * but only the intent details must absolutely stay in sync.
- *
- * @hide
- */
-public class SearchSourceSelector {
-
- private static final String TAG = "SearchSourceSelector";
-
- private static final String SCHEME_COMPONENT = "android.component";
-
- public static final int ICON_VIEW_ID = R.id.search_source_selector_icon;
-
- private final View mView;
-
- private final ImageButton mIconView;
-
- public SearchSourceSelector(View view) {
- mView = view;
- mIconView = (ImageButton) view.findViewById(ICON_VIEW_ID);
- }
-
- /**
- * Sets the icon displayed in the search source selector.
- */
- public void setSourceIcon(Drawable icon) {
- mIconView.setImageDrawable(icon);
- }
-
- public void setVisibility(int visibility) {
- mView.setVisibility(visibility);
- }
-
- 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;
- return new Uri.Builder()
- .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.isEmpty()) return null;
- String cls = path.get(0);
- if (TextUtils.isEmpty(pkg) || TextUtils.isEmpty(cls)) return null;
- return new ComponentName(pkg, cls);
- }
-
- public void setOnKeyListener(View.OnKeyListener listener) {
- mIconView.setOnKeyListener(listener);
- }
-
- public void setOnClickListener(View.OnClickListener listener) {
- mIconView.setOnClickListener(listener);
- }
-
-}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionClickListener.java b/src/com/android/quicksearchbox/ui/SuggestionClickListener.java
index a26beda..9cc3b10 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionClickListener.java
+++ b/src/com/android/quicksearchbox/ui/SuggestionClickListener.java
@@ -16,12 +16,20 @@
package com.android.quicksearchbox.ui;
-import com.android.quicksearchbox.SuggestionPosition;
-
/**
* Listener interface for clicks on suggestions.
*/
public interface SuggestionClickListener {
- void onSuggestionClicked(SuggestionPosition suggestion);
- boolean onSuggestionLongClicked(SuggestionPosition suggestion);
+ /**
+ * 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);
}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionSelectionListener.java b/src/com/android/quicksearchbox/ui/SuggestionSelectionListener.java
index 8b3ab67..91f6e54 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionSelectionListener.java
+++ b/src/com/android/quicksearchbox/ui/SuggestionSelectionListener.java
@@ -16,17 +16,20 @@
package com.android.quicksearchbox.ui;
-import com.android.quicksearchbox.SuggestionPosition;
/**
* Listener interface for suggestion selection.
*/
public interface SuggestionSelectionListener {
/**
- * Called when the suggestion selection changes.
+ * Called when a suggestion is selected
*
- * @param suggestion The new selected suggestion, or {@code null} if
- * no suggestion is now selected.
+ * @param position Position of the new selected suggestion.
*/
- void onSelectionChanged(SuggestionPosition suggestion);
+ void onSuggestionSelected(int position);
+
+ /**
+ * Called when the selection changed so that no suggestion is selected.
+ */
+ void onNothingSelected();
}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionView.java b/src/com/android/quicksearchbox/ui/SuggestionView.java
index 8ab3d08..e2a114a 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionView.java
+++ b/src/com/android/quicksearchbox/ui/SuggestionView.java
@@ -17,7 +17,6 @@
package com.android.quicksearchbox.ui;
import com.android.quicksearchbox.SuggestionCursor;
-import com.android.quicksearchbox.SuggestionPosition;
/**
* Interface to be implemented by any view appearing in the list of suggestions.
@@ -28,8 +27,4 @@
*/
void bindAsSuggestion(SuggestionCursor suggestion);
- /**
- * Gets the SuggestionPosition associated with this view.
- */
- SuggestionPosition getSuggestionPosition();
}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionViewFactory.java b/src/com/android/quicksearchbox/ui/SuggestionViewFactory.java
index 5772236..9fc538f 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionViewFactory.java
+++ b/src/com/android/quicksearchbox/ui/SuggestionViewFactory.java
@@ -46,7 +46,7 @@
*/
SuggestionView getSuggestionView(int viewType, View convertView, ViewGroup parentViewType);
- SourceView createSourceView(ViewGroup parentViewType);
+ CorpusView createSourceView(ViewGroup parentViewType);
String getGlobalSearchLabel();
diff --git a/src/com/android/quicksearchbox/ui/SuggestionViewInflater.java b/src/com/android/quicksearchbox/ui/SuggestionViewInflater.java
index cb86464..e986674 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionViewInflater.java
+++ b/src/com/android/quicksearchbox/ui/SuggestionViewInflater.java
@@ -37,7 +37,7 @@
private static final String TAG = "QSB.SuggestionViewInflater";
// The suggestion view classes that may be returned by this factory.
- private static final Class[] SUGGESTION_VIEW_CLASSES = {
+ private static final Class<?>[] SUGGESTION_VIEW_CLASSES = {
DefaultSuggestionView.class,
ContactSuggestionView.class,
};
@@ -79,26 +79,26 @@
return (SuggestionView) convertView;
}
- public SourceView createSourceView(ViewGroup parentViewType) {
+ public CorpusView createSourceView(ViewGroup parentViewType) {
if (DBG) Log.d(TAG, "createSourceView()");
- SourceView view = (SourceView)
- getInflater().inflate(R.layout.source_list_item, parentViewType, false);
+ CorpusView view = (CorpusView)
+ getInflater().inflate(R.layout.corpus_grid_item, parentViewType, false);
return view;
}
public String getGlobalSearchLabel() {
- return mContext.getString(R.string.global_search_label);
+ return mContext.getString(R.string.corpus_label_global);
}
public Drawable getGlobalSearchIcon() {
- return mContext.getResources().getDrawable(R.drawable.global_search_source);
+ return mContext.getResources().getDrawable(R.drawable.corpus_icon_global);
}
public Uri getGlobalSearchIconUri() {
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(mContext.getPackageName())
- .appendEncodedPath(String.valueOf(R.drawable.global_search_source))
+ .appendEncodedPath(String.valueOf(R.drawable.corpus_icon_global))
.build();
}
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsAdapter.java b/src/com/android/quicksearchbox/ui/SuggestionsAdapter.java
index 6b37cf9..e9af263 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionsAdapter.java
+++ b/src/com/android/quicksearchbox/ui/SuggestionsAdapter.java
@@ -16,11 +16,11 @@
package com.android.quicksearchbox.ui;
+import com.android.quicksearchbox.Corpus;
import com.android.quicksearchbox.SuggestionCursor;
import com.android.quicksearchbox.SuggestionPosition;
import com.android.quicksearchbox.Suggestions;
-import android.content.ComponentName;
import android.database.DataSetObserver;
import android.util.Log;
import android.view.View;
@@ -41,7 +41,7 @@
private SuggestionCursor mCursor;
- private ComponentName mSource = null;
+ private Corpus mCorpus = null;
private Suggestions mSuggestions;
@@ -57,7 +57,7 @@
public void close() {
setSuggestions(null);
- mSource = null;
+ mCorpus = null;
mClosed = true;
}
@@ -93,18 +93,15 @@
/**
* Gets the source whose results are displayed.
*/
- public ComponentName getSource() {
- return mSource;
+ public Corpus getCorpus() {
+ return mCorpus;
}
/**
* Sets the source whose results are displayed.
- *
- * @param source The name of a source, or {@code null} to show
- * the promoted results.
*/
- public void setSource(ComponentName source) {
- mSource = source;
+ public void setCorpus(Corpus corpus) {
+ mCorpus = corpus;
onSuggestionsChanged();
}
@@ -121,10 +118,12 @@
return position;
}
+ @Override
public int getViewTypeCount() {
return mViewFactory.getSuggestionViewTypeCount();
}
+ @Override
public int getItemViewType(int position) {
if (mCursor == null) {
return 0;
@@ -146,7 +145,7 @@
protected void onSuggestionsChanged() {
if (DBG) Log.d(TAG, "onSuggestionsChanged(), mSuggestions=" + mSuggestions);
- SuggestionCursor cursor = getSourceCursor(mSuggestions, mSource);
+ SuggestionCursor cursor = getCorpusCursor(mSuggestions, mCorpus);
changeCursor(cursor);
}
@@ -161,10 +160,10 @@
/**
* Gets the cursor for the given source.
*/
- protected SuggestionCursor getSourceCursor(Suggestions suggestions, ComponentName source) {
+ protected SuggestionCursor getCorpusCursor(Suggestions suggestions, Corpus corpus) {
if (suggestions == null) return null;
- if (source == null) return suggestions.getPromoted();
- return suggestions.getSourceResult(source);
+ if (corpus == null) return suggestions.getPromoted();
+ return suggestions.getCorpusResult(corpus);
}
/**
diff --git a/src/com/android/quicksearchbox/ui/SuggestionsView.java b/src/com/android/quicksearchbox/ui/SuggestionsView.java
index ac0db8b..dad393e 100644
--- a/src/com/android/quicksearchbox/ui/SuggestionsView.java
+++ b/src/com/android/quicksearchbox/ui/SuggestionsView.java
@@ -101,9 +101,8 @@
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (DBG) Log.d(TAG, "onItemClick(" + position + ")");
SuggestionView suggestionView = (SuggestionView) view;
- SuggestionPosition suggestion = suggestionView.getSuggestionPosition();
if (mSuggestionClickListener != null) {
- mSuggestionClickListener.onSuggestionClicked(suggestion);
+ mSuggestionClickListener.onSuggestionClicked(position);
}
}
}
@@ -112,9 +111,8 @@
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (DBG) Log.d(TAG, "onItemLongClick(" + position + ")");
SuggestionView suggestionView = (SuggestionView) view;
- SuggestionPosition suggestion = suggestionView.getSuggestionPosition();
if (mSuggestionClickListener != null) {
- return mSuggestionClickListener.onSuggestionLongClicked(suggestion);
+ return mSuggestionClickListener.onSuggestionLongClicked(position);
}
return false;
}
@@ -124,16 +122,15 @@
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (DBG) Log.d(TAG, "onItemSelected(" + position + ")");
SuggestionView suggestionView = (SuggestionView) view;
- SuggestionPosition suggestion = suggestionView.getSuggestionPosition();
if (mSuggestionSelectionListener != null) {
- mSuggestionSelectionListener.onSelectionChanged(suggestion);
+ mSuggestionSelectionListener.onSuggestionSelected(position);
}
}
public void onNothingSelected(AdapterView<?> parent) {
if (DBG) Log.d(TAG, "onNothingSelected()");
if (mSuggestionSelectionListener != null) {
- mSuggestionSelectionListener.onSelectionChanged(null);
+ mSuggestionSelectionListener.onNothingSelected();
}
}
}