When the Search Manager requests suggestions from your content provider, it calls your content provider's query()
method. You must implement this method to search your suggestion data and return a Cursor
pointing to the suggestions you deem relevant.
Here's a summary of the parameters that the Search Manager passes to your query()
method (listed in order):
uri
Uri
, formatted as:
content://your.authority/optional.suggest.path/SUGGEST_URI_PATH_QUERY
The default behavior is for Search Manager to pass this URI and append it with the query text. For example:
content://your.authority/optional.suggest.path/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 optional.suggest.path
portion is only included in the URI if you have set such a path in your searchable configuration file with the 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: 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
android:searchSuggestSelection
attribute of your searchable configuration file, or null if you have not declared the
android:searchSuggestSelection
attribute. More about using this to
get the query below.
selectionArgs
android:searchSuggestSelection
attribute in your searchable configuration. If you have not declared
android:searchSuggestSelection
, then this parameter is null. More about using this to
get the query below.
sortOrder
The Search Manager 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 uri
parameter. However, if you include a selection value in your searchable configuration's android:searchSuggestSelection
attribute, then the query text is instead passed as the first element of the selectionArgs
string array. Both options are summarized next.
By default, the query is appended as the last segment of the uri
parameter (a Uri
object). To retrieve the query text in this case, simply use getLastPathSegment()
. For example:
String query = uri.getLastPathSegment().toLowerCase();
This returns the last segment of the Uri
, which is the query text entered in the search dialog.
Instead of using the URI, you might decide it makes more sense for your query()
method to receive everything it needs to perform the look-up and you want the selection
and selectionArgs
parameters to carry the appropriate values. In such a case, add the 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 Search Manager calls query()
with the selection string as the selection
parameter and the search query as the first element in the selectionArgs
array.
For example, here's how you might form the android:searchSuggestSelection
attribute to create a full-text search statement:
xml version="1.0" encoding="utf-8"?>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 ?">
With this configuration, your query()
method delivers the selection
parameter as "word MATCH ?" and the selectionArgs
parameter as whatever the user entered in the search dialog. When you pass these to an SQLite 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 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 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 android:searchSuggestSelection
attribute, but would still like to receive the query text in the selectionArgs
parameter, simply provide a non-null value for the android:searchSuggestSelection
attribute. This triggers the query to be passed in selectionArgs
and you can ignore the 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 Search Manager, 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 MatrixCursor
using the required column names and then add a row for each suggestion using addRow(Object[])
. Return the final product from your Content Provider's query()
method.
When you return suggestions to the Search Manager with a Cursor
, the Search Manager 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 Cursor
. The Search Manager understands several columns, but only two are required:
_ID
SUGGEST_COLUMN_TEXT_1
The following columns are all optional (and most are discussed further in the following sections):
SUGGEST_COLUMN_TEXT_2
SUGGEST_COLUMN_ICON_1
SUGGEST_COLUMN_ICON_2
SUGGEST_COLUMN_INTENT_ACTION
android:searchSuggestIntentAction
field in your searchable configuration. If your action is the same for all suggestions, it is more efficient to specify the action using
android:searchSuggestIntentAction
and omit this column.
SUGGEST_COLUMN_INTENT_DATA
android:searchSuggestIntentData
field in your searchable configuration. If neither source is provided, the Intent's data field is null. If your data is the same for all suggestions, or can be described using a constant part and a specific ID, it is more efficient to specify it using
android:searchSuggestIntentData
and omit this column.
SUGGEST_COLUMN_INTENT_DATA_ID
android:searchSuggestIntentData
attribute in the searchable configuration has already been set to an appropriate base string.
SUGGEST_COLUMN_INTENT_EXTRA_DATA
EXTRA_DATA_KEY
key.
SUGGEST_COLUMN_QUERY
QUERY
key. Required if suggestion's action is
ACTION_SEARCH
, optional otherwise.
SUGGEST_COLUMN_SHORTCUT_ID
SUGGEST_NEVER_MAKE_SHORTCUT
, the result is not stored as a shortcut. Otherwise, the shortcut ID is used to check back for an up to date suggestion using
SUGGEST_URI_PATH_SHORTCUT
.
SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING
SUGGEST_COLUMN_ICON_2
while the shortcut of this suggestion is being refreshed in Quick Search Box.
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, the Search Manager sends a custom 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 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:
android:searchSuggestIntentAction
attribute of your searchable configuration file to define the action for all suggestions. For example:
xml version="1.0" encoding="utf-8"?>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" >
SUGGEST_COLUMN_INTENT_ACTION
column to define the action for individual suggestions. Add the SUGGEST_COLUMN_INTENT_ACTION
column to your suggestions table and, for each suggestion, place in it the action to use (such as "android.Intent.action.VIEW"
).
You can also combine these two techniques. For instance, you can include the 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 SUGGEST_COLUMN_INTENT_ACTION
column. If you do not include a value in the SUGGEST_COLUMN_INTENT_ACTION
column, then the Intent provided in the android:searchSuggestIntentAction
attribute is used.
Note: If you do not include the android:searchSuggestIntentAction
attribute in your searchable configuration, then you must include a value in the 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 getData()
or getDataString()
.
You can define the data included with the Intent in two ways:
SUGGEST_COLUMN_INTENT_DATA
column of your suggestions table. Provide all necessary data information for each Intent in the suggestions table by including the 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 getData()
or 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 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 SQLiteQueryBuilder
creates a projection map of column names to aliases.
android:searchSuggestIntentData
attribute of the searchable configuration and the SUGGEST_COLUMN_INTENT_DATA_ID
column of your suggestions table, respectively. Declare the piece of the URI that is common to all suggestions in the android:searchSuggestIntentData
attribute of your searchable configuration. For example:
xml version="1.0" encoding="utf-8"?>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" >
Then include the final path for each suggestion (the unique part) in the SUGGEST_COLUMN_INTENT_DATA_ID
column of your suggestions table. When the user selects a suggestion, the Search Manager takes the string from android:searchSuggestIntentData
, appends a slash ("/") and then adds the respective value from the SUGGEST_COLUMN_INTENT_DATA_ID
column to form a complete content URI. You can then retrieve the Uri
with with getData()
.
If you need to express even more information with your Intent, you can add another table column, SUGGEST_COLUMN_INTENT_EXTRA_DATA
, which can store additional information about the suggestion. The data saved in this column is placed in EXTRA_DATA_KEY
of the Intent's extra Bundle.
Now that your search dialog provides 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 ACTION_SEARCH
Intent, which your searchable Activity already does. Here's an example of how you can handle the Intents during your Activity 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 ACTION_VIEW
and the data carries a complete URI pointing to the suggested item, as synthesized by the android:searchSuggestIntentData
string and SUGGEST_COLUMN_INTENT_DATA_ID
column. The URI is then passed to the local 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 android:searchSuggestIntentAction
attribute or SUGGEST_COLUMN_INTENT_ACTION
column. The Search Manager 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 (trackball or d-pad), the text in the search dialog won't change, 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:
android:searchMode
attribute to your searchable configuration with the "queryRewriteFromText" value. In this case, the content from the suggestion's SUGGEST_COLUMN_TEXT_1
column is used to rewrite the query text. android:searchMode
attribute to your searchable configuration with the "queryRewriteFromData" value. In this case, the content from the suggestion's SUGGEST_COLUMN_INTENT_DATA
column is used to rewrite the query text. This should only be used with URI's or other data formats that are intended to be user-visible, such as HTTP URLs. Internal URI schemes should not be used to rewrite the query in this way. SUGGEST_COLUMN_QUERY
column of your suggestions table. If this column is present and contains a value for the current suggestion, it is used to rewrite the query text (and override either of the previous implementations). 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 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
element for the provider to grant Quick Search Box read access to your content provider. For example:
android:name="MySuggestionProvider"
android:authorities="com.example.MyCustomSuggestionProvider"
android:readPermission="com.example.provider.READ_MY_DATA"
android:writePermission="com.example.provider.WRITE_MY_DATA">
android:pathPrefix="/search_suggest_query"
android:readPermission="android.permission.GLOBAL_SEARCH" />
In this example, the provider restricts read and write access to the content. The
element amends the restriction by granting read access to content inside the "/search_suggest_query"
path prefix when the "android.permission.GLOBAL_SEARCH"
permission exists. This grants access to Quick Search Box so that it may query your content provider for suggestions.
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 android:searchSettingsDescription
attribute to your searchable configuration. For example:
xml version="1.0" encoding="utf-8"?>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" >
The string for 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 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.