diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index 13d752a369223..dce78c4b3ecbc 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -143,6 +143,7 @@
The Android search framework provides the ability for your application to +provide suggestions while the user types into the Android search dialog. In this guide, you'll learn +how to create custom suggestions. These are suggestions based on custom data provided by 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. Once you provide +custom suggestions, you then make them available to the system-wide Quick Search Box, providing +access to your content from outside your application.
+ +Before you begin, you need to have implemented the Android search dialog for searches in your +application. If you haven't done this, see Using the Android Search +Dialog.
+ + +
+
+When the user selects a custom suggestions, the Search Manager will send a customized Intent to +your searchable Activity. Whereas a normal search query will send 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 action), and also include additional 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, you need to do the following:
+ +Just like the Search Manager handles the rendering of the search dialog, it will also do the work +to display all search suggestions below the search dialog. All you need to do is provide a source +from which the suggestions can be retrieved.
+ +Note: If you're not familiar with creating Content +Providers, please read the Content +Providers developer guide before you continue.
+ +When the Search Manager identifies that your Activity is searchable and also provides search +suggestions, the following procedure will take place as soon as the user types into the Android +search box:
+ +At this point, the following may happen:
+ +To add support for custom suggestions, add the {@code android:searchSuggestAuthority} attribute +to the {@code <searchable>} element in your searchable configuration file. 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="my.package.MyCustomSuggestionProvider" > +</searchable> ++ +
You may require 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 relevant sections below.
+ + +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 Search Manager +understands.
+ +When the user starts typing into the search dialog, the Search Manager will query 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 determine to be good suggestions.
+ +The following two sections describe how the Search Manager will send requests to your Content +Provider and how you can handle them, and define the columns that the Search Manager understands and +expects to be provided in the {@link android.database.Cursor} returned with each query.
+ + +When the Search Manager makes a request for suggestions from your content provider, it will call +{@link android.content.ContentProvider#query(Uri,String[],String,String[],String)}. You must +implement this method in your content provider so that it will search your suggestions and return a +Cursor that contains the suggestions you deem relevant.
+ +Here's a summary of the parameters that the Search Manager will pass 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 Search Manager 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 will be encoded using URI encoding rules, so you may need to decode +it.
+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 that {@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.
+projectionselectionselectionArgssortOrderAs you may have realized, there are two ways by which the Search Manager can send you the search +query text. The default manner is for the query text to be included as the last path of the content +URI that is 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 will instead be passed as the first +element of the {@code selectionArgs} string array. Both options are summarized below.
+ + +By default, the query will be 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 will return the last segment of the Uri, which is the query text entered in the search +dialog.
+ + + +Instead of using the URI, you may 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 values. In this case, you can +add the {@code android:searchSuggestSelection} attribute to your searchable configuration with your +SQLite selection string. In this selection string, you can include a question mark ("?") as +a placeholder for the actual search query. This selection string will be delivered as the +{@code selection} string parameter, and the query entered into the search dialog will be delivered +as the first element in the {@code selectionArgs} string array parameter.
+ +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="my.package.MyCustomSuggestionProvider" + android:searchSuggestIntentAction="android.Intent.action.VIEW" + android:searchSuggestSelection="word MATCH ?"> +</searchable> ++ +
When you then receive the {@code selection} and {@code selectionArgs} parameters in your {@link +android.content.ContentProvider#query(Uri,String[],String,String[],String) ContentProvider.query()} +method, they will carry the selection ("word MATCH ?") and the query text, respectively. When +these are passed to an SQLite {@link +android.database.sqlite.SQLiteDatabase#query(String,String[],String,String[],String,String, +String) query} method, they will be synthesized together (replacing the question mark with the query +text, wrapped in single-quotes). Note that if you chose this method and need to add any wildcards to +your query text, you must do so by appending (and/or prefixing) them to the {@code selectionArgs} +parameter, because this is the value that will be wrapped in quotes and inserted in place of the +question mark.
+ +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 will trigger 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 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 the fly. 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 Search Manager with a {@link android.database.Cursor}, the +Search Manager expects there to be 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}. There are several columns that the Search +Manager will understand, but only two are required:
+ +The following columns are all optional (and most will be discussed further in the following +sections, so you may want to skip this list for now):
+ +Again, most of these columns will be discussed in the relevant sections below, so don't worry if +they don't make sense to you now.
+ + + +When the user selects a suggestion from the list that appears below the search +dialog (instead of performing a search), the Search Manager will send +a custom {@link android.content.Intent} to your searchable Activity. You must define both 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 whatever you want and can even be different for each +suggestion.
+ +To declare an Intent action that will be the same for all suggestions, define the action in +the {@code android:searchSuggestIntentAction} attribute of your searchable configuration file. 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="my.package.MySuggestionProvider" + android:searchSuggestIntentAction="android.Intent.action.VIEW" > +</searchable> ++ +
If you want to declare an Intent action that's unique for each suggestion, 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 will be 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 will receive 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 suggestions was selected. Specifically, +the data should be something unique for each suggestion, such as the row ID for the suggestion in +your suggestions table. When the Intent is received, +you can retrieve the attached data with {@link android.content.Intent#getData()} or {@link +android.content.Intent#getDataString()}.
+ +There are two ways to define the data that is included with the Intent:
+ +The first option is straight-forward. Simply 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 will be attached to the Intent exactly as it +is found 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} is used to +create a projection map of column names to aliases.
+ +The second option is to fragment your data URI into the common piece and the unique piece. +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="my.package.MySuggestionProvider" + android:searchSuggestIntentAction="android.Intent.action.VIEW" + android:searchSuggestIntentData="content://my.package/datatable" > +</searchable> ++ +
Now 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 Search Manager will take +the string from {@code android:searchSuggestIntentData}, append a slash ("/") and then add 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 will be placed in {@link +android.app.SearchManager#EXTRA_DATA_KEY} of the Intent's extra Bundle.
+ + +Now that your search dialog provides custom search suggestions with custom formatted Intents, you +need your searchable Activity to handle these Intents as they are delivered once the user selects a +suggestion. (This is, of course, in addition to handling the {@link +android.content.Intent#ACTION_SEARCH} Intent, which your searchable Activity already does.) +Accepting the new Intent is rather self-explanatory, so we'll skip straight to an example:
+ +
+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 my suggestions all use ACTION_VIEW)
+ Uri data = intent.getData());
+ showResult(rowId);
+}
+
+
+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 a local +method that will query the content provider for the item specified by the URI and show it.
+ + + +If the user navigates through the suggestions list using the device directional controls, 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 currently selected suggestion. 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 your application is configured 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 will be required is if your content provider for +custom suggestions requires a permission for read access. In which case, you need to add a special +{@code <path-permission>} element for the provider to grant Quick Search Box read access to your +content provider. For example:
+ ++<provider android:name="MySuggestionProvider" + android:authorities="my.package.authority" + 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 <path-permission>} element amends the restriction by granting read access to content +inside the {@code "/search_suggest_query"} path prefix when the {@code +"android.permission.GLOBAL_SEARCH"} permission exists. This grants access to Quick Search Box +so that it may query your content provider for suggestions.
+ +Content providers that enforce no permissions are already available to the search +infrastructure.
+ + +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="my.package.MySuggestionProvider" + 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 will be provided. This attribute should always be included +when {@code android:includeInGlobalSearch} is "true".
+ +Remember that the user must visit this settings menu to enable search suggestions for your +application before your search suggestions will appear in Quick Search Box. As such, if search is an +important aspect of your application, then you may want to consider a way to message this to your +users — perhaps with a note the first time they launch the app about how to enable search +suggestions for Quick Search Box.
+ + +Suggestions that the user selects from Quick Search Box may be automatically made into shortcuts. +These are suggestions that the Search Manager 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 may change 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 the following ways:
+ +Provide a value in the {@link android.app.SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} column +and the suggestion will be +re-queried for a fresh version of the suggestion each time the shortcut is displayed. The shortcut +will be quickly displayed with whatever data was most recently available until the refresh query +returns, after which the suggestion will be dynamically refreshed with the new information. The +refresh query will be 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 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 will disappear and the shortcut will be removed).
+If a suggestion refers to data that could take longer to refresh, such as a network based +refresh, you may 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" will 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 will never be copied into a shortcut. This should only be necessary if you +absolutely do not want the previously copied suggestion to appear at all. (Recall that if you +provide a normal value for the column then the suggestion shortcut will appear only until the +refresh query returns.)
Simply 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.
Of course, if none of your suggestions will ever change, then you do not need the +{@link android.app.SearchManager#SUGGEST_COLUMN_SHORTCUT_ID} column at all.
+ +Note: Quick Search Box will ultimately decide whether to shortcut +your app's suggestions, considering these values as a strong request from your application.
+ + +Once your application's search results are made available to Quick Search Box, how they surface +to the user for a particular query will be determined as appropriate by Quick Search Box ranking. +This may depend on how many other apps have results for that query, and how often the user has +selected on your results compared to those of the other apps. There is no guarantee about how +ranking will occur, or whether your app's suggestions will show at all for a given query. In +general, you can expect that providing quality results will increase the likelihood that your app's +suggestions are provided in a prominent position, and apps that provide lower quality suggestions +will be more likely to be ranked lower and/or not displayed.
+ +See the Searchable +Dictionary sample app for a complete demonstration of custom search suggestions.
+The Android search framework provides the ability for your application to +provide suggestions while the user types into the Android search dialog. In this guide, you'll learn +how to create recent query suggestions. These are suggestions based +on queries previously entered by the user. So, if the user previously searched for "puppies" then it +will appear as a suggestion as they begin typing the same string of text. The screenshot below +shows an example of recent query suggestions.
+ +Before you begin, you need to have implemented the Android search dialog for searches in your +application. If you haven't done this, see Using the Android Search +Dialog.
+ + +
+
+Recent query suggestions are simply saved searches. When the user selects one of +the suggestions, your searchable Activity will receive a normal {@link +android.content.Intent#ACTION_SEARCH} Intent with the suggestion as the search query, which your +searchable Activity will already handle.
+ +To provide recent queries suggestions, you need to:
+ +Just like the Search Manager handles the rendering of the search dialog, it will also do the work +to display all search suggestions below the search dialog. All you need to do is provide a source +from which the suggestions can be retrieved.
+ +When the Search Manager identifies that your Activity is searchable and also provides search +suggestions, the following procedure will take place as soon as the user types into the Android +search box:
+ +At this point, the following may happen:
+ +As you'll soon discover, the {@link android.content.SearchRecentSuggestionsProvider} class that +you'll extend for your content provider will automatically do the work described above, so there's +actually very little code to write.
+ + +First, you need to add the {@code android:searchSuggestAuthority} and +{@code android:searchSuggestSelection} attributes to the {@code <searchable>} element in your +searchable configuration file. 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="my.package.MySuggestionProvider" + android:searchSuggestSelection=" ?" > +</searchable> ++ +
The value for {@code android:searchSuggestAuthority} should be a fully-qualified name for +your content provider: your application package name followed by the name of your content provider. +This string must match the authority used in the content provider (discussed in the next section). +
+ +The value for {@code android:searchSuggestSelection} must be a single question-mark, preceded by +a space (" ?"), which is simply a placeholder for the SQLite selection argument (which will be +automatically replaced by the query text entered by the user).
+ + +The content provider that you need for recent query suggestions must be an implementation +of {@link android.content.SearchRecentSuggestionsProvider}. This class does practically everything +for you. All you have to do is write a class constructor that executes one line of code.
+ +For example, here's a complete implementation of a content provider for recent query +suggestions:
+ +
+public class MySuggestionProvider extends SearchRecentSuggestionsProvider {
+ public final static String AUTHORITY = "my.package.MySuggestionProvider";
+ public final static int MODE = DATABASE_MODE_QUERIES;
+
+ public MySuggestionProvider() {
+ setupSuggestions(AUTHORITY, MODE);
+ }
+}
+
+
+The call to {@link android.content.SearchRecentSuggestionsProvider#setupSuggestions(String,int)} +passes the name of the search authority (matching the one in the searchable configuration) and a +database mode. The database mode must include {@link +android.content.SearchRecentSuggestionsProvider#DATABASE_MODE_QUERIES} and can optionally include +{@link +android.content.SearchRecentSuggestionsProvider#DATABASE_MODE_2LINES}, which will add another column +to the suggestions table that allows you to provide a second line of text with each suggestion. For +example:
++public final static int MODE = DATABASE_MODE_QUERIES | DATABASE_MODE_2LINES; ++ +
In the following section, you'll see how to save both lines of text.
+ +Now simply declare the content provider in your application manifest with the same authority +string used in the class (and in the searchable configuration). For example:
+ ++<application> + <provider android:name=".MySuggestionProvider" + android:authorities="my.package.authority" /> + ... +</application> ++ + +
In order to populate your collection of recent queries, you need to add each query +received by your searchable Activity to the content provider you've just built. To do this, create +an instance of {@link +android.provider.SearchRecentSuggestions} and call {@link +android.provider.SearchRecentSuggestions#saveRecentQuery(String,String)} each time your searchable +Activity receives a query. For example, here's how you can save the query during your +Activity's {@link android.app.Activity#onCreate(Bundle) onCreate()} method:
+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ Intent Intent = getIntent();
+
+ if (Intent.ACTION_SEARCH.equals(Intent .getAction())) {
+ String query = Intent .getStringExtra(SearchManager.QUERY);
+ SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this,
+ MySuggestionProvider.AUTHORITY, MySuggestionProvider.MODE);
+ suggestions.saveRecentQuery(query, null);
+ }
+}
+
+
+Notice that the {@link android.content.SearchRecentSuggestionsProvider} constructor requires the +same authority and database mode declared by your content provider.
+ +The {@link android.provider.SearchRecentSuggestions#saveRecentQuery(String,String)} method takes +the search query string as the first parameter and, optionally, a second string to include as the +second line of the suggestion. The second parameter is only used if you've enabled two-line mode +for the search suggestions with {@link +android.content.SearchRecentSuggestionsProvider#DATABASE_MODE_2LINES}. If you have enabled +two-line mode, then the query text will be matched against this second line as well.
+ +That's all that's needed to build a recent queries suggestion provider. However, there's one +other important thing to do: provide the ability for the user to clear this search history.
+ + +To protect the user's privacy, you should always provide a way for the user to clear the recent +query suggestions. To clear the recent queries, simply call {@link +android.provider.SearchRecentSuggestions#clearHistory()}. For example:
+ ++SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, + HelloSuggestionProvider.AUTHORITY, HelloSuggestionProvider.MODE); +suggestions.clearHistory(); ++ +
Simply execute this from your choice of a "Clear Search History" menu item, +preference item, or button. You should also provide a confirmation dialog when this is pressed, to +verify that the user wants to delete their search history.
+ diff --git a/docs/html/guide/topics/search/index.jd b/docs/html/guide/topics/search/index.jd new file mode 100644 index 0000000000000..b2252bbf855eb --- /dev/null +++ b/docs/html/guide/topics/search/index.jd @@ -0,0 +1,111 @@ +page.title=Search +@jd:body + +The ability to search is considered to be a core user feature on Android. The user should be able +to search any data that is available to them, whether the content is located on the device or the +Internet. This experience should be seamless and consistent across the entire +system, which is why Android provides a simple search framework to help you provide users with +a familiar search dialog and a great search experience.
+ +
+
+Android's search framework provides a user interface in which the user can perform a search and +an interaction layer that communicates with your application. This way, you don't have to build +a search box that the user must find in order to begin a search. Instead, +a custom search dialog will appear at the top of the screen at the user's command. +The search framework will manage the search dialog and when the user executes their search, the +search framework will pass the query text to your application so that your application can begin a +search. The screenshot to the right shows an example of the search dialog (using +search suggestions).
+ +Once your application is set up to use the search dialog, you can:
+ +The following documents will teach you how to use the search dialog in +your application:
+ +Also, the Searchable Configuration document +provides a reference for the searchable configuration file (though the above +documents also discuss the configuration file in terms of specific behaviors).
+ +Note: The search framework does not provide APIs to +perform searches on your data. Performing actual searches is a task that you must accomplish +using APIs appropriate for your data, such as those in {@link android.database.sqlite} +if your data is in an SQLite database.
+ + +When you implement search in your application, you should take steps to protect the user's +privacy whenever possible. Many users consider their activities on the phone, including searches, to +be private information. To protect the user's privacy, you should abide by the following +principles:
+ +"Personal information" is information that can personally identify your users, such as their +name, email address, billing information, or other data which can be reasonably linked to such +information. If +your application implements search with the assistance of a server, try to avoid sending personal +information along with the search queries. For example, if you are searching for businesses near a +zip code, +you don't need to send the user ID as well — send only the zip code to the server. If you must +send the personal information, you should take steps to avoid logging it. If you must log it, you +should protect that data very carefully and erase it as soon as possible.
+The search framework helps your application provide context-specific suggestions while they type. +Sometimes these +suggestions are based on previous searches, or other actions taken by the user in an earlier +session. A user may not wish for previous searches to be revealed to other users, for instance if +they share their phone with a friend. If your application provides suggestions that can reveal +previous activities, you should implement a "Clear History" menu item, preference, or button. If you +are +using {@link android.provider.SearchRecentSuggestions}, you can simply call its {@link +android.provider.SearchRecentSuggestions#clearHistory()} method. If you are implementing custom +suggestions, you'll need to provide a +similar "clear history" method in your provider that can be invoked by the user.
+When you want to provide search in your application, the last thing you should have to worry +about is where to put your search box. By using the Android search framework, your application will +reveal a custom search dialog whenever the user requests it. At the +press of a dedicated search key or an API call from your application, the search dialog will +appear at the top of the screen and will automatically show your application icon. An example is +shown in the screenshot below.
+ +This guide will teach you how to set up your application to provide search in a custom search +dialog. In doing so, you will provide a standardized search experience and be able to add +features like voice search and search suggestions.
+ + +
+
+The Android search framework will manage the search dialog on your behalf; you never need +to draw it or worry about where it is, and your current Activity will not be +interrupted. The {@link android.app.SearchManager} is the component that does this work for +you (hereafter, referred to as "the Search Manager"). It manages the life of the Android search +dialog and will send your application the search query when executed by the user.
+ +When the user executes a search, the Search Manager will use a specially-formed Intent to pass +the search query to the Activity that you've declared to handle searches. Essentially, all you +need is an Activity that receives this Intent, performs the search, and presents the results. +Specifically, what you need is the following:
+ +The searchable configuration is an XML file that defines several settings for the Android search +dialog in your application. This file is traditionally named {@code searchable.xml} and must be +saved in the {@code res/xml/} project directory.
+ +The file must consist of the {@code <searchable>} element as the root node and specify one +or more attributes that configure your search dialog. For example:
+ ++<?xml version="1.0" encoding="utf-8"?> +<searchable xmlns:android="http://schemas.android.com/apk/res/android" + android:label="@string/app_label" > +</searchable> ++ +
This is the minimum configuration required in order to provide the search dialog. The {@code +android:label} attribute is the only required attribute and points to a string resource, which +should normally be the same as the application. (Although it's required, this +label isn't actually shown to the user until you enable suggestions for Quick Search Box.)
+ +There are several other attributes accepted by the {@code <searchable>} element. Most of +which apply only when configuring features such as search suggestions and voice +search. However, we recommend that you always include the {@code android:hint} attribute, which +specifies a string resource for the text to display in the search dialog's text box before the user +enters their query—it provides important clues to the user about what they can search.
+ +Tip: For consistency among other +Android applications, you should format the string for {@code android:hint} as "Search +<content-or-product>". For example, "Search songs and artists" or "Search +YouTube".
+ +Next, you'll hook this configuration into your application.
+ + +When the user executes a search from the search dialog, the Search Manager will send +your searchable {@link android.app.Activity} the search query with the {@link +android.content.Intent#ACTION_SEARCH} {@link android.content.Intent}. Your searchable Activity will +then search your data and present the results.
+ + +If you don't have one already, create an {@link android.app.Activity} that will be used to +perform searches, then declare it to +accept the {@link android.content.Intent#ACTION_SEARCH} {@link android.content.Intent} and apply the +searchable configuration. To do so, you need to add an {@code +<intent-filter>} element and a {@code <meta-data>} element to the +appropriate {@code <activity>} element in your manifest file. For example:
+ ++<application ... > + <activity android:name=".MySearchableActivity" > + <intent-filter> + <action android:name="android.intent.action.SEARCH" /> + </intent-filter> + <meta-data android:name="android.app.searchable" + android:resource="@xml/searchable"/> + </activity> + ... +</application> ++ +
The {@code android:name} attribute in the {@code <meta-data>} element must be defined with +{@code "android.app.searchable"} and the {@code android:resource} attribute value must be a +reference to the searchable configuration file saved in {@code res/xml} (in this example, it +refers to the {@code res/xml/searchable.xml} file).
+ +If you're wondering why the {@code +<intent-filter>} does not include a {@code <category>} with the {@code DEFAULT} +value, it's because the Intent that is delivered to this Activity when a search is executed will +explicitly define this Activity as the component for the Intent (which the Search Manager knows +from the searcahble meta-data declared for the Activity).
+ +Be aware that the search dialog will not be available from within every +Activity of your application, by default. Rather, the search dialog will be presented to +users only when they +invoke search from a searchable context of your application. A searchable context is any Activity +for which you have +declared searchable meta-data in the manifest file. For example, the searchable Activity itself +(declared in the manifest snippet above) is +a searchable context because it contains searchable meta-data that defines the +searchable configuration. Any other Activity in your application is not a searchable context, by +default, and thus, will not reveal the search dialog. You probably do want the +search dialog to be available from every Activity in your application, so this can be easily +fixed.
+ +If you want all of your activities to provide the search dialog, add another {@code +<meta-data>} element inside the {@code +<application>} element. Use this element to declare the existing searchable Activity as the +default searchable Activity. For example:
+ ++<application ... > + <activity android:name=".MySearchableActivity" > + <intent-filter> + <action android:name="android.intent.action.SEARCH" /> + </intent-filter> + <meta-data android:name="android.app.searchable" + android:resource="@xml/searchable"/> + </activity> + <activity android:name=".AnotherActivity" ... > + </activity> + <!-- this one declares the searchable Activity for the whole app --> + <meta-data android:name="android.app.default_searchable" + android:value=".MySearchableActivity" /> + ... +</application> ++ +
The {@code <meta-data>} element with the {@code android:name} attribute value of +{@code "android.app.default_searchable"} specifies a default searchable Activity for the context in +which it is placed (which, in this case, is the entire application). The searchable Activity to +use is specified with the {@code android:value} attribute. All other activities in the +application, such as {@code AnotherActivity}, are now considered a searchable context and can invoke +the search dialog. When a search is executed, {@code MySearchableActivity} will +be launched to handle the search query.
+ +Notice that this allows you to control which activities provide search at a more granular level. +To specify only an individual Activity as a searchable context, simply place the {@code +<meta-data>} with the {@code +"android.app.default_searchable"} name inside the respective {@code <activity>} +element (rather than inside the {@code <application>}). And, while it is uncommon, you can +even create more than one searchable Activity and provide each one in different contexts of your +application, either by declaring a different searchable Activity in each {@code <activity>} +element, or declaring a default searchable Activity for the entire application and then overriding +it with a different {@code <meta-data>} element inside certain activities.
+ + +Once your Activity is declared searchable, performing the actual search involves three steps: +receiving the query, searching your data, and presenting the results.
+ +Traditionally, your search results should be presented in a {@link android.widget.ListView} +(assuming that our results are text-based), so +you may want your searchable Activity to extend {@link android.app.ListActivity}, which +provides easy access to {@link android.widget.ListView} APIs. (See the List View Tutorial for a simple +{@link android.app.ListActivity} sample.)
+ + +When a search is executed from the search dialog, your searchable Activity will be opened +with the {@link android.content.Intent#ACTION_SEARCH} {@link android.content.Intent}, which carries +the search query in the +{@link android.app.SearchManager#QUERY QUERY} extra. All you need to do is check for +this Intent and extract the string. For example, here's how you can get the query when your +Activity launches:
+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.search);
+
+ Intent intent = getIntent();
+
+ if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ doMySearch(query);
+ }
+}
+
+
+The {@link android.app.SearchManager#QUERY QUERY} string is always included with +the {@link android.content.Intent#ACTION_SEARCH} Intent. In this example, the query is +retrieved and passed to a local {@code doMySearch()} method where the actual search operation +is done.
+ + +The process of storing and searching your data is a process that's unique to your application. +There are many ways that you might do this and discussing all options is beyond the scope of +this document. This guide will not teach you how to store your data and search it; this +is something you must carefully consider in terms of your needs and your data. However, here are +some tips you may be able to apply:
+ +An Adapter will bind individual items from a set of data into individual {@link +android.view.View} objects. When the Adapter +is applied to a {@link android.widget.ListView}, the Views are injected as individual items of the +list. {@link +android.widget.Adapter} is simply an interface, so implementations such as {@link +android.widget.CursorAdapter} (for binding data from a {@link android.database.Cursor}) are needed. +If none of the existing implementations work for your data, then you should implement your own from +{@link android.widget.BaseAdapter}. Install the SDK Samples package for API Level 4 to see a +version of the Searchable Dictionary that creates a custom BaseAdapter.
+Regardless of where your data lives and how you search it, we recommend that you return search +results to your searchable Activity with an {@link android.widget.Adapter}. This way, you can easily +present all the search results in a {@link android.widget.ListView}. If your data comes from a +SQLite database query, then you can easily apply your results to a {@link android.widget.ListView} +using a {@link android.widget.CursorAdapter}. If your data comes in some other type of format, then +you can create an extension of the {@link android.widget.BaseAdapter}.
+ +Presenting your search results is mostly a UI detail and not something covered by the search +framework APIs. However, a simple solution is to create your searchable Activity to extend {@link +android.app.ListActivity} and then call {@link +android.app.ListActivity#setListAdapter(ListAdapter)}, passing it an {@link +android.widget.Adapter} that is bound to your data. This will automatically project all the +results into the Activity {@link android.widget.ListView}.
+ +For more help presenting your results, see the {@link android.app.ListActivity} +documentation.
+ +Also see the Searchable Dictionary sample +application for an a complete demonstration of how to search an SQLite database and use an +{@link android.widget.Adapter} to provide resuls in a {@link android.widget.ListView}.
+ + +Once you have a searchable Activity in place, invoking the search dialog so the user can +submit a +query is easy. Many Android devices provide a dedicated search key and when it is pressed while the +user is within a searchable context of your application, the search dialog will be revealed. +However, +you should never assume that a search key is available on the user's device and should always +provide a search button in your UI that will invoke search.
+ +To invoke search from your Activity, simply call {@link +android.app.Activity#onSearchRequested()}.
+ +For example, you should provide a menu item in your Options Menu or a button in your UI to +invoke search with this method. For your convenience, this search_icons.zip file includes icons for +medium and high density screens, which you can use for your menu item or button (low density +screens will automatically scale-down the hdpi image by one half).
+ + + +You can also enable "type-to-search" functionality in your Activity by calling {@link +android.app.Activity#setDefaultKeyMode(int) setDefaultKeyMode}({@link +android.app.Activity#DEFAULT_KEYS_SEARCH_LOCAL}). When this is enabled and the user begins typing on +the keyboard, search will automatically be +invoked and the keystrokes will be inserted in the search dialog. Be sure to enable this mode +during your Activity {@link android.app.Activity#onCreate(Bundle) onCreate()} method.
+ + +The search dialog behaves like a {@link android.app.Dialog} that floats at the top of the +screen. It +does not cause any change in the Activity stack, so no life-cycle methods (such as {@link +android.app.Activity#onPause()}) will +be called. All that happens is your Activity loses input focus as it is given to the search dialog. +
+ +If you want to be notified when search is invoked, simply override the {@link +android.app.Activity#onSearchRequested()} method. When this is called, you can do any work you may +want to do when your Activity looses input focus (such as pause animations). But unless you are +Passing Search Context Data (discussed above), you should always +call the super class implementation. For example:
+ +
+@Override
+public boolean onSearchRequested() {
+ pauseSomeStuff();
+ return super.onSearchRequested();
+}
+
+
+If the user cancels search by pressing the device Back key, the Activity in which search was +invoked will re-gain input focus. You can register to be notified when the search dialog is +closed with {@link android.app.SearchManager#setOnDismissListener(SearchManager.OnDismissListener)} +and/or {@link android.app.SearchManager#setOnCancelListener(SearchManager.OnCancelListener)}. You +should normally only need to register the {@link android.app.SearchManager.OnDismissListener +OnDismissListener}, because this is called every time that the search dialog is closed. The {@link +android.app.SearchManager.OnCancelListener OnCancelListener} only pertains to events in which the +user explicitly left the search dialog, so it is not called when a search is executed (in which +case, the search dialog naturally disappears).
+ +If the current Activity is not the searchable Activity, then the normal Activity life-cycle +events will be triggered once the user executes a search (the current Activity will receive {@link +android.app.Activity#onPause()} and so forth, as +described in Application +Fundamentals). If, however, the current Activity is the searchable Activity, then one of two +things will happen:
+ +
+@Override
+public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.search);
+ handleIntent(getIntent());
+}
+
+@Override
+protected void onNewIntent(Intent intent) {
+ setIntent(intent);
+ handleIntent(intent);
+}
+
+private void handleIntent(Intent intent) {
+ if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ doMySearch(query);
+ }
+}
+
+
+Compared to the example code in the section about Performing a +Search, all the code to handle the +search Intent has been moved outside the {@link android.app.Activity#onCreate(Bundle) +onCreate()} method so it can also be executed from {@link android.app.Activity#onNewIntent(Intent) +onNewIntent()}. +It's important to note that when {@link android.app.Activity#onNewIntent(Intent)} is +called, the Activity has not been restarted, so the {@link android.app.Activity#getIntent()} method +will still return the Intent that was first received with {@link +android.app.Activity#onCreate(Bundle) onCreate()}. This is why {@link +android.app.Activity#setIntent(Intent)} is called inside {@link +android.app.Activity#onNewIntent(Intent)} (just in case you call {@link +android.app.Activity#getIntent()} at a later time).
+ +This second scenario is normally ideal, because the chances are good that once a search is +completed, the user will perform additional searches and it's a bad experience if your application +piles multiple instances of the searchable Activity on the stack. So we recommend that you set your +searchable Activity to "singleTop" launch mode in the application manifest. For example:
+ ++<activity android:name=".MySearchableActivity" + android:launchMode="singleTop" > + <intent-filter> + <action android:name="android.intent.action.SEARCH" /> + </intent-filter> + <meta-data android:name="android.app.searchable" + android:resource="@xml/searchable"/> + </activity> ++ + +
In order to refine your search criteria, you may want to provide some additional +data to your searchable Activity when a search is executed. For instance, when you search your data, +you may want to filter results based on more than just the search query text. In a simple +case, you could just make your refinements inside the searchable Activity, for every search made. +If, however, your +search criteria may vary from one searchable context to another, then you can pass whatever data is +necessary to refine your search in the {@link android.app.SearchManager#APP_DATA} Bundle, which is +included in the {@link android.content.Intent#ACTION_SEARCH} Intent.
+ +To pass this kind of data to your searchable Activity, you need to override {@link +android.app.Activity#onSearchRequested()} method for the Activity in which search will be invoked. +For example:
+ +
+@Override
+public boolean onSearchRequested() {
+ Bundle appData = new Bundle();
+ appData.putBoolean(MySearchableActivity.JARGON, true);
+ startSearch(null, false, appData, false);
+ return true;
+ }
+
+
+Returning "true" indicates that you have successfully handled this callback event. Then in your +searchable Activity, you can extract this data from the {@link +android.app.SearchManager#APP_DATA} {@link android.os.Bundle} to refine the search. For example:
+ +
+ Bundle appData = getIntent().getBundleExtra(SearchManager.APP_DATA);
+ if (appData != null) {
+ boolean jargon = appData.getBoolean(MySearchableActivity.JARGON);
+ }
+
+
+Note: You should never call the {@link +android.app.Activity#startSearch(String,boolean,Bundle,boolean) startSearch()} method from outside +the {@link android.app.Activity#onSearchRequested()} callback method. When you want to invoke the +search dialog, always call {@link android.app.Activity#onSearchRequested()} so that custom +implementations (such as the addition of {@code appData}, in the above example) can be accounted +for.
+ + +You can easily add voice search functionality to your search dialog by adding the {@code +android:voiceSearchMode} attribute to your searchable configuration. This will add a voice search +button in the search dialog that, when clicked, will launch a voice prompt. When the user +has finished speaking, the transcribed search query will be sent to your searchable +Activity.
+ +To enable voice search for your activity, add the {@code android:voiceSearchMode} +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/search_label" + android:hint="@string/search_hint" + android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" > +</searchable> ++ +
The value {@code showVoiceSearchButton} is required to enable voice +search, while the second value, {@code launchRecognizer}, specifies that the voice search button +should launch a recognizer that returns the transcribed text to the searchable Activity. This is +how most applications should declare this attribute.
+ +There are some additional attributes you can provide to specify the voice search behavior, such +as the language to be expected and the maximum number of results to return. See the Searchable Configuration for more information about the +available attributes.
+ +Note: Carefully consider whether voice search is appropriate for +your application. All searches performed with the voice search button will be immediately sent to +your searchable Activity without a chance for the user to review the transcribed query. Be sure to +sufficiently test the voice recognition and ensure that it understands the types of queries that +the user will submit inside your application.
diff --git a/docs/html/guide/topics/search/searchable-config.jd b/docs/html/guide/topics/search/searchable-config.jd new file mode 100644 index 0000000000000..f3a5bb1b6721c --- /dev/null +++ b/docs/html/guide/topics/search/searchable-config.jd @@ -0,0 +1,356 @@ +page.title=Searchable Configuration +parent.title=Search +parent.link=index.html +@jd:body + +In order to utilize the Android search framework and provide a custom search dialog, your +application must provide a search +configuration in the form of an XML resource. This document describes the search configuration XML +in terms of its syntax and usage. For a more complete discussion about how to implement search +features for your application, see the companion documents about Search.
+ +res/xml/filename.xml
+<?xml version="1.0" encoding="utf-8"?>
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+ android:label="string resource"
+ android:hint="string resource"
+ android:searchMode=["queryRewriteFromData" | "queryRewriteFromText"]
+ android:searchButtonText="string resource"
+ android:inputType="{@link android.R.attr#inputType}"
+ android:imeOptions="{@link android.R.attr#imeOptions}"
+ android:searchSuggestAuthority="string"
+ android:searchSuggestPath="string"
+ android:searchSuggestSelection="string"
+ android:searchSuggestIntentAction="string"
+ android:searchSuggestIntentData="string"
+ android:searchSuggestThreshold="int"
+ android:includeInGlobalSearch=["true" | "false"]
+ android:searchSettingsDescription="string resource"
+ android:queryAfterZeroResults=["true" | "false"]
+ android:voiceSearchMode=["showVoiceSearchButton" | "launchWebSearch" | "launchRecognizer"]
+ android:voiceLanguageModel=["free-form" | "web_search"]
+ android:voicePromptText="string resource"
+ android:voiceLanguage="string"
+ android:voiceMaxResults="int"
+ >
+ <actionkey
+ android:keycode="{@link android.view.KeyEvent KEYCODE}"
+ android:queryActionMsg="string"
+ android:suggestActionMsg="string"
+ android:suggestActionMsgColumn="string" >
+</searchable>
+
+res/xml/searchable.xml:
++<?xml version="1.0" encoding="utf-8"?> +<searchable xmlns:android="http://schemas.android.com/apk/res/android" + android:label="@string/search_label" + android:hint="@string/search_hint" + android:searchSuggestAuthority="dictionary" + android:searchSuggestIntentAction="android.intent.action.VIEW" + android:includeInGlobalSearch="true" + android:searchSettingsDescription="@string/settings_description" > +</searchable> ++ +