Desenvolvimento de apps

Para implementar um aplicativo de interação por voz (VIA, na sigla em inglês), siga estas etapas:

  1. Crie um esqueleto do VIA.
  2. (opcional) Implemente um fluxo de configuração/login.
  3. (Opcional) Implemente uma tela de configurações.
  4. Declare as permissões necessárias no arquivo de manifesto.
  5. Implemente uma interface de placa de voz.
  6. Implemente o reconhecimento de voz (deve incluir a implementação da API RecognitionService).
  7. Implemente a expressão (opcionalmente, você pode implementar a API TextToSpeech).
  8. Implemente o fulfillment de comandos. Consulte este conteúdo em Execução de comandos.

As seções a seguir descrevem como concluir cada etapa mencionada acima.

Criar um esqueleto do VIA

Manifestos

Um app é detectado como um com interação por voz quando o seguinte é incluído no manifesto:

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myvoicecontrol">
    ...

  <application ... >
    <service android:name=".MyInteractionService"
        android:label="@string/app_name"
        android:permission="android.permission.BIND_VOICE_INTERACTION"
        android:process=":interactor">
      <meta-data
          android:name="android.voice_interaction"
          android:resource="@xml/interaction_service" />
      <intent-filter>
        <action android:name=
          "android.service.voice.VoiceInteractionService" />
      </intent-filter>
    </service>
  </application>
</manifest>

Neste exemplo:

  • As VIAs precisam expor um serviço que estenda VoiceInteractionService, com um filtro de intent para a ação VoiceInteractionService.SERVICE_INTERFACE ("android.service.voice.VoiceInteractionService").
  • Esse serviço precisa ter a permissão de assinatura do sistema BIND_VOICE_INTERACTION.
  • Esse serviço precisa incluir um arquivo de metadados android.voice_interaction com o seguinte:

    res/xml/interaction_service.xml

    <voice-interaction-service
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:sessionService=
          "com.example.MyInteractionSessionService"
        android:recognitionService=
          "com.example.MyRecognitionService"
        android:settingsActivity=
          "com.example.MySettingsActivity"
        android:supportsAssist="true"
        android:supportsLaunchVoiceAssistFromKeyguard="true"
        android:supportsLocalInteraction="true" />

Para detalhes sobre cada campo, consulte R.styleable#VoiceInteractionService. Como todas as VIAs também são serviços de reconhecimento de voz, inclua o seguinte no manifesto:

AndroidManifest.xml

<manifest ...>
  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
  <application ...>
    ...
    <service android:name=".RecognitionService" ...>
      <intent-filter>
        <action android:name="android.speech.RecognitionService" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
      <meta-data
        android:name="android.speech"
        android:resource="@xml/recognition_service" />
    </service>
  </application>
</manifest>

Os serviços de reconhecimento de voz também exigem os seguintes metadados:

res/xml/recognition_service.xml

<recognition-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsActivity="com.example.MyRecognizerSettingsActivity" />

VoiceInteractionService, VoiceInteractionSessionService e VoiceInteractionSession

O diagrama a seguir mostra o ciclo de vida de cada uma dessas entidades:

Lifecycle

Figura 1. Lifecycle

Como mencionado antes, VoiceInteractionService é o ponto de entrada de uma VIA. As principais responsabilidades desse serviço são:

  • Inicialize todos os processos que precisam ficar em execução enquanto essa VIA estiver ativa. Por exemplo, a detecção de hotword.
  • Informa os comandos de voz compatíveis (consulte Tocar para ler do Google Assistente).
  • Iniciar sessões de interação por voz na tela de bloqueio (keyguard).

Na forma mais simples, uma implementação de VoiceInteractionService teria esta aparência:

public class MyVoiceInteractionService extends VoiceInteractionService {
    private static final List<String> SUPPORTED_VOICE_ACTIONS =
        Arrays.asList(
            CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION,
            CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION,
            CarVoiceInteractionSession.VOICE_ACTION_HANDLE_EXCEPTION
    );

    @Override
    public void onReady() {
        super.onReady();
        // TODO: Setup hotword detector
    }

    @NonNull
    @Override
    public Set<String> onGetSupportedVoiceActions(
            @NonNull Set<String> voiceActions) {
        Set<String> result = new HashSet<>(voiceActions);
        result.retainAll(SUPPORTED_VOICE_ACTIONS);
        return result;
    }
    ...
}

A implementação de VoiceInteractionService#onGetSupportedVoiceActions() é necessária para processar o Tocar para ler do Assistente de voz. Um VoiceInteractionSessionService é usado pelo sistema para criar e interagir com um VoiceInteractionSession. Ele tem apenas uma responsabilidade: iniciar novas sessões quando solicitado.

public class MyVoiceInteractionSessionService extends VoiceInteractionSessionService {
    @Override
    public VoiceInteractionSession onNewSession(Bundle args) {
        return new MyVoiceInteractionSession(this);
    }
}

Por fim, uma VoiceInteractionSession é onde a maior parte do trabalho seria feita. Uma única instância de sessão pode ser reutilizada para concluir várias interações do usuário. No AAOS, existe um auxiliar CarVoiceInteractionSession , que ajuda a implementar algumas das funcionalidades exclusivas do setor automotivo.

public class MyVoiceInteractionSession extends CarVoiceInteractionSession {

    public InteractionSession(Context context) {
        super(context);
    }

    @Override
    protected void onShow(String action, Bundle args, int showFlags) {
        closeSystemDialogs();
        // TODO: Unhide UI and update UI state
        // TODO: Start processing audio input
    }
    ...
}

O VoiceInteractionSession tem um grande conjunto de métodos de callback que são explicados nas seções a seguir. Consulte a documentação do VoiceInteractionSession para ver uma lista completa.

Implementar um fluxo de configuração/login

A configuração e o login podem ocorrer:

  • Durante a integração do dispositivo (assistente de configuração).
  • Durante a troca de serviço de interação por voz (Configurações).
  • No primeiro lançamento, quando o app é selecionado.

Para mais detalhes sobre a experiência do usuário recomendada e orientações visuais, consulte Assistentes pré-carregados: orientações de UX.

Configuração durante a troca de serviço de voz

O usuário sempre pode selecionar um VIA que não foi configurado corretamente. Isso pode acontecer porque:

  • O usuário pulou o assistente de configuração ou a etapa de configuração da interação por voz.
  • O usuário selecionou uma VIA diferente da configurada durante a integração do dispositivo.

Em qualquer caso, um VoiceInteractionService tem várias maneiras de incentivar o usuário a concluir a configuração:

  • Lembrete de notificação.
  • Resposta automática por voz quando o usuário tenta usar.

Observação: não é recomendado apresentar um fluxo de configuração do VIA sem uma solicitação explícita do usuário. Isso significa que as VIAs não devem mostrar conteúdo automaticamente na HU durante a inicialização do dispositivo ou como resultado de uma troca ou desbloqueio de usuário.

Lembrete de notificação

Um lembrete de notificação é uma maneira não intrusiva de indicar a necessidade de configuração e oferecer aos usuários uma oportunidade de navegar no fluxo de configuração do Google Assistente.

Lembrete de notificação

Figura 2. Lembrete de notificação

Veja como esse fluxo funcionaria:

Fluxo de lembrete de notificação

Figura 3. Fluxo de lembrete de notificação

Responder por voz

Esse é o fluxo mais simples de implementar, iniciando uma expressão em um callback de VoiceInteractionSession#onShow(), explicando ao usuário o que precisa ser feito e perguntando a ele (se a configuração for permitida devido ao estado de restrição de UX) se ele quer iniciar o fluxo de configuração. Se não for possível fazer a configuração no momento, explique a situação.

Configuração no primeiro uso

O usuário sempre pode acionar uma VIA que não foi configurada corretamente. Nesses casos:

  1. Informe o usuário verbalmente sobre essa situação (por exemplo, "Para funcionar corretamente, preciso que você conclua algumas etapas… ").
  2. Se o mecanismo de restrições de UX permitir (consulte UX_RESTRICTIONS_NO_SETUP), pergunte ao usuário se ele quer iniciar o processo de configuração e abra a tela de configurações do VIA.
  3. Caso contrário (por exemplo, se o usuário estiver dirigindo), deixe uma notificação para que ele clique na opção quando for seguro.

Criar telas de configuração de interação por voz

As telas de configuração e login precisam ser desenvolvidas como atividades regulares. Consulte as diretrizes visuais e de UX para o desenvolvimento da interface em Assistentes pré-carregados: orientação de UX.

Diretrizes gerais:

  • Os assistentes virtuais de voz precisam permitir que os usuários interrompam e retomem a configuração a qualquer momento.
  • A configuração não pode ser permitida se a restrição UX_RESTRICTIONS_NO_SETUP estiver em vigor. Para mais detalhes, consulte as Diretrizes sobre distração do motorista.
  • As telas de configuração precisam corresponder ao sistema de design de cada veículo. O layout geral da tela, os ícones, as cores e outros aspectos precisam ser consistentes com o restante da interface. Consulte Personalização para mais detalhes.

Implementar uma tela de configurações

Integração de configurações

Figura 4. Integração de configurações

As telas de configurações são atividades regulares do Android. Se implementado, o ponto de entrada precisa ser declarado no res/xml/interaction_service.xml como parte dos manifestos da VIA (consulte Manifestos). A seção "Configurações" é um bom lugar para continuar a configuração e o login (se o usuário não tiver concluído isso) ou oferecer uma opção de sair ou trocar de usuário, se necessário. Assim como as telas de configuração descritas acima, estas telas precisam:

  • Ofereça a opção de sair e voltar à tela anterior na pilha de telas (por exemplo, para "Configurações do carro").
  • Não pode ser usado ao dirigir. Para mais detalhes, consulte as Diretrizes sobre distração do motorista.
  • Associe cada sistema de design de veículo. Para mais detalhes, consulte Personalização.

Declare as permissões necessárias no arquivo de manifesto

As permissões exigidas por um VIA podem ser divididas em três categorias:

  • Permissões de assinatura do sistema. Essas são permissões concedidas apenas a APKs pré-instalados e assinados pelo sistema. Os usuários não podem conceder essas permissões. Somente os OEMs podem fazer isso ao criar as imagens do sistema. Para mais informações sobre como receber permissões de assinatura, consulte Conceder permissões privilegiadas do sistema.
  • Permissões perigosas. São permissões que um usuário precisa conceder usando a caixa de diálogo PermissionsController. Os OEMs podem pré-conceder algumas dessas permissões ao VoiceInteractionService padrão. Mas, como esse padrão pode mudar de dispositivo para dispositivo, os apps precisam poder solicitar essas permissões quando necessário.
  • Outras permissões. São todas as outras permissões que não exigem intervenção do usuário. Essas permissões são concedidas automaticamente pelo sistema.

Com base no exposto acima, a seção a seguir se concentra apenas em solicitar permissões perigosas. As permissões só devem ser solicitadas enquanto o usuário estiver nas telas de login ou de configurações.

Se o app não tiver as permissões necessárias para operar, o fluxo recomendado é usar uma expressão de voz para explicar a situação ao usuário e uma notificação para fornecer uma ação que o usuário possa usar para voltar às telas de configurações da VIA. Para mais detalhes, consulte 1. Lembrete de notificação.

Solicitar permissões como parte da tela de configurações

As permissões perigosas são solicitadas usando o método ActivityCompat#requestPermission() regular (ou equivalente). Para mais detalhes sobre como solicitar permissões, consulte Solicitar permissões do app.

Solicitar permissões

Figura 5. Solicitar permissões

Permissão de listener de notificações

Para implementar o fluxo de TTR, as VIAs precisam ser designadas como um listener de notificação. Essa não é uma permissão em si, mas uma configuração que permite que o sistema envie notificações para listeners registrados. Para saber se o VIA recebeu acesso a essas informações, os apps podem:

Se esse acesso não for concedido previamente, o VIA vai direcionar o usuário para a seção "Acesso a notificações" das configurações do carro usando uma combinação de frases e notificações. O código a seguir pode ser usado para abrir a seção apropriada do app de configurações:

private void requestNotificationListenerAccess() {
    Intent intent = new Intent(Settings
        .ACTION_NOTIFICATION_LISTENER_SETTINGS);
    intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
    startActivity(intent);
}

Implementar uma interface de placa de voz

Quando um VoiceInteractionSession recebe um callback onShow(), ele pode apresentar uma interface de placa de voz. Para conferir diretrizes visuais e de UX sobre a implementação de placas de voz,consulte Assistentes pré-carregados: orientações de UX.

Como mostrar a placa de voz

Figura 6. Como mostrar a placa de voz

Há duas opções para implementar essa interface:

  • Substituir VoiceInteractionSession#onCreateContentView()
  • Iniciar uma atividade usando VoiceInteractionSession#startAssistantActivity()

Usar onCreateContentView()

Essa é a forma padrão de apresentar uma placa de voz. A classe base VoiceInteractionSession cria uma janela e gerencia o ciclo de vida dela enquanto uma sessão de voz estiver ativa. Os apps precisam substituir VoiceInteractionSession#onCreateContentView() e retornar uma visualização anexada a essa janela assim que a sessão for criada. Essa visualização precisa estar invisível inicialmente. Quando uma interação por voz começa, essa visualização precisa ficar visível em VoiceInteractionSession#onShow() e depois invisível novamente em VoiceInteractionSession#onHide().

public class MyVoiceInteractionSession extends CarVoiceInteractionSession {
    private View mVoicePlate;
    

    @Override
    public View onCreateContentView() {
        mVoicePlate = inflater.inflate(R.layout.voice_plate, null);
        
   }

    @Override
    protected void onShow(String action, Bundle args, int showFlags) {
        // TODO: Update UI state to "listening"
        mVoicePlate.setVisibility(View.VISIBLE);
    }

    @Override
    public void onHide() {
        mVoicePlate.setVisibility(View.GONE);
    }
    
}

Ao usar esse método, talvez seja necessário ajustar VoiceInteractionSession#onComputeInsets() para considerar regiões obscurecidas da sua interface.

Usar startAssistantActivity()

Nesse caso, VoiceInteractionSession delega o processamento da interface do teclado de voz a uma atividade regular. Quando essa opção é usada, uma implementação de VoiceInteractionSession precisa desativar a criação da janela de conteúdo padrão (consulte Usar onCreateContentView()) no callback de onPrepareShow(). Em VoiceInteractionSession#onShow(), a sessão iniciaria a atividade de placa de voz usando VoiceInteractionSession#startAssistantActivity(). Esse método inicia a interface com as configurações de janela e flags de atividade adequadas.

public class MyVoiceInteractionSession extends CarVoiceInteractionSession {
    

    @Override
    public void onPrepareShow(Bundle args, int showFlags) {
        super.onPrepareShow(args, showFlags);
        setUiEnabled(false);
    }

    @Override
    protected void onShow(String action, Bundle args, int showFlags) {
        closeSystemDialogs();
        Intent intent = new Intent(getContext(), VoicePlateActivity.class);
        intent.putExtra(VoicePlateActivity.EXTRA_ACTION, action);
        intent.putExtra(VoicePlateActivity.EXTRA_ARGS, args);
        startAssistantActivity(intent);
    }

    
}

Para manter uma comunicação entre essa atividade e o VoiceInteractionSession, um conjunto de intents internos ou vinculação de serviço pode ser necessário. Por exemplo, quando VoiceInteractionSession#onHide() é invocado, a sessão precisa conseguir transmitir essa solicitação para a atividade.

Importante. No Automotive, apenas atividades especialmente anotadas ou listadas na "lista de permissões" de UXR podem ser mostradas enquanto o veículo está em movimento. Isso também se aplica a atividades iniciadas com VoiceInteractionSession#startAssistantActivity(). Não se esqueça de anotar sua atividade com <meta-data android:name="distractionOptimized" android:value="true"/> ou incluir essa atividade na chave systemActivityWhitelist do arquivo /packages/services/Car/service/res/values/config.xml. Para mais informações, consulte as Diretrizes sobre distração do motorista.

Implementar o reconhecimento de voz

Nesta seção, você vai aprender a implementar o reconhecimento de voz detectando e reconhecendo palavras-gatilho. Uma palavra-chave é uma palavra de ativação usada para iniciar uma nova consulta ou ação por voz. Por exemplo, "Ok Google" ou "Hey Google".

Detecção de hotword da DSP

O Android oferece acesso a um detector de hotword sempre ativo no nível do DSP por meio do AlwaysOnHotwordDetector. maneira de implementar a detecção de hotword com baixa utilização da CPU. O uso dessa funcionalidade é dividido em duas partes:

A implementação do VoiceInteractionService pode criar um detector de palavra de ativação usando VoiceInteractionService#createAlwaysOnHotwordDetector(), transmitindo uma frase-chave e uma localidade que serão usadas para a detecção. Como resultado, o app recebe um callback onAvailabilityChanged() com um dos seguintes valores possíveis:

  • STATE_HARDWARE_UNAVAILABLE. A capacidade da DSP não está disponível no dispositivo. Nesse caso, a detecção de hotword por software é usada.
  • STATE_HARDWARE_UNSUPPORTED. O suporte a DSP não está disponível em geral, mas o DSP não é compatível com a combinação de frase-chave e localidade especificada. O app pode usar a detecção de hotword por software.
  • STATE_HARDWARE_ENROLLED. A detecção de hotword está pronta e pode ser iniciada chamando o método startRecognition().
  • STATE_HARDWARE_UNENROLLED. Um modelo de som para a frase-chave solicitada não está disponível, mas é possível fazer o registro.

O registro de modelos de som de detecção de hotword pode ser feito usando IVoiceInteractionManagerService#updateKeyphraseSoundModel(). Vários modelos podem ser registrados no sistema ao mesmo tempo, mas apenas um modelo é associado a um AlwaysOnHotwordDetector. A detecção de hotword do DSP pode não estar disponível em todos os dispositivos. Os desenvolvedores de VIA precisam verificar os recursos de hardware usando o método getDspModuleProperties(). Para um exemplo de código que mostra como registrar modelos de som, consulte VoiceEnrollment/src/com/android/test/voiceenrollment/EnrollmentUtil.java. Consulte Captura simultânea sobre o reconhecimento simultâneo de hotwords.

Detecção de hotword de software

Como indicado acima, a detecção de palavra-chave ativa do DSP pode não estar disponível em todos os dispositivos. Por exemplo, o emulador do Android não oferece emulação de DSP. Nesse caso, o reconhecimento de voz por software é a única alternativa. Para evitar interferências com outros apps que possam precisar de acesso ao microfone, as VIAs precisam acessar a entrada de áudio usando:

As duas constantes são @hide e estão disponíveis apenas para apps agrupados.

Gerenciar entrada de áudio e reconhecimento de voz

A entrada de áudio seria implementada usando a classe MediaRecorder. Para mais informações sobre como usar essa API, consulte a visão geral do MediaRecorder. Os serviços de interação por voz também precisam ser implementações de classe RecognitionService. Qualquer app no sistema que exija reconhecimento de voz usa o para acessar essa funcionalidade. Para fazer reconhecimento de voz e ter acesso ao microfone, as VIAs precisam manter android.permission.RECORD_AUDIO. Os apps que acessam uma implementação de RecognitionService também precisam ter essa permissão.

Antes do Android 10, o acesso ao microfone era concedido a apenas um app por vez, exceto para a detecção de palavra-chave (consulte acima). A partir do Android 10, o acesso ao microfone pode ser compartilhado. Para mais informações, consulte Compartilhamento de entrada de áudio.

Acessar a saída de áudio

Quando a VIA estiver pronta para dar respostas verbais, siga estas diretrizes: