O Android TV usa a interface de pesquisa para recuperar dados de conteúdo de apps instalados e exibir os resultados da pesquisa para o usuário. Os dados de conteúdo do app podem ser incluídos nesses resultados para dar ao usuário acesso instantâneo ao conteúdo no app.
Seu app precisa fornecer ao Android TV os campos de dados em que o Android TV pode gerar resultados de pesquisa
sugeridos à medida que o usuário digita caracteres na caixa de diálogo de pesquisa. Para fazer isso, o app precisa implementar um
Provedor de conteúdo que exiba
as sugestões junto com um arquivo de configuração
searchable.xml
que descreva o provedor
de conteúdo e outras informações vitais para o Android TV. Você também precisa de uma atividade que processe a
intent acionada quando o usuário seleciona um resultado de pesquisa sugerido. Para
mais detalhes, consulte Adicionar
sugestões de pesquisa personalizadas. Este guia aborda os principais pontos específicos de apps para Android TV.
Antes de ler este guia, familiarize-se com os conceitos explicados no Guia da API Search. Além disso, leia Adicionar a funcionalidade de pesquisa.
O código de amostra deste guia é proveniente do app de exemplo do Leanback .
Identificar colunas
O SearchManager
descreve os campos de dados esperados, representando-os como
colunas de um banco de dados local. Seja qual for o formato dos dados, você precisa mapear os campos de dados para
essas colunas, geralmente na classe que acessa os dados de conteúdo. Para saber mais sobre como criar
uma classe que mapeie seus dados para os campos obrigatórios, consulte
Como criar uma tabela de sugestões.
A classe SearchManager
inclui várias colunas para o Android TV. Algumas das
colunas mais importantes são descritas na tabela a seguir.
Valor | Descrição |
---|---|
SUGGEST_COLUMN_TEXT_1 |
O nome do seu conteúdo (obrigatório) |
SUGGEST_COLUMN_TEXT_2 |
Descrição de texto do seu conteúdo |
SUGGEST_COLUMN_RESULT_CARD_IMAGE |
Uma imagem, pôster ou capa para seu conteúdo |
SUGGEST_COLUMN_CONTENT_TYPE |
O tipo MIME da sua mídia |
SUGGEST_COLUMN_VIDEO_WIDTH |
A largura da resolução da sua mídia |
SUGGEST_COLUMN_VIDEO_HEIGHT |
A altura da resolução da sua mídia |
SUGGEST_COLUMN_PRODUCTION_YEAR |
O ano de produção do seu conteúdo (obrigatório) |
SUGGEST_COLUMN_DURATION |
A duração da mídia em milissegundos (obrigatório) |
O framework de pesquisa requer as seguintes colunas:
Quando os valores dessas colunas do seu conteúdo corresponderem aos valores do mesmo conteúdo de outros provedores encontrados pelos servidores do Google, o sistema fornecerá um link direto para seu app na visualização de detalhes do conteúdo, além de links para apps de outros provedores. Isso é discutido com mais detalhes na seção Link direto para seu app na tela de detalhes.
A classe do banco de dados do seu aplicativo pode definir as colunas da seguinte maneira:
Kotlin
class VideoDatabase { companion object { // The columns we'll include in the video database table val KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1 val KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2 val KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE val KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE val KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE val KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH val KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT val KEY_AUDIO_CHANNEL_CONFIG = SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG val KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE val KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE val KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE val KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE val KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR val KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION val KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION ... } ... }
Java
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; ...
Ao criar o mapa das colunas SearchManager
para os campos de dados, você também precisa especificar _ID
para atribuir um ID exclusivo a cada linha.
Kotlin
companion object { .... private fun buildColumnMap(): Map<String, String> { return mapOf( KEY_NAME to KEY_NAME, KEY_DESCRIPTION to KEY_DESCRIPTION, KEY_ICON to KEY_ICON, KEY_DATA_TYPE to KEY_DATA_TYPE, KEY_IS_LIVE to KEY_IS_LIVE, KEY_VIDEO_WIDTH to KEY_VIDEO_WIDTH, KEY_VIDEO_HEIGHT to KEY_VIDEO_HEIGHT, KEY_AUDIO_CHANNEL_CONFIG to KEY_AUDIO_CHANNEL_CONFIG, KEY_PURCHASE_PRICE to KEY_PURCHASE_PRICE, KEY_RENTAL_PRICE to KEY_RENTAL_PRICE, KEY_RATING_STYLE to KEY_RATING_STYLE, KEY_RATING_SCORE to KEY_RATING_SCORE, KEY_PRODUCTION_YEAR to KEY_PRODUCTION_YEAR, KEY_COLUMN_DURATION to KEY_COLUMN_DURATION, KEY_ACTION to KEY_ACTION, BaseColumns._ID to ("rowid AS " + BaseColumns._ID), SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID), SearchManager.SUGGEST_COLUMN_SHORTCUT_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID) ) } }
Java
... private static HashMap<String, String> buildColumnMap() { HashMap<String, String> map = new HashMap<String, String>(); 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; } ...
No exemplo anterior, observe o mapeamento para o campo
SUGGEST_COLUMN_INTENT_DATA_ID
. Essa é a parte do URI que aponta para o conteúdo exclusivo dos dados nessa
linha (a última parte do URI, que descreve onde o conteúdo é armazenado. A primeira parte do URI,
quando comum a todas as linhas da tabela, é definida no arquivo
searchable.xml
como o atributo
android:searchSuggestIntentData
, conforme descrito na seção
Processar sugestões de pesquisa.
Se a primeira parte do URI for diferente para cada linha na tabela, mapeie esse valor com o campo SUGGEST_COLUMN_INTENT_DATA
.
Quando o usuário seleciona esse conteúdo, a intent disparada fornece os dados de intent da
combinação do SUGGEST_COLUMN_INTENT_DATA_ID
e do atributo android:searchSuggestIntentData
ou do
valor do campo SUGGEST_COLUMN_INTENT_DATA
.
Fornecer dados para sugestões de pesquisa
Implemente um provedor de conteúdo
para retornar sugestões de termos de pesquisa à caixa de diálogo de pesquisa do Android TV. O sistema consulta o provedor de
conteúdo em busca de sugestões chamando o método query()
sempre que
uma letra é digitada. Na implementação de query()
, o provedor
de conteúdo pesquisa os dados de sugestão e retorna um Cursor
que aponta para
as linhas designadas para sugestões.
Kotlin
fun query(uri: Uri, projection: Array<String>, selection: String, selectionArgs: Array<String>, sortOrder: String): Cursor { // Use the UriMatcher to see what kind of query we have and format the db query accordingly when (URI_MATCHER.match(uri)) { SEARCH_SUGGEST -> { Log.d(TAG, "search suggest: ${selectionArgs[0]} URI: $uri") if (selectionArgs == null) { throw IllegalArgumentException( "selectionArgs must be provided for the Uri: $uri") } return getSuggestions(selectionArgs[0]) } else -> throw IllegalArgumentException("Unknown Uri: $uri") } } private fun getSuggestions(query: String): Cursor { val columns = arrayOf<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 videoDatabase.getWordMatch(query.toLowerCase(), columns) }
Java
@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 videoDatabase.getWordMatch(query, columns); } ...
No arquivo de manifesto, o provedor de conteúdo recebe tratamento especial. Em vez de ser
marcado como uma atividade, ele é descrito como
<provider>
. O
provedor inclui o atributo android:authorities
para informar ao sistema o
namespace do provedor de conteúdo. Além disso, é necessário definir o atributo android:exported
como
"true"
para que a pesquisa global do Android possa usar os resultados retornados.
<provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" />
Processar sugestões de pesquisa
Seu app precisa incluir um arquivo
res/xml/searchable.xml
para definir as configurações de sugestões de pesquisa.
No arquivo res/xml/searchable.xml
, inclua
o atributo
android:searchSuggestAuthority
para informar ao sistema o namespace do seu
provedor de conteúdo. Ele precisa corresponder ao valor de string especificado no atributo
android:authorities
do elemento <provider>
no arquivo AndroidManifest.xml
.
Inclua também um rótulo, que é o nome do aplicativo. As configurações de pesquisa do sistema usam esse rótulo ao enumerar apps pesquisáveis.
O arquivo searchable.xml
também precisa incluir o
android:searchSuggestIntentAction
com o valor "android.intent.action.VIEW"
para definir a ação da intent que vai fornecer uma sugestão personalizada. Isso é diferente da ação de intent
que fornece um termo de pesquisa, conforme descrito na seção a seguir.
Para conhecer outras formas de declarar a ação da intent para sugestões,
consulte Declarar a
ação da intent.
Junto com a ação da intent, seu app precisa fornecer os dados da intent, especificados com o
atributo
android:searchSuggestIntentData
. Essa é a primeira parte do URI que aponta para o conteúdo, que descreve a parte do URI comum a todas as linhas na tabela de mapeamento para esse conteúdo. A parte do URI que é exclusiva para cada linha é estabelecida com o campo SUGGEST_COLUMN_INTENT_DATA_ID
, conforme descrito na seção Identificar colunas.
Para conhecer outras maneiras de declarar os dados de intent para sugestões, consulte
Como declarar
os dados de intent.
O atributo android:searchSuggestSelection=" ?"
especifica o valor transmitido
como o parâmetro selection
do método
query()
. O valor do ponto de interrogação (?
) é substituído pelo texto da consulta.
Por fim, você também precisa incluir o atributo
android:includeInGlobalSearch
com o valor "true"
. Confira um exemplo de
arquivo searchable.xml
:
<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>
Processar termos de pesquisa
Assim que a caixa de diálogo de pesquisa tiver uma palavra que corresponda ao valor em uma das colunas do seu app, conforme
descrito na seção Identificar colunas, o sistema acionará a
intent ACTION_SEARCH
.
A atividade no app que processa essa intent procura, no repositório, colunas que tenham a palavra especificada nos valores e retorna uma lista de itens de conteúdo com essas colunas. No arquivo AndroidManifest.xml
, você designa a
atividade que processa a intent ACTION_SEARCH
,
conforme mostrado no exemplo a seguir:
... <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" /> ...
A atividade também precisa descrever a configuração pesquisável com uma referência ao arquivo
searchable.xml
.
Para usar a caixa de diálogo de pesquisa global,
o manifesto precisa descrever qual atividade receberá as consultas de pesquisa. O manifesto também precisa
descrever o elemento <provider>
, exatamente como descrito no arquivo searchable.xml
.
Link direto para seu app na tela de detalhes
Se você tiver definido a configuração da pesquisa conforme descrito na seção Processar sugestões
de pesquisa e mapeado os campos SUGGEST_COLUMN_TEXT_1
,
SUGGEST_COLUMN_PRODUCTION_YEAR
e
SUGGEST_COLUMN_DURATION
, conforme descrito na
seção Identificar colunas, um
link direto para uma ação de assistir do seu conteúdo vai aparecer na tela de detalhes que é iniciada quando
o usuário seleciona um resultado da pesquisa:
Quando o usuário seleciona o link para seu app, identificado pelo botão **Available On** na
tela de detalhes, o sistema inicia a atividade que processa o ACTION_VIEW
definido como
android:searchSuggestIntentAction
com o valor "android.intent.action.VIEW"
no
arquivo searchable.xml
.
Também é possível configurar uma intent personalizada para iniciar sua atividade. Isso é demonstrado no
app de amostra Leanback
. Observe que o app de exemplo inicia o próprio LeanbackDetailsFragment
para
mostrar os detalhes da mídia selecionada. Nos apps, inicie a atividade que reproduz a mídia
imediatamente para salvar o usuário com um ou dois cliques.
Comportamento de pesquisa
A pesquisa está disponível no Android TV na tela inicial e no seu app. Os resultados da pesquisa são diferentes para esses dois casos.
Pesquisar na tela inicial
Quando o usuário pesquisa na tela inicial, o primeiro resultado aparece em um card de entidade. Se houver apps que possam abrir o conteúdo, um link para cada um deles vai aparecer na parte de baixo do card:
Não é possível colocar um app programaticamente no card da entidade. Para serem incluídos como uma opção de reprodução, os resultados da pesquisa de um app precisam corresponder ao título, ano e duração do conteúdo pesquisado.
Mais resultados da pesquisa podem estar disponíveis abaixo do card. Para vê-los, o usuário precisa pressionar o controle remoto e rolar para baixo. Os resultados para cada app aparecem em uma linha separada. Não é possível controlar a ordem das linhas. Os apps com suporte para ações de assistir são listados primeiro.
Pesquisar dentro do app
O usuário também pode iniciar uma pesquisa no app iniciando o microfone pelo controle remoto ou pelo controlador de jogo. Os resultados da pesquisa são exibidos em uma única linha na parte superior do conteúdo do app. Seu app gera resultados da pesquisa usando o próprio provedor de pesquisa global.
Saiba mais
Para saber mais sobre como pesquisar em um app de TV, leia Integrar recursos de pesquisa do Android ao app e Adicionar a funcionalidade de pesquisa.
Para saber mais sobre como personalizar a experiência de pesquisa no app com um SearchFragment
, leia
Pesquisar em apps de TV.