Recurso "Toque para ler" do assistente de voz

O Android Automotive considera a voz um componente crucial para interações seguras ao dirigir e uma das maneiras mais seguras de os usuários interagiarem com o Android Automotive OS enquanto dirigem. Como resultado, ampliamos as APIs do Assistente por voz do Android (incluindo VoiceInteractionSession) para permitir que os assistentes por voz realizem tarefas para os usuários que podem ser difíceis de realizar enquanto dirigem.

O recurso Tocar para ler permite que os assistentes de voz leiam e respondam a mensagens de texto em nome do usuário quando ele interage com as notificações de mensagens. Para oferecer essa funcionalidade, é possível integrar um assistente de voz com CarVoiceInteractionSession.

No Automotive, as notificações postadas na Central de notificações identificadas como INBOX ou INBOX_IN_GROUP (por exemplo, mensagens SMS) incluem um botão Tocar. O usuário pode clicar em Play para que o assistente de voz selecionado leia a notificação em voz e, opcionalmente, responda por voz.

Notificação "Toque para ler"

Figura 1. Notificação "Toque para ler" com botão "Abrir".

Integrar com CarVoiceInteractionSession

As próximas seções descrevem como integrar um assistente de voz com CarVoiceInteractionSession.

Oferecer suporte a interações por voz

Os apps que oferecem serviços de interação por voz no carro precisam se integrar às interações por voz atuais do Android. Para saber mais, consulte Google Assistente para Android (com exceção de VoiceInteractionSession). Embora todos os elementos da API de interação por voz continuem os mesmos implementados em dispositivos móveis, CarVoiceInteractionSession (descrito em Implementar CarVoiceInteractionSession) substitui VoiceInteractionSession. Para ver mais informações, consulte estas páginas:

Implementar a CarVoiceInteractionSession

CarVoiceInteractionSession expõe APIs que podem ser usadas para permitir que assistentes de voz leiam mensagens de texto em voz alta e respondam a essas mensagens em nome do usuário.

A principal diferença entre as classes CarVoiceInteractionSession e VoiceInteractionSession é que CarVoiceInteractionSession transmite a ação em onShow para que o assistente de voz possa detectar o contexto da solicitação do usuário assim que CarVoiceInteractionSession inicia uma sessão. Os parâmetros de onShow para cada classe estão listados na tabela a seguir:

CarVoiceInteractionSession VoiceInteractionSession
onShow usa estes três parâmetros:
  • args
  • showFlags
  • actions
onShow usa estes dois parâmetros:
  • args
  • showFlags

Mudanças no Android 10

A partir do Android 10, a plataforma chama VoiceInteractionService.onGetSupportedVoiceActions para detectar quais ações são compatíveis. O assistente de voz substitui e implementa VoiceInteractionService.onGetSupportedVoiceActions, conforme mostrado no exemplo a seguir:

public class MyInteractionService extends VoiceInteractionService {
    private static final List SUPPORTED_VOICE_ACTIONS = Arrays.asList(
        CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION);

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

As ações válidas são descritas na tabela a seguir. Para saber mais sobre cada ação, consulte Diagramas de sequência.

Ação Payload esperado Ação de interação por voz esperada
VOICE_ACTION_READ_NOTIFICATION Leia as mensagens em voz alta para o usuário e, em seguida, envie a intent "Marcar como lida" quando as mensagens forem lidas. Opcionalmente, peça ao usuário para responder.
VOICE_ACTION_REPLY_NOTIFICATION Parcelável com chave.
KEY_NOTIFICATION que é mapeado para StatusBarNotification.
Exige android.permission.BIND_NOTIFICATION_LISTENER_SERVICE.
Peça ao usuário para declarar a mensagem de resposta, insira a mensagem de resposta em RemoteInputReply da intent pendente e, em seguida, dispare a intent pendente.
VOICE_ACTION_HANDLE_EXCEPTION String com chave.
KEY_EXCEPTION que é mapeado para ExceptionValue (descrito em Valores de exceção).
KEY_FALLBACK_ASSISTANT_ENABLED que mapeia para um valor booleano. Se o valor for true, o assistente alternativo que pode processar a solicitação do usuário foi desativado.
A ação esperada para a exceção é definida na documentação dela.

Valores de exceção

EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING indica ao assistente de voz que a permissão Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE está faltando e que ele precisa conseguir essa permissão do usuário.

Solicitar permissão de listener de notificação

Se o assistente de voz padrão não tiver a permissão de listener de notificação, o FallbackAssistant da plataforma (se ativado pelo fabricante do carro) poderá ler a mensagem em voz alta antes que o assistente de voz seja notificado para solicitar a permissão. Para determinar se FallbackAssistant está ativado e leu a mensagem, o assistente de voz precisa verificar o valor booleano KEY_FALLBACK_ASSISTANT_ENABLED no payload.

A plataforma recomenda que o assistente por voz adicione uma lógica de limitação de taxa para o número de vezes que essa permissão é solicitada. Isso respeita o usuário que não quer conceder essa permissão ao assistente de voz e prefere que o FallbackAssistant leia as mensagens de texto em voz alta. Solicitar permissão a um usuário toda vez que ele pressiona Play em uma notificação de mensagem pode ser uma experiência negativa para o usuário. A plataforma não impõe limites de taxa em nome do assistente por voz.

Ao solicitar a permissão do listener de notificação, o assistente de voz precisa usar CarUxRestrictionsManager para determinar se o usuário está estacionado ou dirigindo. Se o usuário estiver dirigindo, o assistente de voz vai mostrar uma notificação com instruções sobre como conceder a permissão. Isso ajuda (e lembra) o usuário de conceder a permissão quando for mais seguro.

Trabalhar com StatusBarNotification

O StatusBarNotification transmitido com as ações de voz "Ler" e "Responder" está sempre em uma notificação de mensagens compatível com o carro, conforme descrito em Notificar os usuários sobre mensagens. Embora algumas notificações não tenham a intent "Reply Pending", todas têm intents pendentes de "Mark as Read".

Para agilizar as interações com notificações, use NotificationPayloadHandler, que fornece métodos para extrair mensagens da notificação e gravar as mensagens de resposta na intent pendente adequada da notificação. Depois que o assistente de voz ler a mensagem, ele precisa acionar a intent Marcar como lida.

Atender às condições prévias do recurso "Toque para ler"

Apenas o VoiceInteractionSession do assistente de voz padrão é notificado quando um usuário aciona a ação de voz para ler e responder a mensagens. Como mencionado acima, esse assistente de voz padrão também precisa ter a permissão do listener de notificação.

Diagramas de sequência

Estas figuras mostram os fluxos lógicos de CarVoiceInteractionSession actions:

VOICE_ACTION_READ_NOTIFICATION

Figura 2. Diagrama de sequência para VOICE_ACTION_READ_NOTIFICATION.

No caso da Figura 3, o app de limites de taxa em solicitações de permissão é recomendado:

VOICE_ACTION_REPLY_NOTIFICATION

Figura 3. Diagrama de sequência para VOICE_ACTION_REPLY_NOTIFICATION.

VOICE_ACTION_HANDLE_EXCEPTION

Figura 4. Diagrama de sequência para VOICE_ACTION_HANDLE_EXCEPTION.

Ler o nome do app

Se você quiser que o assistente de voz leia o nome do app de mensagens em voz durante a leitura da mensagem (por exemplo, "Sam do Hangouts disse..."), crie uma função como a mostrada no exemplo de código abaixo para garantir que o assistente esteja lendo o nome correto:

@Nullable
String getMessageApplicationName(Context context, StatusBarNotification statusBarNotification) {
    ApplicationInfo info = getApplicationInfo(context, statusBarNotification.getPackageName());
    if (info == null) return null;

    Notification notification = statusBarNotification.getNotification();

    // Sometimes system packages will post on behalf of other apps, so check this
    // field for a system app notification.
    if (isSystemApp(info)
            && notification.extras.containsKey(Notification.EXTRA_SUBSTITUTE_APP_NAME)) {
        return notification.extras.getString(Notification.EXTRA_SUBSTITUTE_APP_NAME);
    } else {
        PackageManager pm = context.getPackageManager();
        return String.valueOf(pm.getApplicationLabel(info));
    }
}

@Nullable
ApplicationInfo getApplicationInfo(Context context, String packageName) {
    final PackageManager pm = context.getPackageManager();
    ApplicationInfo info;
    try {
        info = pm.getApplicationInfo(packageName, 0);
    } catch (PackageManager.NameNotFoundException e) {
        return null;
    }
    return info;
}

boolean isSystemApp(ApplicationInfo info) {
    return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}