コマンドの実行

このページでは、音声対話を使用してコマンドを実行する方法について説明します。

メディアコマンドを実行する

メディア関連のコマンドは、次の 3 つの異なるグループに分割できます。

  • 外部メディア ソース (AAOS にインストールされている Spotify など)。
  • バックエンド メディア ソース (VIA 経由でストリーミングされる音楽など)。
  • 地元のメディアソース (カーラジオなど)。

外部メディアソースコマンドの処理

外部メディア ソースは、 MediaSessionCompatおよびMediaBrowseCompat API をサポートする Android アプリとして定義されます (これらの API の使用の詳細については、「自動車用メディア アプリの構築」を参照してください)。

重要:アシスタント アプリがシステム内にインストールされているすべてのメディア アプリのMediaBrowseServiceに接続するには、次のことを行う必要があります。

  1. システム署名付きでインストールされていること (AAOS のメディア アプリケーション開発ガイドラインとサンプルPackageValidatorコードを参照)。
  2. android.permission.MEDIA_CONTENT_CONTROLシステム特権権限を保持します ( 「システム特権権限を付与する」を参照)。

MediaBrowserCompatMediaControllerCompatに加えて、AAOS は以下を提供します。

  • CarMediaService現在選択されているメディア ソースに関する情報を一元的に提供します。これは、車のシャットダウンと再起動後に、以前に再生していたメディア ソースを再開するためにも使用されます。
  • car-media-commonメディア アプリを一覧表示、接続、操作するための便利な方法を提供します。

以下に、一般的な音声対話コマンドの実装に固有のガイドラインを示します。

インストールされているメディア ソースのリストを取得する

メディア ソースは、 PackageManagerMediaBrowserService.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_ADDEDACTION_PACKAGE_CHANGEDACTION_PACKAGE_REPLACED 、およびACTION_PACKAGE_REMOVEDBroadcastReceiverインスタンスを実装することをお勧めします。

現在再生中のメディアソースに接続します

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 および MediaBrowse API を使用して、その機能をシステムに公開します。ハードウェアの各タイプの特性に対応するために、これらの MediaBrowse サービスは特定の規則を使用して情報とメディア コマンドを編成します。

ハンドルラジオ

Radio MediaBrowseService は、 ACTION_PLAY_BROADCASTRADIOインテント フィルターによって識別できます。これらは、「ラジオの実装」で説明されている再生コントロールとメディア ブラウズ構造に従うことが期待されます。 AAOS は、OEM が定義されたプロトコルに従う独自の無線サービス用の MediaBrowseService 実装を作成するのに役立つ定数とメソッドを含むcar-broadcastradio-supportライブラリを提供し、ブラウズ ツリー (VIA など) を使用するアプリのサポートを提供します。

補助入力、CD オーディオ、USB メディアの処理

AOSP の一部としてこれらのメディア ソースのデフォルト実装はありません。推奨されるアプローチは次のとおりです。

ハンドルBluetooth

Bluetooth メディア コンテンツは、AVRCP Bluetooth プロファイルを通じて公開されます。この機能へのアクセスを容易にするために、AAOS には、通信の詳細を抽象化する MediaBrowserService および MediaSession 実装が含まれています ( package/apps/Bluetoothを参照)。

それぞれのメディア ブラウザのツリー構造はBrowseTreeクラスで定義されます。再生制御コマンドは、MediaSession 実装を使用して他のアプリに同様に配信できます。

ストリーミング メディア コマンドを処理する

サーバー側メディア ストリーミングを実装するには、VIA 自体がメディア ソースになり、MediaBrowse および MediaSession API を実装する必要があります。 「自動車用のメディア アプリを構築する」を参照してください。これらの API を実装すると、音声コントロール アプリで (特に) 次のことが可能になります。

  • メディアソースの選択にシームレスに参加
  • 車の再始動後に自動的に再開される
  • Media Center UI を使用して再生と閲覧の制御を提供します
  • 標準のハードウェア メディア ボタン イベントを受信します

すべてのナビゲーション アプリと対話するための標準化された方法はありません。 Google マップとの統合については、 「Google Maps for Android Automotive Intents」を参照してください。他のアプリとの統合については、アプリ開発者に直接お問い合わせください。アプリ (Google マップを含む) に対してインテントを起動する前に、インテントが解決できることを確認してください ( 「インテント リクエスト」を参照)。これにより、対象のアプリが利用できない場合にユーザーに通知する機会が生まれます。

車両のコマンドを遂行する

車両プロパティへの読み取りと書き込みの両方のアクセスは、 CarPropertyManagerを通じて提供されます。車両プロパティのタイプ、その実装、およびその他の詳細については、「プロパティの構成」で説明されています。 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);
    }

    ...
}

車両のプロパティを設定する

以下はフロントエアコンのオン・オフの例です。

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

    …
}

通信コマンドを実行する

メッセージング コマンドを処理する

VI は、音声アシスタントの「タップして読む」で説明されている「タップして読む」フローに従って受信メッセージを処理する必要があります。オプションで、受信メッセージの送信者への返信の返信を処理できます。さらに、VIA はSmsManager ( android.telephonyパッケージの一部) を使用して、SMS メッセージを作成し、車から直接、または Bluetooth 経由で送信できます。

呼び出しコマンドを処理する

同様の方法で、VIA はTelephonyManagerを使用して電話をかけたり、ユーザーのボイスメール番号に電話をかけたりすることができます。このような場合、VIA はテレフォニー スタックと直接やり取りするか、Car Dialer アプリとやり取りします。いずれの場合も、Car Dialer アプリは音声通話関連の UI をユーザーに表示するアプリである必要があります。

他のコマンドを実行する

VIA とシステム間のその他の可能な統合ポイントのリストについては、よく知られているAndroid インテントのリストを確認してください。多くのユーザー コマンドはサーバー側で解決でき (ユーザーの電子メールやカレンダー イベントの読み取りなど)、音声対話自体以外のシステムとの対話は必要ありません。

没入型アクション (ビジュアルコンテンツの表示)

ユーザーの行動や理解を強化する場合、VIA は車の画面に補足的な視覚コンテンツを提供できます。ドライバーの注意散漫を最小限に抑えるために、そのようなコンテンツはシンプル、簡潔、かつ実用的なものにしてください。没入型アクションに関する UI/UX ガイドラインの詳細については、 「プリロードされたアシスタント: UX ガイダンス」を参照してください。

カスタマイズと残りのヘッド ユニット (HU) 設計との一貫性を可能にするために、VIA はほとんどの UI 要素にCar UI ライブラリコンポーネントを使用する必要があります。詳細については、 「カスタマイズ」を参照してください。