音声アシスタント Tap-to-Read

Android Automotive では、音声が運転中の安全な操作を可能にする重要な要素であり、運転中にユーザーが Android Automotive OS を操作するための最も安全な方法であると考えます。その結果、既存の Android 音声アシスタント API(VoiceInteractionSession を含む)が拡張され、運転中に行うことが難しいタスクをユーザーが行えるようになりました。

Tap-to-Read を使用すると、ユーザーがメッセージ通知を操作するとき、音声アシスタントがユーザーの代わりにテキスト メッセージを読み取り、返信できます。この機能を提供するには、音声アシスタントを CarVoiceInteractionSession と統合します。

Tap-to-Read 通知

Automotive では、通知センターにポストされる通知(SMS メッセージなど)が INBOX または INBOX_IN_GROUP の通知として識別され、そこに再生アクション ボタンが含まれます。このボタンを使用すると、ユーザーは選択した VIA によって通知を読み上げることができます。また、必要に応じて音声で返信することもできます。

図 1. Tap-to-Read 通知

CarVoiceInteractionSession と統合する

1. VoiceInteractions を支援する

車の音声操作サービスを提供するアプリは、既存の Android 音声操作VoiceInteractionSession を除く)と統合する必要があります。音声操作 API のその他のコンポーネントはすべて、モバイル デバイスに実装されているものと同じままですが、CarVoiceInteractionSession(後述)は VoiceInteractionSession を置き換えます。詳しくは、次の記事をご覧ください。

2. CarVoiceInteractionSession を実装する

CarVoiceInteractionSession は、音声アシスタントがユーザーの代わりにテキスト メッセージを読み上げて返信できるようにするために使用できる API を公開します。

CarVoiceInteractionSessionVoiceInteractionSession を比較した場合の主な違いは、CarVoiceInteractionSessiononShow のアクションを渡すため、CarVoiceInteractionSession がセッションを開始するとすぐに音声アシスタントがユーザー リクエストのコンテキストを検出できることです。

CarVoiceInteractionSession VoiceInteractionSession
onShow が取るパラメータは次の 3 つです。
  • args
  • showFlags
  • actions
onShow が取るパラメータは次の 2 つです。
  • args
  • showFlags

Android 10 での変更点

Android 10 以降、プラットフォームは VoiceInteractionService.onGetSupportedVoiceActions を呼び出し、サポートされているアクションを検出します。音声アシスタントは、次のように VoiceInteractionService.onGetSupportedVoiceActions をオーバーライドして実装します。

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;
   }
}

有効なアクションを下表に示します。各アクションの詳細については、後述のシーケンス図をご覧ください。

アクション 予想されるペイロード 予想される音声操作アクション
VOICE_ACTION_READ_NOTIFICATION ユーザーに対してメッセージを読み上げ、メッセージが正常に読み上げられたら既読にするペンディング インテントを開始します。必要に応じて、ユーザーに返信するよう求めます。
VOICE_ACTION_REPLY_NOTIFICATION キーを伴う Parcelable。
StatusBarNotification にマッピングする KEY_NOTIFICATION
android.permission.BIND_NOTIFICATION_LISTENER_SERVICE が必要。
ユーザーに返信メッセージを求め、ペンディング インテントの RemoteInputReply に返信メッセージを入力し、ペンディング インテントを開始します。
VOICE_ACTION_HANDLE_EXCEPTION キーを伴う文字列。
ExceptionValue(後述)にマッピングする KEY_EXCEPTION
ブール値にマッピングする KEY_FALLBACK_ASSISTANT_ENABLED。この値が true の場合、ユーザーのリクエストを処理できるフォールバック アシスタントは無効になっています。
例外に対して行われることが想定されるアクションは、例外についてのドキュメントで定義します。

ExceptionValue

EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING は、Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE 権限がないことと、ユーザーからこの権限を取得することを音声アシスタントに示します。

3.通知リスナー権限をリクエストする

デフォルトの音声アシスタントに通知リスナー権限がない場合、プラットフォームの FallbackAssistant(自動車メーカーが有効にしている場合)は、権限をリクエストするよう音声アシスタントが通知される前に、メッセージを読み上げることがあります。FallbackAssistant が有効になっており、メッセージを読み上げたかどうかを判断するには、音声アシスタントでペイロードの KEY_FALLBACK_ASSISTANT_ENABLED ブール値を確認する必要があります。

プラットフォームでは、この権限がリクエストされる回数について、音声アシスタントによるレート制限ロジックを追加することをおすすめします。これにより、音声アシスタントにこの権限を付与しないユーザーや、FallbackAssistant でテキスト メッセージを読み上げることを希望するユーザーが優先されます。ユーザーがメッセージ通知で [再生] を押すたびに権限を求めると、ユーザー エクスペリエンスが低下する可能性があります。プラットフォームが音声アシスタントに代わってレート制限を課すことはありません。

通知リスナー権限をリクエストする場合、音声アシスタントは CarUxRestrictionsManager を使用して、ユーザーが駐車中か運転中かを判断する必要があります。ユーザーが運転中の場合、音声アシスタントは権限を付与する方法を示す通知を表示します。これにより、安全なときにユーザーが権限を付与できるようにします(通知します)。

4. StatusBarNotification と連携する

読み上げと返信の音声操作で渡される StatusBarNotifications は、ユーザーにメッセージを通知するに記載されているように、常に車載対応メッセージ通知機能に含まれます。一部の通知には返信ペンディング インテントがない場合もありますが、すべての通知に、既読にするペンディング インテントがあります。

通知の操作を簡素化するには、NotificationPayloadHandler を使用します。このメソッドは、通知からメッセージを抽出し、通知の適切なペンディング インテントに返信メッセージを書き込むメソッドを提供します。音声アシスタントがメッセージを読み上げた後、音声アシスタントは既読にするインテントを開始する必要があります

5. Tap-to-Read の前提条件を満たす

ユーザーが音声操作をトリガーしてメッセージの読み上げと返信を行うと、デフォルトの音声アシスタントの VoiceInteractionSession のみに通知されます。前述のとおり、このデフォルトの音声アシスタントには通知リスナー権限も必要です。

シーケンス図

次の図は、CarVoiceInteractionSession actions のロジックフローを示しています。

VOICE_ACTION_READ_NOTIFICATION

図 2. VOICE_ACTION_READ_NOTIFICATION のシーケンス図

次の図 3 の場合は、権限リクエストにレート制限を適用することをおすすめします。

VOICE_ACTION_REPLY_NOTIFICATION

図 3. VOICE_ACTION_REPLY_NOTIFICATION のシーケンス図

VOICE_ACTION_HANDLE_EXCEPTION

図 4. VOICE_ACTION_HANDLE_EXCEPTION のシーケンス図

ヒントとコツ

アプリの名前の読み上げ

メッセージの読み上げ中に音声アシスタントがメッセージ アプリの名前を読み上げるようにする場合(例:「ハングアウトで Sam は...」)、次のような関数を作成して、正しい名前を読み上げるようにする必要があります。

@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 applications, so check this
    // field for a system application 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;
}