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

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

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

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

Tap-to-Read 通知

図 1. Tap-to-Read 通知と [再生] ボタン

CarVoiceInteractionSession と統合する

次のセクションでは、音声アシスタントを CarVoiceInteractionSession と統合する方法について説明します。

音声操作をサポートする

車の音声操作サービスを提供するアプリは、既存の Android 音声操作と統合しなければなりません。詳しくは、Android 版 Google アシスタントをご覧ください(ただし VoiceInteractionSession は例外です)。音声操作 API のすべての要素はモバイル デバイスに実装されているものと同じままですが、CarVoiceInteractionSessionCarVoiceInteractionSession を実装するを参照)は VoiceInteractionSession を置き換えます。詳細については、次のページをご覧ください。

CarVoiceInteractionSession を実装する

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

CarVoiceInteractionSession クラスと VoiceInteractionSession クラスの主な違いは、CarVoiceInteractionSessiononShow のアクションを渡すため、CarVoiceInteractionSession がセッションを開始するとすぐに音声アシスタントがユーザー リクエストのコンテキストを検出できる点です。次の表に、各クラスの onShow のパラメータをまとめます。

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。
KEY_NOTIFICATION にマッピングする StatusBarNotification
android.permission.BIND_NOTIFICATION_LISTENER_SERVICE が必要。
ユーザーに返信メッセージを求め、ペンディング インテントの RemoteInputReply に返信メッセージを入力し、ペンディング インテントを開始します。
VOICE_ACTION_HANDLE_EXCEPTION キーを伴う文字列。
KEY_EXCEPTION にマッピングする ExceptionValue例外値を参照)。
ブール値にマッピングする KEY_FALLBACK_ASSISTANT_ENABLED。この値が true の場合、ユーザーのリクエストを処理できるフォールバック アシスタントは無効になっています。
例外に対して行われることが想定されるアクションは、例外についてのドキュメントで定義します。

例外値

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

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

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

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

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

StatusBarNotification と連携する

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

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

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