アプリ開発

音声操作アプリ(VIA)を実装する手順は次のとおりです。

  1. VIA スケルトンを作成します。
  2. (省略可)セットアップ / ログインフローを実装します。
  3. (省略可)設定画面を実装します。
  4. マニフェスト ファイルで必要な権限を宣言します。
  5. 音声プレート UI を実装します。
  6. 音声認識を実装します(RecognitionService API の実装を含める必要があります)
  7. 発話を実装します(必要に応じて TextToSpeech API を実装できます)。
  8. コマンド フルフィルメントを実装します。詳細については、コマンドの実行をご覧ください。

以降のセクションでは、上記の各手順を実施する方法について説明します。

VIA スケルトンを作成する

マニフェスト

マニフェストに下記が含まれている場合、アプリは音声操作機能を備えたアプリとして検出されます。

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>

この例では

  • VIA は、アクション VoiceInteractionService.SERVICE_INTERFACE ("android.service.voice.VoiceInteractionService") のインテント フィルタを使用して、VoiceInteractionService を拡張するサービスを公開する必要があります。
  • このサービスは、BIND_VOICE_INTERACTION システム署名権限を保持している必要があります。
  • このサービスには、下記を含めるために android.voice_interaction メタデータ ファイルを含める必要があります。

    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" />
    

各フィールドの詳細については、R.styleable#VoiceInteractionService をご覧ください。VIA はすべて音声認識サービスでもあることから、マニフェストに下記も含める必要があります。

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>

音声認識サービスには次のメタデータも必要です。

res/xml/recognition_service.xml

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

VoiceInteractionService、VoiceInteractionSessionService、VoiceInteractionSession

各エンティティのライフサイクルを下図に示します。

ライフサイクル

図 1. ライフサイクル

前述のとおり、VoiceInteractionService は VIA へのエントリポイントです。このサービスが担う内容は主に次のとおりです。

  • この VIA がアクティブである限り実行し続ける必要があるプロセスをすべて初期化する(起動ワード検出など)。
  • サポートされている音声操作をレポートする(音声アシスタント タップツーリードを参照)。
  • ロック画面(キーガード)から音声操作セッションを開始する。

最もシンプルな形式で、VoiceInteractionService の実装は次のようになります。

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

音声アシスタント タップツーリードを処理するには、VoiceInteractionService#onGetSupportedVoiceActions() の実装が必要です。VoiceInteractionSessionService は、VoiceInteractionSession を作成し操作するためにシステムが使用し、リクエストに応じた新しいセッションの開始のみを行います。

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

最終的に、ほとんどの処理は VoiceInteractionSession で行います。1 つのセッション インスタンスを再利用して、複数のユーザー操作を行えます。AAOS にはヘルパー CarVoiceInteractionSession があります。これにより、自動車固有の機能を実装できます。

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

VoiceInteractionSession には、以降のセクションで説明する多数のコールバック メソッドがあります。詳しくは VoiceInteractionSession のドキュメントをご覧ください。

セットアップ / ログインフローを実装する

セットアップとログインは、以下の場合に発生します。

  • デバイスのオンボーディング時(セットアップ ウィザード)。
  • 音声操作サービスの切り替え時(設定)。
  • アプリが選択されて初めて起動されたとき。

推奨されるユーザー エクスペリエンスとビジュアル ガイダンスの詳細については、プリロード アシスタント: UX ガイダンスをご覧ください。

音声サービスの切り替え時のセットアップ

ユーザーが正しく構成されていない VIA を選択する可能性は、常にあります。これには次の原因が考えられます。

  • ユーザーが設定ウィザードを完全にスキップしたか、音声操作の構成ステップをスキップした。
  • ユーザーがデバイスのオンボーディング時に構成したものとは異なる VIA を選択した。

いずれの場合でも、VoiceInteractionService には、ユーザーにセットアップの完了を促す方法がいくつかあります。

  • 通知リマインダー。
  • ユーザーが使用しようとしたときの自動音声応答。

: 明示的なユーザー リクエストなしでは VIA のセットアップ フローを提示しないことを強くおすすめします。つまり VIA では、デバイスの起動時、またはユーザーの切り替えやロック解除の結果として、HU に自動的にコンテンツが表示されないようにする必要があります。

通知リマインダー

通知リマインダーは、セットアップが必要であることを示し、アシスタントのセットアップ フローに移動するユーザー アフォーダンスを提供するための、控えめな方法です。

通知リマインダー

図 2. 通知リマインダー

このフローの仕組みは次のとおりです。

通知リマインダー フロー

図 3. 通知リマインダー フロー

音声応答

これは最も実装が簡単なフローです。VoiceInteractionSession#onShow() コールバックで発話を開始し、必要な処理をユーザーに説明してから、セットアップ フローを開始するかどうかをユーザーに確認します(UX 制限状態でセットアップが許可されている場合)。その時点でセットアップができない場合は、その状況についても説明します。

初回使用時のセットアップ

ユーザーが正しく構成されていない VIA をトリガーする可能性は、常にあります。その場合、次のようにします。

  1. 状況についてユーザーに声で伝えます(「正しく動作させるには、いくつかの手順を踏む必要があります…」など)。
  2. UX 制限エンジンで許可されている場合は(UX_RESTRICTIONS_NO_SETUP 参照)、セットアップ プロセスを開始するかどうかをユーザーに確認してから、VIA の設定画面を開きます。
  3. それ以外の場合(ユーザーが運転中の場合など)は、安全なときにユーザーがオプションをクリックできるように通知を残します。

音声操作のセットアップ画面の作成

セットアップ画面とログイン画面は、通常のアクティビティとして作成する必要があります。プリロード アシスタント: UX ガイダンスで、UI 開発のための UX とビジュアルのガイドラインをご覧ください。

全般的なガイドライン:

  • VIA では、ユーザーがいつでもセットアップを中断、再開できるようにする必要があります。
  • UX_RESTRICTIONS_NO_SETUP 制限が有効になっている場合は、セットアップを許可しないでください。詳細については、ドライバーの注意散漫に関するガイドラインをご覧ください。
  • セットアップ画面は、各車両のデザイン システムと一致する必要があります。一般的な画面レイアウト、アイコン、色、その他の要素は、UI の他の部分と一貫している必要があります。詳細については、カスタマイズをご覧ください。

設定画面を実装する

設定の統合

図 4. 設定の統合

設定画面は、通常の Android アクティビティです。実装した場合、エントリ ポイントは res/xml/interaction_service.xml で VIA マニフェストの一部として宣言する必要があります(マニフェストを参照)。設定セクションは、セットアップを続行してログインする場合や、必要に応じてログアウトするかユーザーを切り替えるオプションを提供する場合に適しています。前述のセットアップ画面と同様に、画面は次のようにする必要があります。

マニフェスト ファイルで必要な権限を宣言する

VIA に必要な権限は、次の 3 つのカテゴリに分類できます。

  • システム署名権限。プリインストールされているシステム署名済み APK にのみ付与される権限です。ユーザーはこれらの権限を付与できません。OEM のみが、システム イメージをビルドするときに付与できます。署名権限の取得の詳細については、システム特権を付与するをご覧ください。
  • 危険な権限。ユーザーが PermissionsController ダイアログを使用して付与する必要がある権限です。OEM は、これらの権限の一部をデフォルトの VoiceInteractionService に事前に付与できます。ただし、このデフォルトがデバイスによって異なる可能性があることを考慮すると、必要に応じて、アプリでこれらの権限をリクエストできるようにする必要があります。
  • その他の権限。これらはすべて、ユーザーの介入を必要としない権限です。これらの権限は、システムによって自動的に付与されます。

上記を踏まえて、次のセクションでは、危険な権限のリクエストのみに焦点を当てます。権限は、ユーザーがログイン画面または設定画面を開いているときにのみリクエストしてください。

動作に必要な権限がアプリに付与されていない場合、推奨されるフローは、音声発話を使用してユーザーに状況を説明し、通知を使用して VIA 設定画面に戻るユーザー アフォーダンスを提供することです。詳細については、1. 通知リマインダーをご覧ください。

設定画面の一部として権限をリクエストする

危険な権限は、通常の ActivityCompat#requestPermission() メソッド(または同等のもの)を使用してリクエストします。権限をリクエストする方法の詳細については、アプリの権限のリクエストをご覧ください。

権限をリクエストする

図 5. 権限をリクエストする

通知リスナー権限

TTR フローを実装するには、通知リスナーとして VIA を指定する必要があります。これは権限そのものではなく、システムが登録済みリスナーに通知を送信できるようにする設定です。VIA にこの情報へのアクセス権が付与されているかどうかを確認するには、アプリで次のようにします。

このアクセス権を事前に付与していない場合、VIA は発話と通知を組み合わせて、車の設定の [通知へのアクセス] セクションにユーザーを誘導する必要があります。次のコードを使用すると、設定アプリの該当するセクションを開くことができます。

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

音声プレート UI を実装する

onShow() コールバックを受信すると、VoiceInteractionSession は音声プレート UI を表示できます。音声プレートの実装に関するビジュアルと UX のガイドラインについては、プリロード アシスタント: UX ガイダンスをご覧ください。

音声プレートの表示

図 6. 音声プレートの表示

この UI を実装するには、次の 2 つの方法があります。

  • VoiceInteractionSession#onCreateContentView() をオーバーライドする
  • VoiceInteractionSession#startAssistantActivity() を使用してアクティビティを起動する

onCreateContentView() を使用する

音声プレートを表示するデフォルトの方法です。VoiceInteractionSession 基本クラスは、音声セッションがアクティブである間、ウィンドウを作成し、そのライフサイクルを管理します。アプリは VoiceInteractionSession#onCreateContentView() をオーバーライドして、セッション作成後すぐにそのウィンドウにアタッチされるビューを返す必要があります。このビューは、最初は表示されません。音声操作が開始されると、このビューは VoiceInteractionSession#onShow() で表示され、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);
    }
    …
}

この方法を使用する場合は、UI の隠れた領域を考慮するように VoiceInteractionSession#onComputeInsets() を調整することをおすすめします。

startAssistantActivity() を使用する

この場合、VoiceInteractionSession は音声プレート UI の処理を通常のアクティビティに委任します。このオプションを使用する場合、VoiceInteractionSession の実装では、onPrepareShow() コールバックでデフォルト コンテンツ ウィンドウの作成を無効にする必要があります(onCreateContentView() を使用するをご覧ください)。VoiceInteractionSession#onShow() で、セッションは VoiceInteractionSession#startAssistantActivity() を使用して音声プレート アクティビティを開始します。この方法では、適切なウィンドウ設定とアクティビティ フラグで UI を開始します。

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

    …
}

このアクティビティと VoiceInteractionSession の間の通信を維持するには、一連の内部インテントまたはサービス バインディングが必要になることがあります。たとえば、VoiceInteractionSession#onHide() が呼び出される場合、セッションはこのリクエストをアクティビティに渡す必要があります。

重要: Automotive では、特別なアノテーションが付けられたアクティビティか、UXR の「許可リスト」にリストされているアクティビティのみを、運転中に表示できます。これは、VoiceInteractionSession#startAssistantActivity() で始まるアクティビティにも適用されます。アクティビティに <meta-data android:name="distractionOptimized" android:value="true"/> でアノテーションを付けるか、このアクティビティを /packages/services/Car/service/res/values/config.xml ファイルの systemActivityWhitelist キーに含めます。詳細については、ドライバーの注意散漫に関するガイドラインをご覧ください。

音声認識を実装する

このセクションでは、起動ワードの検出と認識を通じて音声認識を実装する方法について説明します。起動ワードは、新しいクエリまたはアクションを音声で開始するために使用されるトリガーワードです(「OK Google」など)。

DSP 起動ワード検出

Android では AlwaysOnHotwordDetector を使用して、DSP レベルの常時オンの起動ワード検出機能にアクセスできます。これにより、低 CPU 負荷での起動ワード検出を実装できます。この機能の使用方法は、次の 2 つの部分に分割されます。

VoiceInteractionService の実装では、VoiceInteractionService#createAlwaysOnHotwordDetector() を使用して起動ワード検出機能を作成できます。その際、検出に使用するキーフレーズとロケールを渡します。その結果、アプリは次のいずれかの値を持つ onAvailabilityChanged() コールバックを受け取ります。

  • STATE_HARDWARE_UNAVAILABLE。デバイスで DSP 機能を使用できません。この場合、ソフトウェア起動ワード検出を使用します。
  • STATE_HARDWARE_UNSUPPORTED。通常は DSP サポートを利用できません。DSP は所定のキーフレーズとロケールの組み合わせをサポートしていません。アプリは、ソフトウェア起動ワード検出の使用を選択できます。
  • STATE_HARDWARE_ENROLLED。起動ワード検出の準備ができており、startRecognition() メソッドを呼び出すことで開始できます。
  • STATE_HARDWARE_UNENROLLED。リクエストされたキーフレーズのサウンドモデルは利用できませんが、登録は可能です。

起動ワード検出サウンドモデルを登録するには、IVoiceInteractionManagerService#updateKeyphraseSoundModel() を使用します。同時に複数のモデルをシステムに登録できますが、AlwaysOnHotwordDetector に関連付けられるモデルは 1 つだけです。デバイスによっては DSP 起動ワード検出を利用できない場合があります。VIA デベロッパーは、getDspModuleProperties() メソッドを使用してハードウェア機能を確認する必要があります。サウンドモデルの登録方法を示すサンプルコードについては、VoiceEnrollment/src/com/android/test/voiceenrollment/EnrollmentUtil.java をご覧ください。同時起動ワード認識については、同時キャプチャをご覧ください。

ソフトウェア起動ワード検出

前述のように、デバイスによっては DSP 起動ワード検出を利用できない場合があります(たとえば、Android Emulator では DSP エミュレーションが提供されません)。この場合、唯一の代替策はソフトウェア音声認識です。マイクへのアクセスが必要になる他のアプリに干渉しないように、VIA は下記によってオーディオ入力にアクセスする必要があります。

これらの定数はどちらも @hide であり、バンドルされたアプリでのみ使用できます。

オーディオ入力と音声認識の管理

オーディオ入力は、MediaRecorder クラス を使用して実装します。この API の使用方法について詳しくは、MediaRecorder の概要をご覧ください。音声操作サービスも RecognitionService クラスの実装であることが想定されます。音声認識を必要とするシステム内のアプリはすべて、これを使用してこの機能にアクセスします。音声認識を行い、マイクにアクセスするには、VIA が android.permission.RECORD_AUDIO を保持している必要があります。RecognitionService の実装にアクセスするアプリも、この権限を保持している必要があります。

Android 10 より前は、起動ワード検出を除き、一度に 1 つのアプリしかマイクにアクセスできませんでした(上記参照)。Android 10 以降では、マイクへのアクセスを共有できます。詳細については、音声入力の共有をご覧ください。

オーディオ出力にアクセスする

VIA で声による応答を提供する準備ができたら、次のガイドラインに従うことが重要です。