執行指令

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

執行媒體指令

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

  • 外部媒體來源 (例如在 AAOS 中安裝的 Spotify)。
  • 後端媒體來源 (例如透過 VIA 串流播放的音樂)。
  • 本機媒體來源 (例如汽車收音機)。

處理外部媒體來源指令

外部媒體來源的定義為支援 MediaSessionCompat 的 Android 應用程式 和MediaBrowseCompat API (請參閱「建構媒體應用程式 車輛)。

重要事項:如要讓助理應用程式連結 所有已安裝媒體應用程式的 MediaBrowseService 系統,它必須:

  1. 以系統簽署的形式安裝 (請參閱「媒體應用程式開發指南」 AAOS 和範例 PackageValidator 程式碼)。
  2. 保留 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;
}

請注意,媒體來源隨時都有可能安裝或解除安裝。於 為維持準確的清單,建議您導入 BroadcastReceiver 意圖動作 ACTION_PACKAGE_ADDED 的例項 ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REPLACED、 和 ACTION_PACKAGE_REMOVED

連線至目前正在播放的媒體來源

CarMediaService敬上 提供取得目前所選媒體來源的方法,以及此媒體的 來源變更。之所以出現這類變更,可能是因為使用者與 或因在車上使用硬體按鈕而直接操作另一方面 car-media-common Library 提供便利的方式連線至特定媒體 來源。以下提供簡單的程式碼片段,說明如何連結至目前所選的 媒體應用程式:

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敬上 包含常數和方法的程式庫,可協助原始設備製造商 (OEM) 建立 MediaBrowseService 導入符合自身既定通訊協定的自家電台服務 ,並對使用瀏覽樹狀結構的應用程式 (例如 VIA) 提供支援。

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

Android 開放原始碼計畫並未提供這些媒體來源的預設實作方式。 我們建議的做法如下:

  • 請原始設備製造商為每一種供應商導入媒體服務。詳情請參閱「打造車用媒體應用程式」。
  • 系統會在意圖中找出並回應這些 MediaBrowseService 實作項目 一般播放意圖中定義的動作。
  • 這些服務會根據本文所述的指南顯示瀏覽樹狀結構 「其他來源類型」一文。

處理藍牙

藍牙媒體內容會透過 AVRCP 藍牙設定檔公開。於 以便存取這項功能,AAOS 包含 MediaBrowserService 和 MediaSession 實作會將 通訊詳細資料 (請參閱套件/應用程式/藍牙)。

個別媒體瀏覽器樹狀結構的定義是在 BrowseTree 中定義。 類別播放控制指令的提供方式與其他應用程式類似。 找出最適合的競價方式

處理串流媒體指令

如要實作伺服器端媒體串流,VIA 必須自身成為 媒體來源、導入 MediaBrowse 和 MediaSession API。詳情請參閱 建構媒體應用程式 汽車。導入這些 API 後,語音控制應用程式就能 (以及其他事項):

  • 順利參與媒體來源選擇
  • 在車輛重新啟動後自動恢復運作
  • 使用 Media Center 使用者介面提供播放和瀏覽控制項
  • 接收標準硬體媒體按鈕事件

沒有標準化的操作方式與所有導航應用程式互動。 如要瞭解如何與 Google 地圖整合,請參閱 Google 適用於 Android Automotive 意圖的地圖與其他項目整合 請直接與應用程式開發人員聯絡推出前 向任何應用程式 (包括 Google 地圖) 發出的意圖,請確認該意圖 已解決 (請參閱意圖 要求)。這麼做有機會通知使用者 或目標應用程式無法使用

履行車輛指令

讀取和寫入車輛屬性的存取權是透過以下方式提供: CarPropertyManager。 說明車輛屬性類型、實作方式和其他詳細資料 在資源中 設定為支援屬性提供準確說明 最好直接參照 hardware/interfaces/automotive/vehicle/2.0/types.halVehicleProperty (車輛屬性) 其中定義的列舉同時包含標準和供應商專屬的屬性、資料 類型、變更模式、單位和讀取/寫入存取權定義

如要透過 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 必須在「輕觸閱讀」後處理收到的訊息說明流程 在「語音助理」中使用 輕觸即可讀取,視需要處理回覆給來信寄件者的回覆。 此外,VIA 還可以使用 SmsManager (屬於 android.telephony 的一部分) 套件),直接透過車輛或藍牙撰寫及傳送簡訊。

處理呼叫指令

VIA 也可透過類似方式使用 TelephonyManager 撥打電話,並撥打使用者的語音郵件號碼。在這些情況下 VIA 會直接與電話堆疊互動或與「汽車撥號」互動 應用程式。無論如何,一律應顯示「汽車撥號」應用程式 與使用者的語音通話相關 UI

執行其他指令

如需 VIA 和 ,請參閱Android 意圖清單。多個 可在伺服器端解析使用者指令 (例如讀取 使用者的電子郵件和日曆活動),而且不需要與系統任何互動。 而不是語音互動本身

沉浸式動作 (顯示影像內容)

VIA 可幫助 VIA 提升使用者操作或理解力 。為了盡可能減少駕駛人分心 保持這類內容的精簡、簡單、可行動。進一步瞭解使用者介面/使用者體驗指南 沉浸式動作上,請參閱 預先載入的 Google 助理:使用者體驗指南

如要自訂與一致性, 其餘的車用運算主機 (HU) 設計,VIA 應該使用 汽車 大部分 UI 元素的 UI 程式庫元件。詳情請參閱 自訂