音声操作で以下のタイプのコマンドを実行する方法を学びます。
メディア コマンドの実行
メディア関連のコマンドは次の 3 つのグループに分けることができます。
- 外部メディアソース(AAOS にインストールされている Spotify など)。
- バックエンド メディアソース(VIA 経由でストリーミングされる音楽など)
- ローカル メディアソース(カーラジオなど)。
外部メディアソース コマンドの処理
外部メディアソースは、MediaSessionCompat
API と MediaBrowseCompat
API をサポートする Android アプリとして定義されます(これらの API の詳細な使用方法については、自動車向けメディアアプリを作成するをご覧ください)。
重要: システム内にインストールされているすべてのメディアアプリの MediaBrowseService
にアシスタント アプリを接続するには、メディアアプリが次の条件を満たしている必要があります。
- システム署名済みとしてインストールされている(AAOS とサンプル
PackageValidator
コードについては、メディアアプリ開発ガイドラインをご覧ください)。 android.permission.MEDIA_CONTENT_CONTROL
システム特権を保持している(システム特権を付与するをご覧ください)。
MediaBrowserCompat と MediaControllerCompat に加えて、AAOS は以下を提供します。
CarMediaService
。現在選択されているメディアソースに関する一元情報を提供します。また、以前再生されていたメディアソースを車両の再起動後に再開するためにも使用されます。car-media-common
。メディアアプリのリスト表示、接続、操作を実行するための便利な方法を提供します。
以下に、一般的な音声操作コマンドの実装に固有のガイドラインを示します。
インストールされているメディアソースのリストの取得
メディアソースは、PackageManager を使用して、MediaBrowserService.SERVICE_INTERFACE に一致するサービスをフィルタリングすることで検出できます。車によっては特殊なメディア ブラウザ サービスが実装されている場合がありますが、これは除外する必要があります。このロジックの例を次に示します。
private Map<String, MediaSource> getAvailableMediaSources() { List<String> customMediaServices = Arrays.asList(mContext.getResources() .getStringArray(R.array.custom_media_packages)); List<ResolveInfo> mediaServices = mPackageManager.queryIntentServices( new Intent(MediaBrowserService.SERVICE_INTERFACE), PackageManager.GET_RESOLVED_FILTER); Map<String, MediaSource> result = new HashMap<>(); for (ResolveInfo info : mediaServices) { String packageName = info.serviceInfo.packageName; if (customMediaServices.contains(packageName)) { // Custom media sources should be ignored, as they might have a // specialized handling (e.g.: radio). continue; } String className = info.serviceInfo.name; ComponentName componentName = new ComponentName(packageName, className); MediaSource source = MediaSource.create(mContext, componentName); result.put(source.getDisplayName().toString().toLowerCase(), source); } return result; }
なお、メディアソースのインストールやアンインストールはいつでも可能です。正確なリストを保持するためには、ACTION_PACKAGE_ADDED、ACTION_PACKAGE_CHANGED、ACTION_PACKAGE_REPLACED、ACTION_PACKAGE_REMOVED のインテントのアクションに BroadcastReceiver を実装することをおすすめします。
現在再生中のメディアソースへの接続
CarMediaService では、現在選択されているメディアソースと、このメディアソースが変更されるタイミングを取得できます。これらの変更は、ユーザーが UI を直接操作したことや車のハードウェア ボタンが使用されたことが原因で生じる可能性があります。一方、car-media-common ライブラリを使用すると、特定のメディアソースに簡単に接続できます。選択中のメディアアプリに接続するための簡単なスニペットを以下に示します。
public class MediaActuator implements MediaBrowserConnector.onConnectedBrowserChanged { private final Car mCar; private CarMediaManager mCarMediaManager; private MediaBrowserConnector mBrowserConnector; … public void initialize(Context context) { mCar = Car.createCar(context); mBrowserConnector = new MediaBrowserConnector(context, this); mCarMediaManager = (CarMediaManager) mCar.getCarManager(Car.CAR_MEDIA_SERVICE); mBrowserConnector.connectTo(mCarMediaManager.getMediaSource()); … } @Override public void onConnectedBrowserChanged( @Nullable MediaBrowserCompat browser) { // TODO: Handle connected/disconnected browser } … }
現在再生中のメディアソースの再生の制御
MediaBrowserCompat を接続すると、ターゲット アプリに「トランスポート制御」コマンドを送信する操作が非常に簡単になります。以下に簡単な例を示します。
public class MediaActuator … { … private MediaControllerCompat mMediaController; @Override public void onConnectedBrowserChanged( @Nullable MediaBrowserCompat browser) { if (browser != null && browser.isConnected()) { mMediaController = new MediaControllerCompat(mContext, browser.getSessionToken()); } else { mMediaController = null; } } private boolean playSongOnCurrentSource(String song) { if (mMediaController == null) { // No source selected. return false; } MediaControllerCompat.TransportControls controls = mMediaController.getTransportControls(); PlaybackStateCompat state = controller.getPlaybackState(); if (state == null || ((state.getActions() & PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH) == 0)) { // Source can't play from search return false; } controls.playFromSearch(query, null); return true; } … }
ローカル メディアソース コマンドの処理(ラジオ、CD プレーヤー、Bluetooth、USB)
ローカル メディアソースは、前述の同じ MediaSession API と MediaBrowse API を使用してシステムに機能を公開します。各タイプのハードウェアの特性に対応するために、MediaBrowsing サービスは特定の規則を使用して情報やメディア コマンドを整理します。
ラジオの処理
ラジオの MediaBrowseService は、ACTION_PLAY_BINDERRADIO インテント フィルタで識別できます。これらには、こちら(Media アプリ上でのラジオの実装)で説明されている再生制御とメディア ブラウズの構造への準拠が求められます。AAOS には、定数とメソッドを含む car-broadcastradio-support ライブラリが用意されており、OEM はこれらを使用することで、定義済みプロトコルに準拠する独自のラジオサービスの MediaBrowseService 実装を作成できます。また、AAOS は、ブラウズツリーを使用するアプリ(VIA など)をサポートしています。
補助入力、CD オーディオ、USB メディアの処理
AOSP には、これらのメディアソースのデフォルト実装は含まれていません。次のような方法をおすすめします。
- OEM に各メディアソースに対してメディア サービスを実装してもらいます。詳しくは、自動車向けメディアアプリを作成するをご覧ください。
- これらの MediaBrowseService の実装は、一般的な再生インテントで定義されているインテント アクションで識別され対応されます。
- これらのサービスは、他のソースタイプに記載されているガイドラインに従ってブラウズツリーを公開します。
Bluetooth の処理
Bluetooth メディア コンテンツは AVRCP Bluetooth プロファイルを通じて公開されます。この機能へのアクセスを容易にするために、AAOS には、通信の詳細を抽出する MediaBrowserService と MediaSession の実装が含まれています(packages/apps/Bluetooth を参照してください)。
それぞれのメディア ブラウザのツリー構造は、BrowseTree クラスで定義されています。再生制御コマンドは、他のアプリと同様に、MediaSession の実装を使用して配信できます。
ストリーミング メディア コマンドの処理
サーバー側のメディア ストリーミングを実装するには、VIA 自体をメディアソースにして、MediaBrowse と MediaSession API を実装する必要があります。自動車向けメディアアプリを作成するを参照してください。これらの API を実装することで、音声操作アプリでは、たとえば次のようなことが可能になります。
- メディアソースの選択にシームレスに関与する
- 車の再起動後に自動的に再開する
- メディア センター UI を使用して再生とブラウジングの制御を提供する
- 標準のハードウェア メディア ボタン イベントを受信する
ナビゲーション コマンドの実行
すべてのナビゲーション アプリを操作できる標準化された方法はありません。 Google マップとの統合については、Android Automotive インテント向け Google マップをご覧ください。他のアプリとの統合については、アプリのデベロッパーに直接お問い合わせください。アプリ(Google マップを含む)へのインテントを起動する前に、そのインテントが解決可能であることを確認してください(インテント リクエストをご覧ください)。これにより、ターゲット アプリが利用できない場合にはユーザーに通知することができます。
車両コマンドの実行
車両プロパティへのアクセスは、読み書きどちらの場合も、CarPropertyManager を通じて行われます。車両のプロパティ タイプやその実装などの詳細については、こちら(AOSP / 開発 / Automotive - 車両プロパティ)で説明されています。Android でサポートされているプロパティの正確な説明については、hardware/interfaces/automotive/vehicle/2.0/types.hal を直接確認することをおすすめします。そこで定義されている VehicleProperty 列挙型には、標準とベンダー固有の両方のプロパティ、データ型、変更モード、単位、読み書きアクセスの定義が含まれています。
これらの同じ定数に Java からアクセスするには、VehiclePropertyIds とそのコンパニオン クラスを使用できます。プロパティが異なると、アクセスを制御する Android 権限も異なります。これらの権限は CarService マニフェストで宣言されます。プロパティと権限のマッピングは、VehiclePropertyIds Javadoc で記述され、PropertyHalServiceIds で適用されます。
車両プロパティの読み取り
次の例は、車両速度の読み取り方法を示しています。
public class CarActuator ... { private final Car mCar; private final CarPropertyManager mCarPropertyManager; private final TextToSpeech mTTS; /** Global VHAL area id */ public static final int GLOBAL_AREA_ID = 0; public CarActuator(Context context, TextToSpeech tts) { mCar = Car.createCar(context); mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE); mTTS = tts; ... } @Nullable private void getSpeedInMetersPerSecond() { if (!mCarPropertyManager.isPropertyAvailable(VehiclePropertyIds.PERF_VEHICLE_SPEED, GLOBAL_AREA_ID)) { mTTS.speak("I'm sorry, but I can't read the speed of this vehicle"); return; } // Data type and unit can be found in // automotive/vehicle/2.0/types.hal float speedInMps = mCarPropertyManager.getFloatProperty( VehiclePropertyIds.PERF_VEHICLE_SPEED, GLOBAL_AREA_ID); int speedInMph = (int)(speedInMetersPerSecond * 2.23694f); mTTS.speak(String.format("Sure. Your current speed is %d miles " + "per hour", speedInUserUnit); } ... }
車両プロパティの設定
次の例は、フロント AC のオン / オフを切り替える方法を示しています。
public class CarActuator … { … private void changeFrontAC(boolean turnOn) { List<CarPropertyConfig> configs = mCarPropertyManager .getPropertyList(new ArraySet<>(Arrays.asList( VehiclePropertyIds.HVAC_AC_ON))); if (configs == null || configs.size() != 1) { mTTS.speak("I'm sorry, but I can't control the AC of your vehicle"); return; } // Find the front area Ids for the AC property. int[] areaIds = configs.get(0).getAreaIds(); List<Integer> areasToChange = new ArrayList<>(); for (int areaId : areaIds) { if ((areaId & (VehicleAreaSeat.SEAT_ROW_1_CENTER | VehicleAreaSeat.SEAT_ROW_1_LEFT | VehicleAreaSeat.SEAT_ROW_1_RIGHT)) == 0) { continue; } boolean isACInAreaAlreadyOn = mCarPropertyManager .getBooleanProperty(VehiclePropertyIds.HVAC_AC_ON, areaId); if ((!isACInAreaAlreadyOn && turnOn) || (isACInAreaAlreadyOn && !turnOn)) { areasToChange.add(areaId); } } if (areasToChange.isEmpty()) { mTTS.speak(String.format("The AC is already %s", turnOn ? "on" : "off")); return; } for (int areaId : areasToChange) { mCarPropertyManager.setBooleanProperty( VehiclePropertyIds.HVAC_AC_ON, areaId, turnOn); } mTTS.speak(String.format("Okay, I'm turning your front AC %s", turnOn ? "on" : "off")); } … }
通信コマンドの実行
メッセージング コマンドの処理
VIA は、音声アシスタント Tap-to-Read で説明している「Tap-to-Read」フローに従って着信メッセージを処理する必要があります。必要に応じて着信メッセージの送信者への返信も処理できます。 また VIA では、SmsManager(android.telephony パッケージの一部)を使用して、直接車内からまたは Bluetooth 経由で SMS メッセージを作成して送信できます。
コールコマンドの処理
同様に、VIA では TelephonyManager を使用してユーザーのボイスメール番号への呼び出しや通話を行うことができます。このような場合、VIA は、テレフォニー スタックを直接操作するか Car Dialer アプリを操作します。いずれの場合も、Car Dialer アプリは、音声通話関連の UI をユーザーに表示する必要があります。
その他のコマンドの実行
VIA とシステムの統合が行われる可能性があるその他のポイントのリストについては、広く認知されている Android インテントのリストを確認してください。多くのユーザー コマンドはサーバー側で解決できます(ユーザーのメールやカレンダーの予定の読み上げなど)。また、音声操作そのもの以外には、システムの操作を必要としません。
没入型アクション(ビジュアル コンテンツの表示)
VIA は、ユーザーのアクションまたは認識を強化する場合において、補助的なビジュアル コンテンツを車の画面に提供できます。ドライバーの注意散漫を最小限に抑えるために、こうしたコンテンツは、シンプルかつ実用的なものにしてください。没入型アクションに関する UI / UX ガイドラインの詳細については、プリロード アシスタント: UX ガイダンスをご覧ください。
他のヘッドユニット(HU)のデザインとの整合性やカスタマイズを実現するため、VIA ではほとんどの UI 要素に Car UI ライブラリ コンポーネントを使用する必要があります。詳細については、カスタマイズをご覧ください。