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</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</ul> 103 104<p>When the values of these columns for your content match the values for the same content from other 105providers found by Google servers, the system provides a 106<a href="{@docRoot}training/app-indexing/deep-linking.html">deep link</a> to your app in the details 107view for the content, along with links to the apps of other providers. This is discussed more in 108<a href="#details">Display Content in the Details Screen</a>, below.</p> 109 110<p>Your application's database class might define the columns as follows:</p> 111 112<pre> 113public class VideoDatabase { 114 //The columns we'll include in the video database table 115 public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1; 116 public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2; 117 public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE; 118 public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE; 119 public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE; 120 public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH; 121 public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT; 122 public static final String KEY_AUDIO_CHANNEL_CONFIG = 123 SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG; 124 public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE; 125 public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE; 126 public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE; 127 public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE; 128 public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR; 129 public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION; 130 public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION; 131... 132</pre> 133 134<p>When you build the map from the {@link android.app.SearchManager} columns to your data fields, you 135must also specify the {@link android.provider.BaseColumns#_ID} to give each row a unique ID.</p> 136 137<pre> 138... 139 private static HashMap<String, String> buildColumnMap() { 140 HashMap<String, String> map = new HashMap<String, String>(); 141 map.put(KEY_NAME, KEY_NAME); 142 map.put(KEY_DESCRIPTION, KEY_DESCRIPTION); 143 map.put(KEY_ICON, KEY_ICON); 144 map.put(KEY_DATA_TYPE, KEY_DATA_TYPE); 145 map.put(KEY_IS_LIVE, KEY_IS_LIVE); 146 map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH); 147 map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT); 148 map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG); 149 map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE); 150 map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE); 151 map.put(KEY_RATING_STYLE, KEY_RATING_STYLE); 152 map.put(KEY_RATING_SCORE, KEY_RATING_SCORE); 153 map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR); 154 map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION); 155 map.put(KEY_ACTION, KEY_ACTION); 156 map.put(BaseColumns._ID, "rowid AS " + 157 BaseColumns._ID); 158 map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " + 159 SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); 160 map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " + 161 SearchManager.SUGGEST_COLUMN_SHORTCUT_ID); 162 return map; 163 } 164... 165</pre> 166 167<p>In the example above, notice the mapping to the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID} 168field. This is the portion of the URI that points to the content unique to the data in this row — 169that is, the last part of the URI describing where the content is stored. The first part of the URI, 170when it is common to all of the rows in the table, is set in the 171<a href="{@docRoot}guide/topics/search/searchable-config.html"> {@code searchable.xml}</a> file as the 172<a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestIntentData"> 173{@code android:searchSuggestIntentData}</a> attribute, as described in 174<a href="#suggestions">Handle Search Suggestions</a>, below. 175 176<p>If the first part of the URI is different for each row in the 177table, you map that value with the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA} field. 178When the user selects this content, the intent that fires provides the intent data from the 179combination of the {@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID} 180and either the {@code android:searchSuggestIntentData} attribute or the 181{@link android.app.SearchManager#SUGGEST_COLUMN_INTENT_DATA} field value.</p> 182 183<h2 id="provide">Provide Search Suggestion Data</h2> 184 185<p>Implement a <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Provider</a> 186to return search term suggestions to the Android TV search dialog. The system queries your content 187provider for suggestions by calling the {@link android.content.ContentProvider#query(android.net.Uri, 188java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) query()} method each time 189a letter is typed. In your implementation of {@link android.content.ContentProvider#query(android.net.Uri, 190java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) query()}, your content 191provider searches your suggestion data and returns a {@link android.database.Cursor} that points to 192the rows you have designated for suggestions.</p> 193 194<pre> 195@Override 196 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 197 String sortOrder) { 198 // Use the UriMatcher to see what kind of query we have and format the db query accordingly 199 switch (URI_MATCHER.match(uri)) { 200 case SEARCH_SUGGEST: 201 Log.d(TAG, "search suggest: " + selectionArgs[0] + " URI: " + uri); 202 if (selectionArgs == null) { 203 throw new IllegalArgumentException( 204 "selectionArgs must be provided for the Uri: " + uri); 205 } 206 return getSuggestions(selectionArgs[0]); 207 default: 208 throw new IllegalArgumentException("Unknown Uri: " + uri); 209 } 210 } 211 212 private Cursor getSuggestions(String query) { 213 query = query.toLowerCase(); 214 String[] columns = new String[]{ 215 BaseColumns._ID, 216 VideoDatabase.KEY_NAME, 217 VideoDatabase.KEY_DESCRIPTION, 218 VideoDatabase.KEY_ICON, 219 VideoDatabase.KEY_DATA_TYPE, 220 VideoDatabase.KEY_IS_LIVE, 221 VideoDatabase.KEY_VIDEO_WIDTH, 222 VideoDatabase.KEY_VIDEO_HEIGHT, 223 VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG, 224 VideoDatabase.KEY_PURCHASE_PRICE, 225 VideoDatabase.KEY_RENTAL_PRICE, 226 VideoDatabase.KEY_RATING_STYLE, 227 VideoDatabase.KEY_RATING_SCORE, 228 VideoDatabase.KEY_PRODUCTION_YEAR, 229 VideoDatabase.KEY_COLUMN_DURATION, 230 VideoDatabase.KEY_ACTION, 231 SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID 232 }; 233 return mVideoDatabase.getWordMatch(query, columns); 234 } 235... 236</pre> 237 238<p>In your manifest file, the content provider receives special treatment. Rather than getting 239tagged as an activity, it is described as a 240<a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code <provider>}</a>. The 241provider includes the {@code android:searchSuggestAuthority} attribute to tell the system the 242namespace of your content provider. Also, you must set its {@code android:exported} attribute to 243{@code "true"} so that the Android global search can use the results returned from it.</p> 244 245<pre> 246<provider android:name="com.example.android.tvleanback.VideoContentProvider" 247 android:authorities="com.example.android.tvleanback" 248 android:exported="true" /> 249</pre> 250 251<h2 id="suggestions">Handle Search Suggestions</h2> 252 253<p>Your app must include a <a href="{@docRoot}guide/topics/search/searchable-config.html"> 254{@code res/xml/searchable.xml}</a> file to configure the search suggestions settings. It inlcudes 255the <a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestAuthority"> 256{@code android:searchSuggestAuthority}</a> attribute to tell the system the namespace of your 257content provider. This must match the string value you specify in the 258<a href="{@docRoot}guide/topics/manifest/provider-element.html#auth">{@code android:authorities}</a> 259attribute of the <a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code <provider>} 260</a> element in your {@code AndroidManifest.xml} file.</p> 261 262The <a href="{@docRoot}guide/topics/search/searchable-config.html">{@code searchable.xml}</a> file 263must also include the <a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestIntentAction"> 264{@code android:searchSuggestIntentAction}</a> with the value {@code "android.intent.action.VIEW"} 265to define the intent action for providing a custom suggestion. This is different from the intent 266action for providing a search term, explained below. See also, 267<a href="{@docRoot}guide/topics/search/adding-custom-suggestions.html#IntentAction">Declaring the 268intent action</a> for other ways to declare the intent action for suggestions.</p> 269 270<p>Along with the intent action, your app must provide the intent data, which you specify with the 271<a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestIntentData"> 272{@code android:searchSuggestIntentData}</a> attribute. This is the first part of the URI that points 273to the content. It describes the portion of the URI common to all rows in the mapping table for that 274content. 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, 275as described above in <a href="#columns">Identify Columns</a>. See also, 276<a href="{@docRoot}guide/topics/search/adding-custom-suggestions.html#IntentData"> 277Declaring the intent data</a> for other ways to declare the intent data for suggestions.</p> 278 279<p>Also, note the {@code android:searchSuggestSelection=" ?"} attribute which specifies the value passed 280as the {@code selection} parameter of the {@link android.content.ContentProvider#query(android.net.Uri, 281java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) query()} method where the 282question mark ({@code ?}) value is replaced with the query text.</p> 283 284<p>Finally, you must also include the <a href="{@docRoot}guide/topics/search/searchable-config.html#includeInGlobalSearch"> 285{@code android:includeInGlobalSearch}</a> attribute with the value {@code "true"}. Here is an example 286<a href="{@docRoot}guide/topics/search/searchable-config.html">{@code searchable.xml}</a> 287file:</p> 288 289<pre> 290<searchable xmlns:android="http://schemas.android.com/apk/res/android" 291 android:label="@string/search_label" 292 android:hint="@string/search_hint" 293 android:searchSettingsDescription="@string/settings_description" 294 android:searchSuggestAuthority="com.example.android.tvleanback" 295 android:searchSuggestIntentAction="android.intent.action.VIEW" 296 android:searchSuggestIntentData="content://com.example.android.tvleanback/video_database_leanback" 297 android:searchSuggestSelection=" ?" 298 android:searchSuggestThreshold="1" 299 android:includeInGlobalSearch="true" 300 > 301</searchable> 302</pre> 303 304<h2 id="terms">Handle Search Terms</h2> 305 306<p>As soon as the search dialog has a word which matches the value in one of your app's columns 307(described in <a href="#identifying">Identifying Columns</a>, above), the system fires the 308{@link android.content.Intent#ACTION_SEARCH} intent. The activity in your app which handles that 309intent searches the repository for columns with the given word in their values, and returns a list 310of content items with those columns. In your {@code AndroidManifest.xml} file, you designate the 311activity which handles the {@link android.content.Intent#ACTION_SEARCH} intent like this: 312 313<pre> 314... 315 <activity 316 android:name="com.example.android.tvleanback.DetailsActivity" 317 android:exported="true"> 318 319 <!-- Receives the search request. --> 320 <intent-filter> 321 <action android:name="android.intent.action.SEARCH" /> 322 <!-- No category needed, because the Intent will specify this class component --> 323 </intent-filter> 324 325 <!-- Points to searchable meta data. --> 326 <meta-data android:name="android.app.searchable" 327 android:resource="@xml/searchable" /> 328 </activity> 329... 330 <!-- Provides search suggestions for keywords against video meta data. --> 331 <provider android:name="com.example.android.tvleanback.VideoContentProvider" 332 android:authorities="com.example.android.tvleanback" 333 android:exported="true" /> 334... 335</pre> 336 337<p>The activity must also describe the searchable configuration with a reference to the 338<a href="{@docRoot}guide/topics/search/searchable-config.html">{@code searchable.xml}</a> file. 339To <a href="{@docRoot}guide/topics/search/search-dialog.html">use the global search dialog</a>, 340the manifest must describe which activity should receive search queries. The manifest must also 341describe the <a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code <provider>} 342</a>element, exactly as it is described in the <a href="{@docRoot}guide/topics/search/searchable-config.html"> 343{@code searchable.xml}</a> file.</p> 344 345<h2 id="details">Deep Link to Your App in the Details Screen</h2> 346 347<p>If you have set up the search configuration as described in <a href="#suggestions">Handle Search 348Suggestions</a> and mapped the {@link android.app.SearchManager#SUGGEST_COLUMN_TEXT_1}, 349{@link android.app.SearchManager#SUGGEST_COLUMN_CONTENT_TYPE}, and 350{@link android.app.SearchManager#SUGGEST_COLUMN_PRODUCTION_YEAR} fields as described in 351<a href="#columns">Identify Columns</a>, a <a href="{@docRoot}training/app-indexing/deep-linking.html"> 352deep link</a> to a watch action for your content appears in the details screen that launches when 353the user selects a search result, as shown in figure 1.</p> 354 355<img itemprop="image" src="{@docRoot}images/tv/deep-link.png" alt="Deep link in the details screen"/> 356<p class="img-caption"><b>Figure 1.</b> The details screen displays a deep link for the 357Videos by Google (Leanback) sample app. Sintel: © copyright Blender Foundation, www.sintel.org.</p> 358 359<p>When the user selects the link for your app, identified by the "Available On" button in the 360details screen, the system launches the activity which handles the {@link android.content.Intent#ACTION_VIEW} 361(set as <a href="{@docRoot}guide/topics/search/searchable-config.html#searchSuggestIntentAction"> 362{@code android:searchSuggestIntentAction}</a> with the value {@code "android.intent.action.VIEW"} in 363the <a href="{@docRoot}guide/topics/search/searchable-config.html">{@code searchable.xml}</a> file).</p> 364 365<p>You can also set up a custom intent to launch your activity, and this is demonstrated in the 366<a class="external-link" href="https://github.com/googlesamples/androidtv-Leanback">Android Leanback 367sample app</a>. Note that the sample app launches its own <code>LeanbackDetailsFragment</code> to 368show the details for the selected media, but you should launch the activity that plays the media 369immediately to save the user another click or two.</p> 370 371 372 373 374 375 376