• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1page.title=Making TV Apps Searchable
2page.tags="search","searchable"
3
4trainingnavtop=true
5
6@jd:body
7
8<div id="tb-wrapper">
9<div id="tb">
10  <h2>This lesson teaches you to</h2>
11  <ol>
12    <li><a href="#columns">Identify Columns</a></li>
13    <li><a href="#provide">Provide Search Suggestion Data</a></li>
14    <li><a href="#suggestions">Handle Search Suggestions</a></li>
15    <li><a href="#terms">Handle Search Terms</a></li>
16    <li><a href="#details">Deep Link to Your App in the Details Screen</a></li>
17  </ol>
18  <h2>You should also read</h2>
19  <ul>
20    <li><a href="{@docRoot}guide/topics/search/index.html">Search</a></li>
21    <li><a href="{@docRoot}training/search/index.html">Adding Search Functionality</a></li>
22  </ul>
23  <h2>Try it out</h2>
24  <ul>
25    <li><a class="external-link" href="https://github.com/googlesamples/androidtv-Leanback">Android Leanback sample app</a></li>
26  </ul>
27</div>
28</div>
29
30<p>Android TV uses the Android <a href="{@docRoot}guide/topics/search/index.html">search interface</a>
31to retrieve content data from installed apps and deliver search results to the user. Your app's
32content data can be included with these results, to give the user instant access to the content in
33your app.</p>
34
35<p>Your app must provide Android TV with the data fields from which it generates suggested search
36results as the user enters characters in the search dialog. To do that, your app must implement a
37<a href="{@docRoot}guide/topics/providers/content-providers.html">Content Provider</a> that serves
38up the suggestions along with a <a href="{@docRoot}guide/topics/search/searchable-config.html">
39{@code searchable.xml}</a> configuration file that describes the content
40provider and other vital information for Android TV. You also need an activity that handles the
41intent that fires when the user selects a suggested search result. All of this is described in
42more detail in <a href="{@docRoot}guide/topics/search/adding-custom-suggestions.html">Adding Custom
43Suggestions</a>. Here are described the main points for Android TV apps.</p>
44
45<p>This lesson builds on your knowledge of using search in Android to show you how to make your app
46searchable in Android TV. Be sure you are familiar with the concepts explained in the
47<a href="{@docRoot}guide/topics/search/index.html">Search API guide</a> before following this lesson.
48See also the training <a href="{@docRoot}training/search/index.html">Adding Search Functionality</a>.</p>
49
50<p>This discussion describes some code from the
51<a class="external-link" href="https://github.com/googlesamples/androidtv-Leanback">Android Leanback sample app</a>,
52available on GitHub.</p>
53
54<h2 id="columns">Identify Columns</h2>
55
56<p>The {@link android.app.SearchManager} describes the data fields it expects by representing them as
57columns of an SQLite database. Regardless of your data's format, you must map your data fields to
58these columns, usually in the class that accessess your content data. For information about building
59a class that maps your existing data to the required fields, see
60<a href="{@docRoot}guide/topics/search/adding-custom-suggestions.html#SuggestionTable">
61Building a suggestion table</a>.</p>
62
63<p>The {@link android.app.SearchManager} class includes several columns for Android TV. Some of the
64more important columns are described below.</p>
65
66<table>
67<tr>
68   <th>Value</th>
69   <th>Description</th>
70</tr><tr>
71   <td>{@code SUGGEST_COLUMN_TEXT_1}</td>
72   <td>The name of your content <strong>(required)</strong></td>
73</tr><tr>
74   <td>{@code SUGGEST_COLUMN_TEXT_2}</td>
75   <td>A text description of your content</td>
76</tr><tr>
77   <td>{@code SUGGEST_COLUMN_RESULT_CARD_IMAGE}</td>
78   <td>An image/poster/cover for your content</td>
79</tr><tr>
80   <td>{@code SUGGEST_COLUMN_CONTENT_TYPE}</td>
81   <td>The MIME type of your media <strong>(required)</strong></td>
82</tr><tr>
83   <td>{@code SUGGEST_COLUMN_VIDEO_WIDTH}</td>
84   <td>The resolution width of your media</td>
85</tr><tr>
86   <td>{@code SUGGEST_COLUMN_VIDEO_HEIGHT}</td>
87   <td>The resolution height of your media</td>
88</tr><tr>
89   <td>{@code SUGGEST_COLUMN_PRODUCTION_YEAR}</td>
90   <td>The production year of your content <strong>(required)</strong></td>
91</tr><tr>
92   <td>{@code SUGGEST_COLUMN_DURATION}</td>
93   <td>The duration in milliseconds of your media <strong>(required)</strong></td>
94</tr>
95</table>
96
97<p>The search framework requires the following columns:</p>
98<ul>
99  <li>{@link android.app.SearchManager#SUGGEST_COLUMN_TEXT_1}</li>
100  <li>{@link android.app.SearchManager#SUGGEST_COLUMN_CONTENT_TYPE}</li>
101  <li>{@link android.app.SearchManager#SUGGEST_COLUMN_PRODUCTION_YEAR}</li>
102  <li>{@link android.app.SearchManager#SUGGEST_COLUMN_DURATION}</li>
103</ul>
104
105<p>When the values of these columns for your content match the values for the same content from other
106providers found by Google servers, the system provides a
107<a href="{@docRoot}training/app-indexing/deep-linking.html">deep link</a> to your app in the details
108view for the content, along with links to the apps of other providers. This is discussed more in
109<a href="#details">Display Content in the Details Screen</a>, below.</p>
110
111<p>Your application's database class might define the columns as follows:</p>
112
113<pre>
114public class VideoDatabase {
115  //The columns we'll include in the video database table
116  public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1;
117  public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2;
118  public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE;
119  public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE;
120  public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE;
121  public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH;
122  public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT;
123  public static final String KEY_AUDIO_CHANNEL_CONFIG =
124          SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG;
125  public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE;
126  public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE;
127  public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE;
128  public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE;
129  public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR;
130  public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION;
131  public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION;
132...
133</pre>
134
135<p>When you build the map from the {@link android.app.SearchManager} columns to your data fields, you
136must also specify the {@link android.provider.BaseColumns#_ID} to give each row a unique ID.</p>
137
138<pre>
139...
140  private static HashMap<String, String> buildColumnMap() {
141    HashMap<String, String> map = new HashMap<String, String>();
142    map.put(KEY_NAME, KEY_NAME);
143    map.put(KEY_DESCRIPTION, KEY_DESCRIPTION);
144    map.put(KEY_ICON, KEY_ICON);
145    map.put(KEY_DATA_TYPE, KEY_DATA_TYPE);
146    map.put(KEY_IS_LIVE, KEY_IS_LIVE);
147    map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH);
148    map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT);
149    map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG);
150    map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE);
151    map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE);
152    map.put(KEY_RATING_STYLE, KEY_RATING_STYLE);
153    map.put(KEY_RATING_SCORE, KEY_RATING_SCORE);
154    map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR);
155    map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION);
156    map.put(KEY_ACTION, KEY_ACTION);
157    map.put(BaseColumns._ID, "rowid AS " +
158            BaseColumns._ID);
159    map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " +
160            SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
161    map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " +
162            SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
163    return map;
164  }
165...
166</pre>
167
168<p>In the example above, notice the mapping to the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID}
169field. This is the portion of the URI that points to the content unique to the data in this row &mdash;
170that is, the last part of the URI describing where the content is stored. The first part of the URI,
171when it is common to all of the rows in the table, is set in the
172<a href="{@docRoot}guide/topics/search/searchable-config.html"> {@code searchable.xml}</a> file as the
173<a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestIntentData">
174{@code android:searchSuggestIntentData}</a> attribute, as described in
175<a href="#suggestions">Handle Search Suggestions</a>, below.
176
177<p>If the first part of the URI is different for each row in the
178table, you map that value with the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA} field.
179When the user selects this content, the intent that fires provides the intent data from the
180combination of the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID}
181and either the {@code android:searchSuggestIntentData} attribute or the
182{@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA} field value.</p>
183
184<h2 id="provide">Provide Search Suggestion Data</h2>
185
186<p>Implement a <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Provider</a>
187to return search term suggestions to the Android TV search dialog. The system queries your content
188provider for suggestions by calling the {@link android.content.ContentProvider#query(android.net.Uri,
189java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) query()} method each time
190a letter is typed. In your implementation of {@link android.content.ContentProvider#query(android.net.Uri,
191java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) query()}, your content
192provider searches your suggestion data and returns a {@link android.database.Cursor} that points to
193the rows you have designated for suggestions.</p>
194
195<pre>
196&#64;Override
197  public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
198                      String sortOrder) {
199    // Use the UriMatcher to see what kind of query we have and format the db query accordingly
200    switch (URI_MATCHER.match(uri)) {
201      case SEARCH_SUGGEST:
202          Log.d(TAG, "search suggest: " + selectionArgs[0] + " URI: " + uri);
203          if (selectionArgs == null) {
204              throw new IllegalArgumentException(
205                      "selectionArgs must be provided for the Uri: " + uri);
206          }
207          return getSuggestions(selectionArgs[0]);
208      default:
209          throw new IllegalArgumentException("Unknown Uri: " + uri);
210    }
211  }
212
213  private Cursor getSuggestions(String query) {
214    query = query.toLowerCase();
215    String[] columns = new String[]{
216      BaseColumns._ID,
217      VideoDatabase.KEY_NAME,
218      VideoDatabase.KEY_DESCRIPTION,
219      VideoDatabase.KEY_ICON,
220      VideoDatabase.KEY_DATA_TYPE,
221      VideoDatabase.KEY_IS_LIVE,
222      VideoDatabase.KEY_VIDEO_WIDTH,
223      VideoDatabase.KEY_VIDEO_HEIGHT,
224      VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG,
225      VideoDatabase.KEY_PURCHASE_PRICE,
226      VideoDatabase.KEY_RENTAL_PRICE,
227      VideoDatabase.KEY_RATING_STYLE,
228      VideoDatabase.KEY_RATING_SCORE,
229      VideoDatabase.KEY_PRODUCTION_YEAR,
230      VideoDatabase.KEY_COLUMN_DURATION,
231      VideoDatabase.KEY_ACTION,
232      SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID
233    };
234    return mVideoDatabase.getWordMatch(query, columns);
235  }
236...
237</pre>
238
239<p>In your manifest file, the content provider receives special treatment. Rather than getting
240tagged as an activity, it is described as a
241<a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code <provider>}</a>. The
242provider includes the {@code android:searchSuggestAuthority} attribute to tell the system the
243namespace of your content provider. Also, you must set its {@code android:exported} attribute to
244{@code "true"} so that the Android global search can use the results returned from it.</p>
245
246<pre>
247&lt;provider android:name="com.example.android.tvleanback.VideoContentProvider"
248    android:authorities="com.example.android.tvleanback"
249    android:exported="true" /&gt;
250</pre>
251
252<h2 id="suggestions">Handle Search Suggestions</h2>
253
254<p>Your app must include a <a href="{@docRoot}guide/topics/search/searchable-config.html">
255{@code res/xml/searchable.xml}</a> file to configure the search suggestions settings. It inlcudes
256the <a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestAuthority">
257{@code android:searchSuggestAuthority}</a> attribute to tell the system the namespace of your
258content provider. This must match the string value you specify in the
259<a href="{@docRoot}guide/topics/manifest/provider-element.html#auth">{@code android:authorities}</a>
260attribute of the <a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code <provider>}
261</a> element in your {@code AndroidManifest.xml} file.</p>
262
263The <a href="{@docRoot}guide/topics/search/searchable-config.html">{@code searchable.xml}</a> file
264must also include the <a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestIntentAction">
265{@code android:searchSuggestIntentAction}</a> with the value {@code "android.intent.action.VIEW"}
266to define the intent action for providing a custom suggestion. This is different from the intent
267action for providing a search term, explained below. See also,
268<a href="{@docRoot}guide/topics/search/adding-custom-suggestions.html#IntentAction">Declaring the
269intent action</a> for other ways to declare the intent action for suggestions.</p>
270
271<p>Along with the intent action, your app must provide the intent data, which you specify with the
272<a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestIntentData">
273{@code android:searchSuggestIntentData}</a> attribute. This is the first part of the URI that points
274to the content. It describes the portion of the URI common to all rows in the mapping table for that
275content. 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,
276as described above in <a href="#columns">Identify Columns</a>. See also,
277<a href="{@docRoot}guide/topics/search/adding-custom-suggestions.html#IntentData">
278Declaring the intent data</a> for other ways to declare the intent data for suggestions.</p>
279
280<p>Also, note the {@code android:searchSuggestSelection=" ?"} attribute which specifies the value passed
281as the {@code selection} parameter of the {@link android.content.ContentProvider#query(android.net.Uri,
282java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) query()} method where the
283question mark ({@code ?}) value is replaced with the query text.</p>
284
285<p>Finally, you must also include the <a href="{@docRoot}guide/topics/search/searchable-config.html#includeInGlobalSearch">
286{@code android:includeInGlobalSearch}</a> attribute with the value {@code "true"}. Here is an example
287<a href="{@docRoot}guide/topics/search/searchable-config.html">{@code searchable.xml}</a>
288file:</p>
289
290<pre>
291&lt;searchable xmlns:android="http://schemas.android.com/apk/res/android"
292    android:label="@string/search_label"
293        android:hint="@string/search_hint"
294        android:searchSettingsDescription="@string/settings_description"
295        android:searchSuggestAuthority="com.example.android.tvleanback"
296        android:searchSuggestIntentAction="android.intent.action.VIEW"
297        android:searchSuggestIntentData="content://com.example.android.tvleanback/video_database_leanback"
298        android:searchSuggestSelection=" ?"
299        android:searchSuggestThreshold="1"
300        android:includeInGlobalSearch="true"
301    &gt;
302&lt;/searchable&gt;
303</pre>
304
305<h2 id="terms">Handle Search Terms</h2>
306
307<p>As soon as the search dialog has a word which matches the value in one of your app's columns
308(described in <a href="#identifying">Identifying Columns</a>, above), the system fires the
309{@link android.content.Intent#ACTION_SEARCH} intent. The activity in your app which handles that
310intent searches the repository for columns with the given word in their values, and returns a list
311of content items with those columns. In your {@code AndroidManifest.xml} file, you designate the
312activity which handles the {@link android.content.Intent#ACTION_SEARCH} intent like this:
313
314<pre>
315...
316  &lt;activity
317      android:name="com.example.android.tvleanback.DetailsActivity"
318      android:exported="true"&gt;
319
320      &lt;!-- Receives the search request. --&gt;
321      &lt;intent-filter&gt;
322          &lt;action android:name="android.intent.action.SEARCH" /&gt;
323          &lt;!-- No category needed, because the Intent will specify this class component --&gt;
324      &lt;/intent-filter&gt;
325
326      &lt;!-- Points to searchable meta data. --&gt;
327      &lt;meta-data android:name="android.app.searchable"
328          android:resource="@xml/searchable" /&gt;
329  &lt;/activity&gt;
330...
331  &lt;!-- Provides search suggestions for keywords against video meta data. --&gt;
332  &lt;provider android:name="com.example.android.tvleanback.VideoContentProvider"
333      android:authorities="com.example.android.tvleanback"
334      android:exported="true" /&gt;
335...
336</pre>
337
338<p>The activity must also describe the searchable configuration with a reference to the
339<a href="{@docRoot}guide/topics/search/searchable-config.html">{@code searchable.xml}</a> file.
340To <a href="{@docRoot}guide/topics/search/search-dialog.html">use the global search dialog</a>,
341the manifest must describe which activity should receive search queries. The manifest must also
342describe the <a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code <provider>}
343</a>element, exactly as it is described in the <a href="{@docRoot}guide/topics/search/searchable-config.html">
344{@code searchable.xml}</a> file.</p>
345
346<h2 id="details">Deep Link to Your App in the Details Screen</h2>
347
348<p>If you have set up the search configuration as described in <a href="#suggestions">Handle Search
349Suggestions</a> and mapped the {@link android.app.SearchManager#SUGGEST_COLUMN_TEXT_1},
350{@link android.app.SearchManager#SUGGEST_COLUMN_CONTENT_TYPE}, and
351{@link android.app.SearchManager#SUGGEST_COLUMN_PRODUCTION_YEAR} fields as described in
352<a href="#columns">Identify Columns</a>, a <a href="{@docRoot}training/app-indexing/deep-linking.html">
353deep link</a> to a watch action for your content appears in the details screen that launches when
354the user selects a search result, as shown in figure 1.</p>
355
356<img itemprop="image" src="{@docRoot}images/tv/deep-link.png" alt="Deep link in the details screen"/>
357<p class="img-caption"><b>Figure 1.</b> The details screen displays a deep link for the
358Videos by Google (Leanback) sample app. Sintel: &copy; copyright Blender Foundation, www.sintel.org.</p>
359
360<p>When the user selects the link for your app, identified by the "Available On" button in the
361details screen, the system launches the activity which handles the {@link android.content.Intent#ACTION_VIEW}
362(set as <a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestIntentAction">
363{@code android:searchSuggestIntentAction}</a> with the value {@code "android.intent.action.VIEW"} in
364the <a href="{@docRoot}guide/topics/search/searchable-config.html">{@code searchable.xml}</a> file).</p>
365
366<p>You can also set up a custom intent to launch your activity, and this is demonstrated in the
367<a class="external-link" href="https://github.com/googlesamples/androidtv-Leanback">Android Leanback
368sample app</a>. Note that the sample app launches its own <code>LeanbackDetailsFragment</code> to
369show the details for the selected media, but you should launch the activity that plays the media
370immediately to save the user another click or two.</p>
371
372
373
374
375
376
377