A pesquisa de configurações permite pesquisar e alterar configurações específicas de maneira rápida e fácil no aplicativo Configurações Automotivas sem navegar pelos menus do aplicativo para encontrá-las. A pesquisa é a maneira mais eficaz de encontrar uma configuração específica. Por padrão, a pesquisa encontra apenas as configurações de AOSP. Configurações adicionais, injetadas ou não, requerem alterações adicionais para serem indexadas.
Visão geral
Requisitos
Para que uma configuração seja indexável pela pesquisa de configurações, os dados devem vir de um:
- Fragmento
SearchIndexable
dentroCarSettings
. - Aplicação em nível de sistema.
Definindo os dados
Campos comuns:
-
Key
. ( Obrigatório ) Chave String legível exclusiva para identificar o resultado. -
IconResId
. Opcional Se um ícone aparecer em seu aplicativo ao lado do resultado, adicione o ID do recurso, caso contrário, ignore. -
IntentAction
. Obrigatório seIntentTargetPackage
ouIntentTargetClass
não estiver definido. Define a ação que a intenção do resultado da pesquisa deve executar. -
IntentTargetPackage
. Obrigatório seIntentAction
não estiver definido. Define o pacote para o qual a intenção do resultado da pesquisa deve resolver. -
IntentTargetClass
. Obrigatório seIntentAction
não estiver definido. Define a classe (atividade) para a qual a intenção do resultado da pesquisa deve resolver.
SearchIndexableResource
apenas:
-
XmlResId
. ( Obrigatório ) Define o ID do recurso XML da página que contém os resultados a serem indexados.
SearchIndexableRaw
apenas:
-
Title
. ( Obrigatório ) Título do resultado da pesquisa. -
SummaryOn
. ( Opcional ) Resumo do resultado da pesquisa. -
Keywords
-chave. ( Opcional ) Lista de palavras associadas ao resultado da pesquisa. Corresponde à consulta ao seu resultado. -
ScreenTitle
. ( Opcional ) Título da página com o resultado da sua pesquisa.
Ocultar dados
Cada resultado da pesquisa aparecerá em Pesquisar, a menos que seja marcado de outra forma. Enquanto os resultados da pesquisa estática são armazenados em cache, uma nova lista de chaves não indexáveis é recuperada sempre que a pesquisa é aberta. Os motivos para ocultar os resultados podem incluir:
- Duplicado. Por exemplo, aparece em várias páginas.
- Mostrado apenas condicionalmente. Por exemplo, mostra apenas as configurações de dados móveis quando um cartão SIM está presente).
- Página modelada. Por exemplo, uma página de detalhes para um aplicativo individual.
- A configuração precisa de mais contexto do que um título e uma legenda. Por exemplo, uma configuração de "Configurações", que é relevante apenas para o título da tela.
Para ocultar uma configuração, seu provedor ou SEARCH_INDEX_DATA_PROVIDER
deve retornar a chave do resultado da pesquisa de getNonIndexableKeys
. A chave sempre pode ser retornada (casos de página duplicados e com modelo) ou adicionado condicionalmente (sem caso de dados móveis).
Índice estático
Use o índice estático se seus dados de índice forem sempre os mesmos. Por exemplo, o título e o resumo dos dados XML ou os dados brutos de código rígido. Os dados estáticos serão indexados apenas uma vez quando a pesquisa de configurações for iniciada pela primeira vez.
Para indexáveis dentro de configurações, implemente getXmlResourcesToIndex
e/ou getRawDataToIndex
. Para configurações injetadas, implemente os queryXmlResources
e/ou queryRawData
.
Índice dinâmico
Se os dados indexáveis puderem ser atualizados adequadamente, use o método dinâmico para indexar seus dados. A pesquisa de configurações atualizará essa lista dinâmica quando for iniciada.
Para indexáveis dentro de configurações, implemente getDynamicRawDataToIndex
. Para configurações injetadas, implemente os queryDynamicRawData methods
.
Indexação nas configurações do carro
Visão geral
Para indexar diferentes configurações a serem incluídas no recurso de pesquisa, consulte Provedores de conteúdo . Os pacotes SettingsLib
e android.provider
já possuem interfaces definidas e classes abstratas a serem estendidas para fornecer novas entradas a serem indexadas. Nas configurações de AOSP, as implementações dessas classes são usadas para indexar os resultados. A interface principal a ser preenchida é SearchIndexablesProvider
, que é usada por SettingsIntelligence
para indexar dados.
public abstract class SearchIndexablesProvider extends ContentProvider { public abstract Cursor queryXmlResources(String[] projection); public abstract Cursor queryRawData(String[] projection); public abstract Cursor queryNonIndexableKeys(String[] projection); }
Em teoria, cada fragmento poderia ser adicionado aos resultados em SearchIndexablesProvider
, nesse caso SettingsIntelligence
seria o conteúdo. Para tornar o processo fácil de manter facilmente para adicionar novos fragmentos, use a geração de código SettingsLib
de SearchIndexableResources
. Específico para as configurações do carro, cada fragmento indexável é anotado com @SearchIndexable
e, em seguida, possui um campo estático SearchIndexProvider
que fornece os dados relevantes para esse fragmento. Fragmentos com a anotação, mas sem um SearchIndexProvider
, resultam em um erro de compilação.
interface SearchIndexProvider { List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled); List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled); List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled); List<String> getNonIndexableKeys(Context context); }
Todos esses fragmentos são então adicionados à classe SearchIndexableResourcesAuto
gerada automaticamente, que é um wrapper fino em torno da lista de campos SearchIndexProvider
para todos os fragmentos. O suporte é fornecido para especificar um destino específico para uma anotação (como Auto, TV e Wear), mas a maioria das anotações é deixada no padrão ( All
). Nesse caso de uso, não há necessidade específica de especificar o destino Auto, portanto, ele permanece como All
. A implementação básica de SearchIndexProvider
deve ser suficiente para a maioria dos fragmentos.
public class CarBaseSearchIndexProvider implements Indexable.SearchIndexProvider { private static final Logger LOG = new Logger(CarBaseSearchIndexProvider.class); private final int mXmlRes; private final String mIntentAction; private final String mIntentClass; public CarBaseSearchIndexProvider(@XmlRes int xmlRes, String intentAction) { mXmlRes = xmlRes; mIntentAction = intentAction; mIntentClass = null; } public CarBaseSearchIndexProvider(@XmlRes int xmlRes, @NonNull Class intentClass) { mXmlRes = xmlRes; mIntentAction = null; mIntentClass = intentClass.getName(); } @Override public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) { SearchIndexableResource sir = new SearchIndexableResource(context); sir.xmlResId = mXmlRes; sir.intentAction = mIntentAction; sir.intentTargetPackage = context.getPackageName(); sir.intentTargetClass = mIntentClass; return Collections.singletonList(sir); } @Override public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { return null; } @Override public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled) { return null; } @Override public List<String> getNonIndexableKeys(Context context) { if (!isPageSearchEnabled(context)) { try { return PreferenceXmlParser.extractMetadata(context, mXmlRes, FLAG_NEED_KEY) .stream() .map(bundle -> bundle.getString(METADATA_KEY)) .collect(Collectors.toList()); } catch (IOException | XmlPullParserException e) { LOG.w("Error parsing non-indexable XML - " + mXmlRes); } } return null; } /** * Returns true if the page should be considered in search query. If return false, entire page will be suppressed during search query. */ protected boolean isPageSearchEnabled(Context context) { return true; } }
Indexando um novo fragmento
Com esse design, é relativamente fácil adicionar um novo SettingsFragment
a ser indexado, geralmente uma atualização de duas linhas fornecendo o XML para o fragmento e a intenção a ser seguida. Com WifiSettingsFragment
como exemplo:
@SearchIndexable public class WifiSettingsFragment extends SettingsFragment { [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.wifi_list_fragment, Settings.ACTION_WIFI_SETTINGS); }
A implementação AAOS do SearchIndexablesProvider
, que usa SearchIndexableResources
e faz a tradução de SearchIndexProviders
para o esquema de banco de dados para SettingsIntelligence
, mas é independente de quais fragmentos estão sendo indexados. O SettingsIntelligence
suporta qualquer número de provedores, então novos provedores podem ser criados para dar suporte a casos de uso especializados, permitindo que cada um seja especializado e focado em resultados com estruturas semelhantes. As preferências definidas no PreferenceScreen
para o fragmento devem ter uma chave exclusiva atribuída a elas para serem indexadas. Além disso, o PreferenceScreen
deve ter uma chave atribuída para que o título da tela seja indexado.
Exemplos de indexação
Em alguns casos, um fragmento pode não ter uma ação de intenção específica associada a ele. Nesses casos, é possível passar a classe de atividade para a intenção CarBaseSearchIndexProvider
usando um componente em vez de uma ação. Isso ainda requer que a atividade esteja presente no arquivo de manifesto e que seja exportada.
@SearchIndexable public class LanguagesAndInputFragment extends SettingsFragment { [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.languages_and_input_fragment, LanguagesAndInputActivity.class); }
Em alguns casos especiais, alguns métodos de CarBaseSearchIndexProvider
podem precisar ser substituídos para obter os resultados desejados a serem indexados. Por exemplo, em NetworkAndInternetFragment
, as preferências relacionadas à rede móvel não deveriam ser indexadas em dispositivos sem rede móvel. Nesse caso, substitua o método getNonIndexableKeys
e marque as chaves apropriadas como não indexáveis quando um dispositivo não tiver uma rede móvel.
@SearchIndexable public class NetworkAndInternetFragment extends SettingsFragment { [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.network_and_internet_fragment, Settings.Panel.ACTION_INTERNET_CONNECTIVITY) { @Override public List<String> getNonIndexableKeys(Context context) { if (!NetworkUtils.hasMobileNetwork( context.getSystemService(ConnectivityManager.class))) { List<String> nonIndexableKeys = new ArrayList<>(); nonIndexableKeys.add(context.getString( R.string.pk_mobile_network_settings_entry)); nonIndexableKeys.add(context.getString( R.string.pk_data_usage_settings_entry)); return nonIndexableKeys; } return null; } }; }
Dependendo das necessidades do fragmento específico, outros métodos do CarBaseSearchIndexProvider
podem ser substituídos para incluir outros dados indexáveis, como dados brutos estáticos e dinâmicos.
@SearchIndexable public class RawIndexDemoFragment extends SettingsFragment { public static final String KEY_CUSTOM_RESULT = "custom_result_key"; [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.raw_index_demo_fragment, RawIndexDemoActivity.class) { @Override public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { List<SearchIndexableRaw> rawData = new ArrayList<>(); SearchIndexableRaw customResult = new SearchIndexableRaw(context); customResult.key = KEY_CUSTOM_RESULT; customResult.title = context.getString(R.string.my_title); customResult.screenTitle = context.getString(R.string.my_screen_title); rawData.add(customResult); return rawData; } @Override public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled) { List<SearchIndexableRaw> rawData = new ArrayList<>(); SearchIndexableRaw customResult = new SearchIndexableRaw(context); if (hasIndexData()) { customResult.key = KEY_CUSTOM_RESULT; customResult.title = context.getString(R.string.my_title); customResult.screenTitle = context.getString(R.string.my_screen_title); } rawData.add(customResult); return rawData; } }; }
Indexando configurações injetadas
Visão geral
Para injetar uma configuração a ser indexada:
- Defina um
SearchIndexablesProvider
para seu aplicativo estendendo a classeandroid.provider.SearchIndexablesProvider
. - Atualize o
AndroidManifest.xml
do aplicativo com o provedor na Etapa 1. O formato é:<provider android:name="PROVIDER_CLASS_NAME" android:authorities="PROVIDER_AUTHORITY" android:multiprocess="false" android:grantUriPermissions="true" android:permission="android.permission.READ_SEARCH_INDEXABLES" android:exported="true"> <intent-filter> <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" /> </intent-filter> </provider>
- Adicione dados indexáveis ao seu provedor. A implementação depende das necessidades da aplicação. Dois tipos de dados diferentes podem ser indexados:
SearchIndexableResource
eSearchIndexableRaw
.
Exemplo de provedor SearchIndexables
public class SearchDemoProvider extends SearchIndexablesProvider { /** * Key for Auto brightness setting. */ public static final String KEY_AUTO_BRIGHTNESS = "auto_brightness"; /** * Key for my magic preference. */ public static final String KEY_MY_PREFERENCE = "my_preference_key"; /** * Key for my custom search result. */ public static final String KEY_CUSTOM_RESULT = "custom_result_key"; private String mPackageName; @Override public boolean onCreate() { mPackageName = getContext().getPackageName(); return true; } @Override public Cursor queryXmlResources(String[] projection) { MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS); cursor.addRow(getResourceRow(R.xml.demo_xml)); return cursor; } @Override public Cursor queryRawData(String[] projection) { MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS); Context context = getContext(); Object[] raw = new Object[INDEXABLES_RAW_COLUMNS.length]; raw[COLUMN_INDEX_RAW_TITLE] = context.getString(R.string.my_title); raw[COLUMN_INDEX_RAW_SUMMARY_ON] = context.getString(R.string.my_summary); raw[COLUMN_INDEX_RAW_KEYWORDS] = context.getString(R.string.my_keywords); raw[COLUMN_INDEX_RAW_SCREEN_TITLE] = context.getString(R.string.my_screen_title); raw[COLUMN_INDEX_RAW_KEY] = KEY_CUSTOM_RESULT; raw[COLUMN_INDEX_RAW_INTENT_ACTION] = Intent.ACTION_MAIN; raw[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = mPackageName; raw[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = MyDemoFragment.class.getName(); cursor.addRow(raw); return cursor; } @Override public Cursor queryDynamicRawData(String[] projection) { MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS); DemoObject object = getDynamicIndexData(); Object[] raw = new Object[INDEXABLES_RAW_COLUMNS.length]; raw[COLUMN_INDEX_RAW_KEY] = object.key; raw[COLUMN_INDEX_RAW_TITLE] = object.title; raw[COLUMN_INDEX_RAW_KEYWORDS] = object.keywords; raw[COLUMN_INDEX_RAW_INTENT_ACTION] = object.intentAction; raw[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = object.mPackageName; raw[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = object.className; cursor.addRow(raw); return cursor; } @Override public Cursor queryNonIndexableKeys(String[] projection) { MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS); cursor.addRow(getNonIndexableRow(KEY_AUTO_BRIGHTNESS)); if (!Utils.isMyPreferenceAvailable) { cursor.addRow(getNonIndexableRow(KEY_MY_PREFERENCE)); } return cursor; } private Object[] getResourceRow(int xmlResId) { Object[] row = new Object[INDEXABLES_XML_RES_COLUMNS.length]; row[COLUMN_INDEX_XML_RES_RESID] = xmlResId; row[COLUMN_INDEX_XML_RES_ICON_RESID] = 0; row[COLUMN_INDEX_XML_RES_INTENT_ACTION] = Intent.ACTION_MAIN; row[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = mPackageName; row[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = SearchResult.class.getName(); return row; } private Object[] getNonIndexableRow(String key) { final Object[] ref = new Object[NON_INDEXABLES_KEYS_COLUMNS.length]; ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] = key; return ref; } private DemoObject getDynamicIndexData() { if (hasIndexData) { DemoObject object = new DemoObject(); object.key = "demo key"; object.title = "demo title"; object.keywords = "demo, keywords"; object.intentAction = "com.demo.DYNAMIC_INDEX"; object.packageName = "com.demo"; object.className = "DemoClass"; return object; } } }