This lesson teaches you to
- Identify Columns
- Provide Search Suggestion Data
- Handle Search Suggestions
- Handle Search Terms
- Deep Link to Your App in the Details Screen
You should also read
Try it out
Android 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
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.
Identify Columns
The 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 SearchManager
class includes several columns for Android TV. Some of the
more important columns are described below.
Value | Description |
---|---|
SUGGEST_COLUMN_TEXT_1 |
The name of your content (required) |
SUGGEST_COLUMN_TEXT_2 |
A text description of your content |
SUGGEST_COLUMN_RESULT_CARD_IMAGE |
An image/poster/cover for your content |
SUGGEST_COLUMN_CONTENT_TYPE |
The MIME type of your media (required) |
SUGGEST_COLUMN_VIDEO_WIDTH |
The resolution width of your media |
SUGGEST_COLUMN_VIDEO_HEIGHT |
The resolution height of your media |
SUGGEST_COLUMN_PRODUCTION_YEAR |
The production year of your content (required) |
SUGGEST_COLUMN_DURATION |
The duration in milliseconds of your media (required) |
The search framework requires the following columns:
SUGGEST_COLUMN_TEXT_1
SUGGEST_COLUMN_CONTENT_TYPE
SUGGEST_COLUMN_PRODUCTION_YEAR
SUGGEST_COLUMN_DURATION
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 SearchManager
columns to your data fields, you
must also specify the _ID
to give each row a unique ID.
... private static HashMapbuildColumnMap() { 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 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
searchable.xml
file as the
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 SUGGEST_COLUMN_INTENT_DATA
field.
When the user selects this content, the intent that fires provides the intent data from the
combination of the SUGGEST_COLUMN_INTENT_DATA_ID
and either the android:searchSuggestIntentData
attribute or the
SUGGEST_COLUMN_INTENT_DATA
field value.
Provide Search Suggestion Data
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 query()
method each time
a letter is typed. In your implementation of query()
, your content
provider searches your suggestion data and returns a 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
<provider>
. The
provider includes the android:searchSuggestAuthority
attribute to tell the system the
namespace of your content provider. Also, you must set its android:exported
attribute to
"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" />
Handle Search Suggestions
Your app must include a
res/xml/searchable.xml
file to configure the search suggestions settings. It inlcudes
the
android:searchSuggestAuthority
attribute to tell the system the namespace of your
content provider. This must match the string value you specify in the
android:authorities
attribute of the <provider>
element in your AndroidManifest.xml
file.
searchable.xml
file
must also include the
android:searchSuggestIntentAction
with the value "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
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 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 android:searchSuggestSelection=" ?"
attribute which specifies the value passed
as the selection
parameter of the query()
method where the
question mark (?
) value is replaced with the query text.
Finally, you must also include the
android:includeInGlobalSearch
attribute with the value "true"
. Here is an example
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>
Handle Search Terms
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
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 AndroidManifest.xml
file, you designate the
activity which handles the 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
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 <provider>
element, exactly as it is described in the
searchable.xml
file.
Deep Link to Your App in the Details Screen
If you have set up the search configuration as described in Handle Search
Suggestions and mapped the SUGGEST_COLUMN_TEXT_1
,
SUGGEST_COLUMN_CONTENT_TYPE
, and
SUGGEST_COLUMN_PRODUCTION_YEAR
fields as described in
Identify Columns, a
deep link to a watch action for your content appears in the details screen that launches when
the user selects a search result, as shown in figure 1.
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 ACTION_VIEW
(set as
android:searchSuggestIntentAction
with the value "android.intent.action.VIEW"
in
the 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.