page.title=Adding Custom Suggestions page.tags=SearchRecentSuggestionsProvider, @jd:body
When using the Android search dialog or search widget, you can provide custom search suggestions that are created from data in your application. For example, if your application is a word dictionary, you can suggest words from the dictionary that match the text entered so far. These are the most valuable suggestions, because you can effectively predict what the user wants and provide instant access to it. Figure 1 shows an example of a search dialog with custom suggestions.
Once you provide custom suggestions, you can also make them available to the system-wide Quick Search Box, providing access to your content from outside your application.
Before you begin with this guide to add custom suggestions, you need to have implemented the Android search dialog or a search widget for searches in your application. If you haven't, see Creating a Search Interface.
When the user selects a custom suggestion, the Android system sends an {@link android.content.Intent} to your searchable activity. Whereas a normal search query sends an intent with the {@link android.content.Intent#ACTION_SEARCH} action, you can instead define your custom suggestions to use {@link android.content.Intent#ACTION_VIEW} (or any other intent action), and also include data that's relevant to the selected suggestion. Continuing the dictionary example, when the user selects a suggestion, your application can immediately open the definition for that word, instead of searching the dictionary for matches.
To provide custom suggestions, do the following:
Just as the Android system displays the search dialog, it also displays your search suggestions. All you need is a content provider from which the system can retrieve your suggestions. If you're not familiar with creating content providers, read the Content Providers developer guide before you continue.
When the system identifies that your activity is searchable and provides search suggestions, the following procedure takes place when the user types a query:
Once the custom suggestions are displayed, the following might happen:
To add support for custom suggestions, add the {@code android:searchSuggestAuthority} attribute
to the {@code
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider"> </searchable>
You might need some additional attributes, depending on the type of intent you attach to each suggestion and how you want to format queries to your content provider. The other optional attributes are discussed in the following sections.
Creating a content provider for custom suggestions requires previous knowledge about content providers that's covered in the Content Provider developer guide. For the most part, a content provider for custom suggestions is the same as any other content provider. However, for each suggestion you provide, the respective row in the {@link android.database.Cursor} must include specific columns that the system understands and uses to format the suggestions.
When the user starts typing into the search dialog or search widget, the system queries your content provider for suggestions by calling {@link android.content.ContentProvider#query(Uri,String[],String,String[],String) query()} each time a letter is typed. In your implementation of {@link android.content.ContentProvider#query(Uri,String[],String,String[],String) query()}, your content provider must search your suggestion data and return a {@link android.database.Cursor} that points to the rows you have determined to be good suggestions.
Details about creating a content provider for custom suggestions are discussed in the following two sections:
When the system requests suggestions from your content provider, it calls your content provider's {@link android.content.ContentProvider#query(Uri,String[],String,String[],String) query()} method. You must implement this method to search your suggestion data and return a {@link android.database.Cursor} pointing to the suggestions you deem relevant.
Here's a summary of the parameters that the system passes to your {@link android.content.ContentProvider#query(Uri,String[],String,String[],String) query()} method (listed in order):
uri
content://your.authority/optional.suggest.path/{@link android.app.SearchManager#SUGGEST_URI_PATH_QUERY}
The default behavior is for system to pass this URI and append it with the query text. For example:
content://your.authority/optional.suggest.path/{@link android.app.SearchManager#SUGGEST_URI_PATH_QUERY}/puppies
The query text on the end is encoded using URI encoding rules, so you might need to decode it before performing a search.
The {@code optional.suggest.path} portion is only included in the URI if you have set such a path in your searchable configuration file with the {@code android:searchSuggestPath} attribute. This is only needed if you use the same content provider for multiple searchable activities, in which case, you need to disambiguate the source of the suggestion query.
Note: {@link android.app.SearchManager#SUGGEST_URI_PATH_QUERY} is not the literal string provided in the URI, but a constant that you should use if you need to refer to this path.
projection
selection
selectionArgs
sortOrder
The system can send you the search query text in two ways. The default manner is for the query text to be included as the last path of the content URI passed in the {@code uri} parameter. However, if you include a selection value in your searchable configuration's {@code android:searchSuggestSelection} attribute, then the query text is instead passed as the first element of the {@code selectionArgs} string array. Both options are summarized next.
By default, the query is appended as the last segment of the {@code uri} parameter (a {@link android.net.Uri} object). To retrieve the query text in this case, simply use {@link android.net.Uri#getLastPathSegment()}. For example:
String query = uri.getLastPathSegment().toLowerCase();
This returns the last segment of the {@link android.net.Uri}, which is the query text entered by the user.
Instead of using the URI, you might decide it makes more sense for your {@link android.content.ContentProvider#query(Uri,String[],String,String[],String) query()} method to receive everything it needs to perform the look-up and you want the {@code selection} and {@code selectionArgs} parameters to carry the appropriate values. In such a case, add the {@code android:searchSuggestSelection} attribute to your searchable configuration with your SQLite selection string. In the selection string, include a question mark ("?") as a placeholder for the actual search query. The system calls {@link android.content.ContentProvider#query(Uri,String[],String,String[],String) query()} with the selection string as the {@code selection} parameter and the search query as the first element in the {@code selectionArgs} array.
For example, here's how you might form the {@code android:searchSuggestSelection} attribute to create a full-text search statement:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider" android:searchSuggestIntentAction="android.intent.action.VIEW" android:searchSuggestSelection="word MATCH ?"> </searchable>
With this configuration, your {@link android.content.ContentProvider#query(Uri,String[],String,String[],String) query()} method delivers the {@code selection} parameter as {@code "word MATCH ?"} and the {@code selectionArgs} parameter as the search query. When you pass these to an SQLite {@link android.database.sqlite.SQLiteDatabase#query(String,String[],String,String[],String,String, String) query()} method, as their respective arguments, they are synthesized together (the question mark is replaced with the query text). If you chose to receive suggestion queries this way and need to add wildcards to the query text, append (and/or prefix) them to the {@code selectionArgs} parameter, because this value is wrapped in quotes and inserted in place of the question mark.
Another new attribute in the example above is {@code android:searchSuggestIntentAction}, which defines the intent action sent with each intent when the user selects a suggestion. It is discussed further in the section about Declaring an Intent for Suggestions.
Tip: If you don't want to define a selection clause in the {@code android:searchSuggestSelection} attribute, but would still like to receive the query text in the {@code selectionArgs} parameter, simply provide a non-null value for the {@code android:searchSuggestSelection} attribute. This triggers the query to be passed in {@code selectionArgs} and you can ignore the {@code selection} parameter. In this way, you can instead define the actual selection clause at a lower level so that your content provider doesn't have to handle it.
If your search suggestions are not stored in a table format (such as an SQLite table) using the columns required by the system, then you can search your suggestion data for matches and then format them into the necessary table on each request. To do so, create a {@link android.database.MatrixCursor} using the required column names and then add a row for each suggestion using {@link android.database.MatrixCursor#addRow(Object[])}. Return the final product from your Content Provider's {@link android.content.ContentProvider#query(Uri,String[],String,String[],String) query()} method.
When you return suggestions to the system with a {@link android.database.Cursor}, the system expects specific columns in each row. So, regardless of whether you decide to store your suggestion data in an SQLite database on the device, a database on a web server, or another format on the device or web, you must format the suggestions as rows in a table and present them with a {@link android.database.Cursor}. The system understands several columns, but only two are required:
The following columns are all optional (and most are discussed further in the following sections):
Some of these columns are discussed more in the following sections.
When the user selects a suggestion from the list that appears below the search dialog or widget, the system sends a custom {@link android.content.Intent} to your searchable activity. You must define the action and data for the intent.
The most common intent action for a custom suggestion is {@link android.content.Intent#ACTION_VIEW}, which is appropriate when you want to open something, like the definition for a word, a person's contact information, or a web page. However, the intent action can be any other action and can even be different for each suggestion.
Depending on whether you want all suggestions to use the same intent action, you can define the action in two ways:
For example:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider" android:searchSuggestIntentAction="android.Intent.action.VIEW" > </searchable>
Add the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_ACTION} column to your suggestions table and, for each suggestion, place in it the action to use (such as {@code "android.Intent.action.VIEW"}).
You can also combine these two techniques. For instance, you can include the {@code android:searchSuggestIntentAction} attribute with an action to be used with all suggestions by default, then override this action for some suggestions by declaring a different action in the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_ACTION} column. If you do not include a value in the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_ACTION} column, then the intent provided in the {@code android:searchSuggestIntentAction} attribute is used.
Note: If you do not include the {@code android:searchSuggestIntentAction} attribute in your searchable configuration, then you must include a value in the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_ACTION} column for every suggestion, or the intent will fail.
When the user selects a suggestion, your searchable activity receives the intent with the action you've defined (as discussed in the previous section), but the intent must also carry data in order for your activity to identify which suggestion was selected. Specifically, the data should be something unique for each suggestion, such as the row ID for the suggestion in your SQLite table. When the intent is received, you can retrieve the attached data with {@link android.content.Intent#getData()} or {@link android.content.Intent#getDataString()}.
You can define the data included with the intent in two ways:
Provide all necessary data information for each intent in the suggestions table by including the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA} column and then populating it with unique data for each row. The data from this column is attached to the intent exactly as you define it in this column. You can then retrieve it with with {@link android.content.Intent#getData()} or {@link android.content.Intent#getDataString()}.
Tip: It's usually easiest to use the table's row ID as the Intent data, because it's always unique. And the easiest way to do that is by using the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA} column name as an alias for the row ID column. See the Searchable Dictionary sample app for an example in which {@link android.database.sqlite.SQLiteQueryBuilder} creates a projection map of column names to aliases.
Declare the piece of the URI that is common to all suggestions in the {@code android:searchSuggestIntentData} attribute of your searchable configuration. For example:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider" android:searchSuggestIntentAction="android.intent.action.VIEW" android:searchSuggestIntentData="content://com.example/datatable" > </searchable>
Then include the final path for each suggestion (the unique part) in the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID} column of your suggestions table. When the user selects a suggestion, the system takes the string from {@code android:searchSuggestIntentData}, appends a slash ("/") and then adds the respective value from the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID} column to form a complete content URI. You can then retrieve the {@link android.net.Uri} with with {@link android.content.Intent#getData()}.
If you need to express even more information with your intent, you can add another table column, {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_EXTRA_DATA}, which can store additional information about the suggestion. The data saved in this column is placed in {@link android.app.SearchManager#EXTRA_DATA_KEY} of the intent's extra Bundle.
Now that you provide custom search suggestions with custom intents, you need your searchable activity to handle these intents when the user selects a suggestion. This is in addition to handling the {@link android.content.Intent#ACTION_SEARCH} intent, which your searchable activity already does. Here's an example of how you can handle the intents during your activity {@link android.app.Activity#onCreate(Bundle) onCreate()} callback:
Intent intent = getIntent(); if (Intent.ACTION_SEARCH.equals(intent.getAction())) { // Handle the normal search query case String query = intent.getStringExtra(SearchManager.QUERY); doSearch(query); } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { // Handle a suggestions click (because the suggestions all use ACTION_VIEW) Uri data = intent.getData(); showResult(data); }
In this example, the intent action is {@link android.content.Intent#ACTION_VIEW} and the data carries a complete URI pointing to the suggested item, as synthesized by the {@code android:searchSuggestIntentData} string and {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID} column. The URI is then passed to the local {@code showResult()} method that queries the content provider for the item specified by the URI.
Note: You do not need to add an intent filter to your Android manifest file for the intent action you defined with the {@code android:searchSuggestIntentAction} attribute or {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_ACTION} column. The system opens your searchable activity by name to deliver the suggestion's intent, so the activity does not need to declare the accepted action.
If the user navigates through the suggestions list using the directional controls (such as with a trackball or d-pad), the query text does not update, by default. However, you can temporarily rewrite the user's query text as it appears in the text box with a query that matches the suggestion currently in focus. This enables the user to see what query is being suggested (if appropriate) and then select the search box and edit the query before dispatching it as a search.
You can rewrite the query text in the following ways:
Once you configure your application to provide custom search suggestions, making them available to the globally accessible Quick Search Box is as easy as modifying your searchable configuration to include {@code android:includeInGlobalSearch} as "true".
The only scenario in which additional work is necessary is when your content provider demands a
read permission. In which case, you need to add a special
{@code
<provider android:name="MySuggestionProvider" android:authorities="com.example.MyCustomSuggestionProvider" android:readPermission="com.example.provider.READ_MY_DATA" android:writePermission="com.example.provider.WRITE_MY_DATA"> <path-permission android:pathPrefix="/search_suggest_query" android:readPermission="android.permission.GLOBAL_SEARCH" /> </provider>
In this example, the provider restricts read and write access to the content. The
{@code
If your content provider does not enforce read permissions, then Quick Search Box can read it by default.
When your application is configured to provide suggestions in Quick Search Box, it is not actually enabled to provide suggestions in Quick Search Box, by default. It is the user's choice whether to include suggestions from your application in the Quick Search Box. To enable search suggestions from your application, the user must open "Searchable items" (in Settings > Search) and enable your application as a searchable item.
Each application that is available to Quick Search Box has an entry in the Searchable items settings page. The entry includes the name of the application and a short description of what content can be searched from the application and made available for suggestions in Quick Search Box. To define the description text for your searchable application, add the {@code android:searchSettingsDescription} attribute to your searchable configuration. For example:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider" android:searchSuggestIntentAction="android.intent.action.VIEW" android:includeInGlobalSearch="true" android:searchSettingsDescription="@string/search_description" > </searchable>
The string for {@code android:searchSettingsDescription} should be as concise as possible and state the content that is searchable. For example, "Artists, albums, and tracks" for a music application, or "Saved notes" for a notepad application. Providing this description is important so the user knows what kind of suggestions are provided. You should always include this attribute when {@code android:includeInGlobalSearch} is "true".
Remember that the user must visit the settings menu to enable search suggestions for your application before your search suggestions appear in Quick Search Box. As such, if search is an important aspect of your application, then you might want to consider a way to convey that to your users — you might provide a note the first time they launch the app that instructs them how to enable search suggestions for Quick Search Box.
Suggestions that the user selects from Quick Search Box can be automatically made into shortcuts. These are suggestions that the system has copied from your content provider so it can quickly access the suggestion without the need to re-query your content provider.
By default, this is enabled for all suggestions retrieved by Quick Search Box, but if your suggestion data changes over time, then you can request that the shortcuts be refreshed. For instance, if your suggestions refer to dynamic data, such as a contact's presence status, then you should request that the suggestion shortcuts be refreshed when shown to the user. To do so, include the {@link android.app.SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} in your suggestions table. Using this column, you can configure the shortcut behavior for each suggestion in one of the following ways:
Provide a value in the {@link android.app.SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} column and the suggestion is re-queried for a fresh version each time the shortcut is displayed. The shortcut is quickly displayed with whatever data was most recently available until the refresh query returns, at which point the suggestion is refreshed with the new information. The refresh query is sent to your content provider with a URI path of {@link android.app.SearchManager#SUGGEST_URI_PATH_SHORTCUT} (instead of {@link android.app.SearchManager#SUGGEST_URI_PATH_QUERY}).
The {@link android.database.Cursor} you return should contain one suggestion using the same columns as the original suggestion, or be empty, indicating that the shortcut is no longer valid (in which case, the suggestion disappears and the shortcut is removed).
If a suggestion refers to data that could take longer to refresh, such as a network-based refresh, you can also add the {@link android.app.SearchManager#SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING} column to your suggestions table with a value of "true" in order to show a progress spinner for the right hand icon until the refresh is complete. Any value other than "true" does not show the progress spinner.
Provide a value of {@link android.app.SearchManager#SUGGEST_NEVER_MAKE_SHORTCUT} in the {@link android.app.SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} column. In this case, the suggestion is never copied into a shortcut. This should only be necessary if you absolutely do not want the previously copied suggestion to appear. (Recall that if you provide a normal value for the column, then the suggestion shortcut appears only until the refresh query returns.)
Leave the {@link android.app.SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} empty for each suggestion that will not change and can be saved as a shortcut.
If none of your suggestions ever change, then you do not need the {@link android.app.SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} column at all.
Note: Quick Search Box ultimately decides whether or not to create a shortcut for a suggestion, considering these values as a strong request from your application—there is no guarantee that the behavior you have requested for your suggestion shortcuts will be honored.
Once you make your application's search suggestions available to Quick Search Box, the Quick Search Box ranking determines how the suggestions are surfaced to the user for a particular query. This might depend on how many other apps have results for that query, and how often the user has selected your results compared to those from other apps. There is no guarantee about how your suggestions are ranked, or whether your app's suggestions show at all for a given query. In general, you can expect that providing quality results increases the likelihood that your app's suggestions are provided in a prominent position and apps that provide low quality suggestions are more likely to be ranked lower or not displayed.
See the Searchable Dictionary sample app for a complete demonstration of custom search suggestions.