From e2b44f86b9ff32cc5fec04c10d09d639fd1f45f2 Mon Sep 17 00:00:00 2001
From: Scott Rowe attributes:
<?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"
+ 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}"
@@ -69,7 +69,7 @@ Android uses the filename as the resource ID.
-
android:labelandroid:labelandroid:includeInGlobalSearch to "true", in which case, this label is used to identify
your application as a searchable item in the system's search settings.android:hintandroid:hintandroid:searchModeandroid:searchMode
android:searchButtonTextandroid:searchButtonTextandroid:inputTypeandroid:inputTypeandroid:imeOptionsandroid:imeOptionsandroid:searchSuggestAuthorityandroid:searchSuggestAuthorityandroid:searchSuggestPathandroid:searchSuggestPathandroid:searchSuggestSelectionandroid:searchSuggestSelectionandroid:searchSuggestIntentActionandroid:searchSuggestIntentActionandroid:searchSuggestIntentDataandroid:searchSuggestIntentDataandroid:searchSuggestThresholdandroid:searchSuggestThresholdandroid:includeInGlobalSearchandroid:includeInGlobalSearchandroid:searchSettingsDescriptionandroid:searchSettingsDescriptionandroid:queryAfterZeroResultsandroid:queryAfterZeroResultsandroid:voiceSearchModeandroid:voiceSearchModeandroid:voiceLanguageModelandroid:voiceLanguageModelAlso see +
Also see {@link android.speech.RecognizerIntent#EXTRA_LANGUAGE_MODEL} for more information.
android:voicePromptTextandroid:voicePromptTextandroid:voiceLanguageandroid:voiceLanguageandroid:voiceMaxResultsandroid:voiceMaxResultsattributes:
android:keycodeandroid:keycodeandroid:queryActionMsgandroid:queryActionMsgandroid:suggestActionMsgandroid:suggestActionMsgandroid:suggestActionMsgColumnandroid:suggestActionMsgColumnAndroid TV uses the Android search interface +to retrieve content data from installed apps and deliver search results to the user. Your app's +content data can be included with these results, to give the user instant access to the content in +your app.
+ +Your app must provide Android TV with the data fields from which it generates suggested search +results as the user enters characters in the search dialog. To do that, your app must implement a +Content Provider that serves +up the suggestions along with a +{@code searchable.xml} configuration file that describes the content +provider and other vital information for Android TV. You also need an activity that handles the +intent that fires when the user selects a suggested search result. All of this is described in +more detail in Adding Custom +Suggestions. Here are described the main points for Android TV apps.
+ +This lesson builds on your knowledge of using search in Android to show you how to make your app +searchable in Android TV. Be sure you are familiar with the concepts explained in the +Search API guide before following this lesson. +See also the training Adding Search Functionality.
+ +This discussion describes some code from the +Android Leanback sample app, +available on GitHub.
+ +The {@link android.app.SearchManager} describes the data fields it expects by representing them as +columns of an SQLite database. Regardless of your data's format, you must map your data fields to +these columns, usually in the class that accessess your content data. For information about building +a class that maps your existing data to the required fields, see + +Building a suggestion table.
+ +The {@link android.app.SearchManager} class includes several columns for Android TV. Some of the +more important columns are described below.
+ +| Value | +Description | +
|---|---|
| {@code SUGGEST_COLUMN_TEXT_1} | +The name of your content (required) | +
| {@code SUGGEST_COLUMN_TEXT_2} | +A text description of your content | +
| {@code SUGGEST_COLUMN_RESULT_CARD_IMAGE} | +An image/poster/cover for your content | +
| {@code SUGGEST_COLUMN_CONTENT_TYPE} | +The MIME type of your media (required) | +
| {@code SUGGEST_COLUMN_VIDEO_WIDTH} | +The resolution width of your media | +
| {@code SUGGEST_COLUMN_VIDEO_HEIGHT} | +The resolution height of your media | +
| {@code SUGGEST_COLUMN_PRODUCTION_YEAR} | +The production year of your content (required) | +
| {@code SUGGEST_COLUMN_DURATION} | +The duration in milliseconds of your media | +
The search framework requires the following columns:
+When the values of these columns for your content match the values for the same content from other +providers found by Google servers, the system provides a +deep link to your app in the details +view for the content, along with links to the apps of other providers. This is discussed more in +Display Content in the Details Screen, below.
+ +Your application's database class might define the columns as follows:
+ + +
+public class VideoDatabase {
+ //The columns we'll include in the video database table
+ public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1;
+ public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2;
+ public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE;
+ public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE;
+ public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE;
+ public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH;
+ public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT;
+ public static final String KEY_AUDIO_CHANNEL_CONFIG =
+ SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG;
+ public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE;
+ public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE;
+ public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE;
+ public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE;
+ public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR;
+ public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION;
+ public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION;
+...
+
+
+When you build the map from the {@link android.app.SearchManager} columns to your data fields, you +must also specify the {@link android.provider.BaseColumns#_ID} to give each row a unique ID.
+ + ++... + private static HashMap+ +buildColumnMap() { + HashMap map = new HashMap (); + map.put(KEY_NAME, KEY_NAME); + map.put(KEY_DESCRIPTION, KEY_DESCRIPTION); + map.put(KEY_ICON, KEY_ICON); + map.put(KEY_DATA_TYPE, KEY_DATA_TYPE); + map.put(KEY_IS_LIVE, KEY_IS_LIVE); + map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH); + map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT); + map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG); + map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE); + map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE); + map.put(KEY_RATING_STYLE, KEY_RATING_STYLE); + map.put(KEY_RATING_SCORE, KEY_RATING_SCORE); + map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR); + map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION); + map.put(KEY_ACTION, KEY_ACTION); + map.put(BaseColumns._ID, "rowid AS " + + BaseColumns._ID); + map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " + + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); + map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " + + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID); + return map; + } +... +
In the example above, notice the mapping to the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID} +field. This is the portion of the URI that points to the content unique to the data in this row — +that is, the last part of the URI describing where the content is stored. The first part of the URI, +when it is common to all of the rows in the table, is set in the + {@code searchable.xml} file as the + +{@code android:searchSuggestIntentData} attribute, as described in +Handle Search Suggestions, below. + +
If the first part of the URI is different for each row in the +table, you map that value with the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA} field. +When the user selects this content, the intent that fires provides the intent data from the +combination of the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID} +and either the {@code android:searchSuggestIntentData} attribute or the +{@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA} field value.
+ +Implement a Content Provider +to return search term suggestions to the Android TV search dialog. The system queries your content +provider for suggestions by calling the {@link android.content.ContentProvider#query(android.net.Uri, +java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) query()} method each time +a letter is typed. In your implementation of {@link android.content.ContentProvider#query(android.net.Uri, +java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) query()}, your content +provider searches your suggestion data and returns a {@link android.database.Cursor} that points to +the rows you have designated for suggestions.
+ + +
+@Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ // Use the UriMatcher to see what kind of query we have and format the db query accordingly
+ switch (URI_MATCHER.match(uri)) {
+ case SEARCH_SUGGEST:
+ Log.d(TAG, "search suggest: " + selectionArgs[0] + " URI: " + uri);
+ if (selectionArgs == null) {
+ throw new IllegalArgumentException(
+ "selectionArgs must be provided for the Uri: " + uri);
+ }
+ return getSuggestions(selectionArgs[0]);
+ default:
+ throw new IllegalArgumentException("Unknown Uri: " + uri);
+ }
+ }
+
+ private Cursor getSuggestions(String query) {
+ query = query.toLowerCase();
+ String[] columns = new String[]{
+ BaseColumns._ID,
+ VideoDatabase.KEY_NAME,
+ VideoDatabase.KEY_DESCRIPTION,
+ VideoDatabase.KEY_ICON,
+ VideoDatabase.KEY_DATA_TYPE,
+ VideoDatabase.KEY_IS_LIVE,
+ VideoDatabase.KEY_VIDEO_WIDTH,
+ VideoDatabase.KEY_VIDEO_HEIGHT,
+ VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG,
+ VideoDatabase.KEY_PURCHASE_PRICE,
+ VideoDatabase.KEY_RENTAL_PRICE,
+ VideoDatabase.KEY_RATING_STYLE,
+ VideoDatabase.KEY_RATING_SCORE,
+ VideoDatabase.KEY_PRODUCTION_YEAR,
+ VideoDatabase.KEY_COLUMN_DURATION,
+ VideoDatabase.KEY_ACTION,
+ SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
+ };
+ return mVideoDatabase.getWordMatch(query, columns);
+ }
+...
+
+
+In your manifest file, the content provider receives special treatment. Rather than getting +tagged as an activity, it is described as a +{@code <provider>}. The +provider includes the {@code android:searchSuggestAuthority} attribute to tell the system the +namespace of your content provider. Also, you must set its {@code android:exported} attribute to +{@code "true"} so that the Android global search can use the results returned from it.
+ + ++<provider android:name="com.example.android.tvleanback.VideoContentProvider" + android:authorities="com.example.android.tvleanback" + android:exported="true" /> ++ +
Your app must include a +{@code res/xml/searchable.xml} file to configure the search suggestions settings. It inlcudes +the +{@code android:searchSuggestAuthority} attribute to tell the system the namespace of your +content provider. This must match the string value you specify in the +{@code android:authorities} +attribute of the {@code <provider>} + element in your {@code AndroidManifest.xml} file.
+ +The {@code searchable.xml} file +must also include the +{@code android:searchSuggestIntentAction} with the value {@code "android.intent.action.VIEW"} +to define the intent action for providing a custom suggestion. This is different from the intent +action for providing a search term, explained below. See also, +Declaring the +intent action for other ways to declare the intent action for suggestions. + +Along with the intent action, your app must provide the intent data, which you specify with the + +{@code android:searchSuggestIntentData} attribute. This is the first part of the URI that points +to the content. It describes the portion of the URI common to all rows in the mapping table for that +content. The portion of the URI that is unique to each row is established with the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID} field, +as described above in Identify Columns. See also, + +Declaring the intent data for other ways to declare the intent data for suggestions.
+ +Also, note the {@code android:searchSuggestSelection=" ?"} attribute which specifies the value passed +as the {@code selection} parameter of the {@link android.content.ContentProvider#query(android.net.Uri, +java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) query()} method where the +question mark ({@code ?}) value is replaced with the query text.
+ +Finally, you must also include the +{@code android:includeInGlobalSearch} attribute with the value {@code "true"}. Here is an example +{@code searchable.xml} +file:
+ + ++<searchable xmlns:android="http://schemas.android.com/apk/res/android" + android:label="@string/search_label" + android:hint="@string/search_hint" + android:searchSettingsDescription="@string/settings_description" + android:searchSuggestAuthority="com.example.android.tvleanback" + android:searchSuggestIntentAction="android.intent.action.VIEW" + android:searchSuggestIntentData="content://com.example.android.tvleanback/video_database_leanback" + android:searchSuggestSelection=" ?" + android:searchSuggestThreshold="1" + android:includeInGlobalSearch="true" + > +</searchable> ++ +
As soon as the search dialog has a word which matches the value in one of your app's columns +(described in Identifying Columns, above), the system fires the +{@link android.content.Intent#ACTION_SEARCH} intent. The activity in your app which handles that +intent searches the repository for columns with the given word in their values, and returns a list +of content items with those columns. In your {@code AndroidManifest.xml} file, you designate the +activity which handles the {@link android.content.Intent#ACTION_SEARCH} intent like this: + +
++... + <activity + android:name="com.example.android.tvleanback.DetailsActivity" + android:exported="true"> + + <!-- Receives the search request. --> + <intent-filter> + <action android:name="android.intent.action.SEARCH" /> + <!-- No category needed, because the Intent will specify this class component --> + </intent-filter> + + <!-- Points to searchable meta data. --> + <meta-data android:name="android.app.searchable" + android:resource="@xml/searchable" /> + </activity> +... + <!-- Provides search suggestions for keywords against video meta data. --> + <provider android:name="com.example.android.tvleanback.VideoContentProvider" + android:authorities="com.example.android.tvleanback" + android:exported="true" /> +... ++ +
The activity must also describe the searchable configuration with a reference to the +{@code searchable.xml} file. +To use the global search dialog, +the manifest must describe which activity should receive search queries. The manifest must also +describe the {@code <provider>} +element, exactly as it is described in the +{@code searchable.xml} file.
+ +If you have set up the search configuration as described in Handle Search +Suggestions and mapped the {@link android.app.SearchManager#SUGGEST_COLUMN_TEXT_1}, +{@link android.app.SearchManager#SUGGEST_COLUMN_CONTENT_TYPE}, and +{@link android.app.SearchManager#SUGGEST_COLUMN_PRODUCTION_YEAR} fields as described in +Identify Columns, a +deep link to your content appears in the details screen that launches when the user selects a +search result.
+ +When the user selects the link for your app, identified by the "Available On" button in the +details screen, the system launches the activity which handles the {@link android.content.Intent#ACTION_VIEW} +(set as +{@code android:searchSuggestIntentAction} with the value {@code "android.intent.action.VIEW"} in +the {@code searchable.xml} file).
+ +You can also set up a custom intent to launch your activity, and this is demonstrated in the
+Android Leanback
+sample app. Note that the sample app launches its own LeanbackDetailsFragment to
+show the details for the selected media, but you should launch the activity that plays the media
+immediately to save the user another click or two.