執行指令

本頁面說明如何透過語音互動執行指令。

執行媒體指令

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

  • 外部媒體來源 (例如在 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 服務會使用特定慣例來整理資訊和媒體指令。

處理無線電

可透過 ACTION_PLAY_BROADCASTRADIO 意圖篩選器識別 Radio MediaBrowseService。這些控制項應遵循「實作廣播電台」一文所述的播放控制項和媒體瀏覽結構。AAOS 提供 car-broadcastradio-support 程式庫,其中包含常數和方法,可協助原始設備製造商為自家電台服務建立 MediaBrowseService 實作項目,以便遵循定義的通訊協定,並為使用瀏覽樹狀結構的應用程式 (例如 VIA) 提供支援。

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

AOSP 中沒有這些媒體來源的預設實作項目。建議做法如下:

  • 請原始設備製造商 (OEM) 為每個裝置實作媒體服務。詳情請參閱「打造車用媒體應用程式」。
  • 系統會在一般播放意圖中定義的意圖動作中,識別並回應這些 MediaBrowseService 實作。
  • 這些服務會根據「其他來源類型」一節所述的規範,提供瀏覽樹狀結構。

處理藍牙

藍牙媒體內容會透過 AVRCP 藍牙設定檔公開。為了方便存取這項功能,AAOS 包含 MediaBrowserService 和 MediaSession 實作,可抽象化通訊詳細資料 (請參閱 packages/apps/Bluetooth)。

相應的媒體瀏覽器樹狀結構定義在 BrowseTree 類別中。您可以使用 MediaSession 實作項目,以類似於任何其他應用程式的方式傳送播放控制指令。

處理串流媒體指令

如要實作伺服器端媒體串流,VIA 本身必須成為媒體來源,並實作 MediaBrowse 和 MediaSession API。請參閱「打造車用媒體應用程式」一文。透過實作這些 API,語音控制應用程式將可執行下列操作 (以及其他操作):

  • 參與媒體來源選取作業
  • 在車輛重新啟動後自動繼續執行
  • 使用 Media Center UI 提供播放和瀏覽控制功能
  • 接收標準硬體媒體按鈕事件

目前沒有標準方式可與所有導航應用程式互動。如要整合 Google 地圖,請參閱「適用於 Android Automotive 意圖的 Google 地圖」。如要整合其他應用程式,請直接與應用程式開發人員聯絡。向任何應用程式 (包括 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 可使用 SmsManager (android.telephony 套件的一部分),直接透過車輛或藍牙撰寫及傳送簡訊。

處理通話指令

同樣地,VIA 可使用 TelephonyManager 撥打電話,並撥打至使用者的語音信箱號碼。在這些情況下,VIA 會直接與電話服務堆疊或 Car Dialer 應用程式互動。無論如何,Car Dialer 應用程式都應向使用者顯示與語音通話相關的使用者介面。

執行其他指令

如需 VIA 與系統之間其他可能的整合點清單,請查看知名的 Android 意圖清單。許多使用者指令可在伺服器端解析 (例如讀取使用者的電子郵件和日曆活動),而且除了語音互動本身,不需要與系統進行任何互動。

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

在可提升使用者操作或理解的情況下,VIA 可在車輛螢幕上提供輔助視覺內容。為了盡量減少駕駛人分心,請確保這類內容簡單、簡短且可採取行動。如要進一步瞭解沉浸式動作的 UI/UX 指南,請參閱「預先載入的 Google 助理:使用者體驗指南」。

為讓自訂內容與其他車用主機 (HU) 設計保持一致,VIA 應在大部分的 UI 元素中使用 Car UI Library 元件。詳情請參閱「自訂」。