執行命令

本頁介紹如何透過語音互動完成指令。

完成媒體指令

與媒體相關的命令可以分為三個不同的群組:

  • 外部媒體來源(例如 AAOS 中安裝的 Spotify)。
  • 後端媒體來源(例如透過 VIA 傳輸的音樂)。
  • 當地媒體來源(例如汽車廣播)。

處理外部媒體來源指令

外部媒體來源定義為支援MediaSessionCompatMediaBrowseCompat API 的 Android 應用程式(有關使用這些 API 的詳細說明,請參閱建置汽車媒體應用程式)。

重要提示:對於要連接到系統中所有已安裝媒體應用程式的MediaBrowseService的助理應用程序,它必須:

  1. 作為系統簽章安裝(請參閱 AAOS 的媒體應用程式開發指南和範例PackageValidator程式碼)。
  2. 持有android.permission.MEDIA_CONTENT_CONTROL系統特權權限(請參閱授予系統特權權限)。

除了MediaBrowserCompatMediaControllerCompat之外,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_ADDEDACTION_PACKAGE_CHANGEDACTION_PACKAGE_REPLACEDACTION_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 播放器、藍牙、USB)

本地媒體來源使用上面詳述的相同 MediaSession 和 MediaBrowse API 向系統公開其功能。為了適應每種類型硬體的特殊性,這些 MediaBrowse 服務使用特定的約定來組織其資訊和媒體命令。

手把收音機

Radio MediaBrowseService 可以透過ACTION_PLAY_BROADCASTRADIO意圖過濾器來識別。他們應該遵循實現廣播中描述的播放控制和媒體瀏覽結構。 AAOS 提供car-broadcastradio-support庫,其中包含常數和方法,可協助 OEM 為自己的遵循定義協定的無線電服務建立 MediaBrowseService 實現,並為使用其瀏覽樹(例如 VIA)的應用程式提供支援。

處理輔助輸入、CD 音訊和 USB 媒體

作為 AOSP 的一部分,這些媒體來源沒有預設實作。建議的方法是:

手柄藍牙

藍牙媒體內容透過 AVRCP 藍牙設定檔公開。為了方便存取此功能,AAOS 包含 MediaBrowserService 和 MediaSession 實現,以抽象化通訊詳細資訊(請參閱packages/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"));
    }

    …
}

完成通訊命令

處理訊息命令

VIA 必須按照語音助理點擊閱讀中所述的「點擊閱讀」流程處理傳入訊息,該流程可以選擇處理將回覆發送回傳入訊息發送者。此外,VIA 還可以使用SmsManagerandroid.telephony套件的一部分)直接從汽車或透過藍牙編寫和發送 SMS 訊息。

處理呼叫命令

類似地,VIA 可以使用TelephonyManager撥打電話並呼叫使用者的語音郵件號碼。在這些情況下,VIA 將直接與電話堆疊或汽車撥號器應用程式互動。無論如何,汽車撥號器應用程式應該是向用戶顯示語音呼叫相關 UI 的應用程式。

完成其他指令

有關 VIA 和系統之間其他可能的整合點的列表,請查看眾所周知的Android 意圖列表。許多使用者命令可以在伺服器端解析(例如,閱讀使用者電子郵件和日曆事件),並且除了語音互動本身之外不需要與系統進行任何互動。

沉浸式動作(顯示視覺內容)

在增強用戶操作或理解的地方,VIA 可以在汽車螢幕上提供補充視覺內容。為了最大限度地減少駕駛員分心,請保持此類內容簡單、簡短且可操作。有關沉浸式操作的 UI/UX 指南的詳細信息,請參閱預加載助手:UX 指南

為了實現自訂並與主機 (HU) 設計的其餘部分保持一致,VIA 應為大多數 UI 元素使用汽車 UI 庫元件。詳情請參閱定制