了解如何通過語音交互來完成這些類型的命令:
履行媒體命令
媒體相關的命令可以分為三個不同的組:
- 外部媒體源(例如安裝在 AAOS 中的 Spotify)。
- 後端媒體源(例如通過 VIA 流式傳輸的音樂)。
- 當地媒體來源(如汽車收音機)。
處理外部媒體源命令
外部媒體源定義為支持MediaSession
和MediaBrowse
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; }
請注意,可能隨時安裝或卸載媒體源。為了保持準確的列表,建議為以下意圖操作實現BroadcastReceiver : ACTION_PACKAGE_ADDED 、 ACTION_PACKAGE_CHANGED 、 ACTION_PACKAGE_REPLACED 、 ACTION_PACKAGE_REMOVED 。
連接到當前播放的媒體源
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意圖過濾器來識別。他們應該遵循此處描述的播放控件和媒體瀏覽結構: Implementing Radio with Media 。 AAOS 提供了car-broadcastradio-support庫,其中包含常量和方法,以幫助 OEM 為他們自己的遵循定義協議的無線電服務創建 MediaBrowseService 實現,並為使用其瀏覽樹的應用程序(例如,VIA)提供支持。
處理輔助輸入、CD 音頻和 USB 媒體
作為 AOSP 的一部分,這些媒體源沒有默認實現。建議的方法是:
- 讓 OEM 為他們每個人實施媒體服務。有關詳細信息,請參閱為汽車構建媒體應用程序。
- 這些 MediaBrowseService 實現將在General Play Intents中定義的 Intent 動作中被識別和響應。
- 這些服務將按照其他源類型中描述的指南公開瀏覽樹。
處理藍牙
藍牙媒體內容通過 AVRCP 藍牙配置文件公開。為了便於訪問此功能,AAOS 包含一個 MediaBrowserService 和 MediaSession 實現,用於抽像出通信細節(參見packages/apps/Bluetooth )。
相應的媒體瀏覽器樹結構在BrowseTree類中定義。通過使用其 MediaSession 實現,可以像任何其他應用程序一樣交付播放控制命令。
處理流媒體命令
要實現服務器端媒體流,VIA 必須自己成為媒體源,實現 MediaBrowse 和 MediaSession API。請參閱為汽車構建媒體應用程序。通過實現這些 API,語音控制應用程序將能夠(除其他外):
- 無縫參與媒體源選擇
- 汽車重啟後自動恢復
- 使用 Media Center UI 提供播放和瀏覽控制
- 接收標準硬件媒體按鈕事件
完成導航命令
沒有與所有導航應用程序交互的標準化方式。有關與 Google Maps 的集成,請參閱適用於 Android Automotive Intents 的 Google Maps 。如需與其他應用程序的集成,請直接聯繫應用程序開發人員。在向任何應用程序(包括 Google 地圖)啟動意圖之前,請驗證是否可以解析該意圖(請參閱意圖請求)。這創造了在目標應用程序不可用的情況下通知用戶的機會。
履行車輛命令
通過CarPropertyManager提供對車輛屬性的讀寫訪問。車輛屬性類型、其實現和其他細節在此處解釋: AOSP/Develop/Automotive - Vehicle Properties 。 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 必須按照Voice Assistant Tap-to-Read中描述的“點擊閱讀”流程處理傳入消息,它可以選擇處理將回復發送回傳入消息發送者。此外,VIA 可以使用SmsManager ( android.telephony包的一部分)直接從汽車或通過藍牙編寫和發送 SMS 消息。
處理調用命令
以類似的方式,VIA 可以使用TelephonyManager撥打電話並撥打用戶的語音郵件號碼。在這些情況下,VIA 將直接與電話堆棧或與 Car Dialer 應用程序交互。在任何情況下,Car Dialer 應用程序都應該是向用戶顯示語音呼叫相關 UI 的應用程序。
執行其他命令
有關 VIA 和系統之間其他可能集成點的列表,請查看眾所周知的Android Intents列表。許多用戶命令可以在服務器端解析(例如,閱讀用戶電子郵件和日曆事件),並且除了語音交互本身之外不需要與系統進行任何交互。
沉浸式動作(顯示視覺內容)
在增強用戶操作或理解的情況下,VIA 可以在汽車屏幕上提供補充視覺內容。為盡量減少駕駛員分心,請保持此類內容簡單、簡短且可操作。有關沉浸式操作的 UI/UX 指南的詳細信息,請參閱預加載助手:UX 指南。
為了實現與其他主機 (HU) 設計的定制和一致性,VIA 應該為大多數 UI 元素使用汽車 UI 庫組件。有關詳細信息,請參閱自定義。