diff --git a/core/java/android/provider/SearchRecentSuggestions.java b/core/java/android/provider/SearchRecentSuggestions.java index 0632d9498b84d..13ad9d345a496 100644 --- a/core/java/android/provider/SearchRecentSuggestions.java +++ b/core/java/android/provider/SearchRecentSuggestions.java @@ -20,36 +20,35 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.SearchRecentSuggestionsProvider; -import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; import android.util.Log; +import java.util.concurrent.Semaphore; + /** - * This is a utility class providing access to + * This is a utility class providing access to * {@link android.content.SearchRecentSuggestionsProvider}. - * + * *
Unlike some utility classes, this one must be instantiated and properly initialized, so that * it can be configured to operate with the search suggestions provider that you have created. - * + * *
Typically, you will do this in your searchable activity, each time you receive an incoming * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} Intent. The code to record each * incoming query is as follows: *
- * SearchSuggestions suggestions = new SearchSuggestions(this, + * SearchSuggestions suggestions = new SearchSuggestions(this, * MySuggestionsProvider.AUTHORITY, MySuggestionsProvider.MODE); * suggestions.saveRecentQuery(queryString, null); *- * + * *
For a working example, see SearchSuggestionSampleProvider and SearchQueryResults in
* samples/ApiDemos/app.
*/
public class SearchRecentSuggestions {
// debugging support
private static final String LOG_TAG = "SearchSuggestions";
- // DELETE ME (eventually)
- private static final int DBG_SUGGESTION_TIMESTAMPS = 0;
-
+
// This is a superset of all possible column names (need not all be in table)
private static class SuggestionColumns implements BaseColumns {
public static final String DISPLAY1 = "display1";
@@ -57,27 +56,28 @@ public class SearchRecentSuggestions {
public static final String QUERY = "query";
public static final String DATE = "date";
}
-
+
/* if you change column order you must also change indices below */
/**
* This is the database projection that can be used to view saved queries, when
* configured for one-line operation.
*/
public static final String[] QUERIES_PROJECTION_1LINE = new String[] {
- SuggestionColumns._ID,
+ SuggestionColumns._ID,
SuggestionColumns.DATE,
- SuggestionColumns.QUERY,
+ SuggestionColumns.QUERY,
SuggestionColumns.DISPLAY1,
};
+
/* if you change column order you must also change indices below */
/**
* This is the database projection that can be used to view saved queries, when
* configured for two-line operation.
*/
public static final String[] QUERIES_PROJECTION_2LINE = new String[] {
- SuggestionColumns._ID,
+ SuggestionColumns._ID,
SuggestionColumns.DATE,
- SuggestionColumns.QUERY,
+ SuggestionColumns.QUERY,
SuggestionColumns.DISPLAY1,
SuggestionColumns.DISPLAY2,
};
@@ -91,11 +91,6 @@ public class SearchRecentSuggestions {
public static final int QUERIES_PROJECTION_DISPLAY1_INDEX = 3;
/** Index into the provided query projections. For use with Cursor.update methods. */
public static final int QUERIES_PROJECTION_DISPLAY2_INDEX = 4; // only when 2line active
-
- /* columns needed to determine whether to truncate history */
- private static final String[] TRUNCATE_HISTORY_PROJECTION = new String[] {
- SuggestionColumns._ID, SuggestionColumns.DATE
- };
/*
* Set a cap on the count of items in the suggestions table, to
@@ -103,73 +98,90 @@ public class SearchRecentSuggestions {
* cap when/if db/layout performance improvements are made.
*/
private static final int MAX_HISTORY_COUNT = 250;
-
+
// client-provided configuration values
- private Context mContext;
- private String mAuthority;
- private boolean mTwoLineDisplay;
- private Uri mSuggestionsUri;
- private String[] mQueriesProjection;
+ private final Context mContext;
+ private final String mAuthority;
+ private final boolean mTwoLineDisplay;
+ private final Uri mSuggestionsUri;
+
+ /** Released once per completion of async write. Used for tests. */
+ private static final Semaphore sWritesInProgress = new Semaphore(0);
/**
* Although provider utility classes are typically static, this one must be constructed
- * because it needs to be initialized using the same values that you provided in your
- * {@link android.content.SearchRecentSuggestionsProvider}.
- *
+ * because it needs to be initialized using the same values that you provided in your
+ * {@link android.content.SearchRecentSuggestionsProvider}.
+ *
* @param authority This must match the authority that you've declared in your manifest.
* @param mode You can use mode flags here to determine certain functional aspects of your
* database. Note, this value should not change from run to run, because when it does change,
* your suggestions database may be wiped.
- *
+ *
* @see android.content.SearchRecentSuggestionsProvider
* @see android.content.SearchRecentSuggestionsProvider#setupSuggestions
*/
public SearchRecentSuggestions(Context context, String authority, int mode) {
- if (TextUtils.isEmpty(authority) ||
+ if (TextUtils.isEmpty(authority) ||
((mode & SearchRecentSuggestionsProvider.DATABASE_MODE_QUERIES) == 0)) {
throw new IllegalArgumentException();
}
// unpack mode flags
mTwoLineDisplay = (0 != (mode & SearchRecentSuggestionsProvider.DATABASE_MODE_2LINES));
-
+
// saved values
mContext = context;
mAuthority = new String(authority);
-
+
// derived values
mSuggestionsUri = Uri.parse("content://" + mAuthority + "/suggestions");
-
- if (mTwoLineDisplay) {
- mQueriesProjection = QUERIES_PROJECTION_2LINE;
- } else {
- mQueriesProjection = QUERIES_PROJECTION_1LINE;
- }
}
/**
- * Add a query to the recent queries list.
- *
+ * Add a query to the recent queries list. Returns immediately, performing the save
+ * in the background.
+ *
* @param queryString The string as typed by the user. This string will be displayed as
* the suggestion, and if the user clicks on the suggestion, this string will be sent to your
* searchable activity (as a new search query).
- * @param line2 If you have configured your recent suggestions provider with
- * {@link android.content.SearchRecentSuggestionsProvider#DATABASE_MODE_2LINES}, you can
+ * @param line2 If you have configured your recent suggestions provider with
+ * {@link android.content.SearchRecentSuggestionsProvider#DATABASE_MODE_2LINES}, you can
* pass a second line of text here. It will be shown in a smaller font, below the primary
* suggestion. When typing, matches in either line of text will be displayed in the list.
* If you did not configure two-line mode, or if a given suggestion does not have any
* additional text to display, you can pass null here.
*/
- public void saveRecentQuery(String queryString, String line2) {
+ public void saveRecentQuery(final String queryString, final String line2) {
if (TextUtils.isEmpty(queryString)) {
return;
}
if (!mTwoLineDisplay && !TextUtils.isEmpty(line2)) {
throw new IllegalArgumentException();
}
-
+
+ new Thread("saveRecentQuery") {
+ @Override
+ public void run() {
+ saveRecentQueryBlocking(queryString, line2);
+ sWritesInProgress.release();
+ }
+ }.start();
+ }
+
+ // Visible for testing.
+ void waitForSave() {
+ // Acquire writes semaphore until there is nothing available.
+ // This is to clean up after any previous callers to saveRecentQuery
+ // who did not also call waitForSave().
+ do {
+ sWritesInProgress.acquireUninterruptibly();
+ } while (sWritesInProgress.availablePermits() > 0);
+ }
+
+ private void saveRecentQueryBlocking(String queryString, String line2) {
ContentResolver cr = mContext.getContentResolver();
long now = System.currentTimeMillis();
-
+
// Use content resolver (not cursor) to insert/update this query
try {
ContentValues values = new ContentValues();
@@ -183,14 +195,14 @@ public class SearchRecentSuggestions {
} catch (RuntimeException e) {
Log.e(LOG_TAG, "saveRecentQuery", e);
}
-
+
// Shorten the list (if it has become too long)
truncateHistory(cr, MAX_HISTORY_COUNT);
}
-
+
/**
* Completely delete the history. Use this call to implement a "clear history" UI.
- *
+ *
* Any application that implements search suggestions based on previous actions (such as
* recent queries, page/items viewed, etc.) should provide a way for the user to clear the
* history. This gives the user a measure of privacy, if they do not wish for their recent
@@ -203,7 +215,7 @@ public class SearchRecentSuggestions {
/**
* Reduces the length of the history table, to prevent it from growing too large.
- *
+ *
* @param cr Convenience copy of the content resolver.
* @param maxEntries Max entries to leave in the table. 0 means remove all entries.
*/
@@ -211,7 +223,7 @@ public class SearchRecentSuggestions {
if (maxEntries < 0) {
throw new IllegalArgumentException();
}
-
+
try {
// null means "delete all". otherwise "delete but leave n newest"
String selection = null;
diff --git a/core/tests/coretests/src/android/content/SearchRecentSuggestionsProviderTest.java b/core/tests/coretests/src/android/provider/SearchRecentSuggestionsProviderTest.java
similarity index 92%
rename from core/tests/coretests/src/android/content/SearchRecentSuggestionsProviderTest.java
rename to core/tests/coretests/src/android/provider/SearchRecentSuggestionsProviderTest.java
index a4c33f5bde44c..1512eab06c72f 100644
--- a/core/tests/coretests/src/android/content/SearchRecentSuggestionsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SearchRecentSuggestionsProviderTest.java
@@ -14,72 +14,58 @@
* limitations under the License.
*/
-package android.content;
+package android.provider;
import android.app.SearchManager;
+import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
-import android.provider.SearchRecentSuggestions;
import android.test.ProviderTestCase2;
-import android.test.suitebuilder.annotation.Suppress;
-
-/**
- * Very simple provider that I can instantiate right here.
- */
-class TestProvider extends SearchRecentSuggestionsProvider {
- final static String AUTHORITY = "android.content.TestProvider";
- final static int MODE = DATABASE_MODE_QUERIES + DATABASE_MODE_2LINES;
-
- public TestProvider() {
- super();
- setupSuggestions(AUTHORITY, MODE);
- }
-}
+import android.test.suitebuilder.annotation.MediumTest;
/**
* ProviderTestCase that performs unit tests of SearchRecentSuggestionsProvider.
- *
+ *
* You can run this test in isolation via the commands:
- *
+ *
* $ (cd tests/FrameworkTests/ && mm) && adb sync
* $ adb shell am instrument -w \
- * -e class android.content.SearchRecentSuggestionsProviderTest
+ * -e class android.provider.SearchRecentSuggestionsProviderTest
* com.android.frameworktest.tests/android.test.InstrumentationTestRunner
*/
-// Suppress these until bug http://b/issue?id=1416586 is fixed.
-@Suppress
+@MediumTest
public class SearchRecentSuggestionsProviderTest extends ProviderTestCase2