本頁面說明如何透過語音互動執行指令。
執行媒體指令
媒體相關指令可分為三種不同群組:
- 外部媒體來源 (例如在 AAOS 中安裝的 Spotify)。
- 後端媒體來源 (例如透過 VIA 串流的音樂)。
- 本機媒體來源 (例如車用收音機)。
處理外部媒體來源指令
外部媒體來源是指支援 MediaSessionCompat 和 MediaBrowseCompat 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; }
請注意,媒體來源隨時可能安裝或解除安裝。為維持準確的清單,建議針對意圖動作 ACTION_PACKAGE_ADDED、ACTION_PACKAGE_CHANGED、ACTION_PACKAGE_REPLACED 和 ACTION_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 程式庫,內含常數和方法,可協助 OEM 廠商為自己的電台服務建立 MediaBrowseService 實作項目,並遵循定義的通訊協定,同時支援使用瀏覽樹狀結構的應用程式 (例如 VIA)。
處理外部音源輸入、CD 音訊和 USB 媒體
AOSP 不會預設實作這些媒體來源。 建議做法如下:
- 請 OEM 為每項服務導入媒體服務。詳情請參閱「打造車用媒體應用程式」。
- 系統會找出這些 MediaBrowseService 實作項目,並在一般播放意圖中定義的意圖動作中做出回應。
- 這些服務會公開瀏覽樹狀結構,並遵循「其他來源類型」一節所述的指南。
處理藍牙
藍牙媒體內容會透過 AVRCP 藍牙設定檔公開。為方便存取這項功能,AAOS 包含 MediaBrowserService 和 MediaSession 實作項目,可抽象化通訊詳細資料 (請參閱 packages/apps/Bluetooth)。
相關媒體瀏覽器樹狀結構是在 BrowseTree 類別中定義。播放控制指令可透過 MediaSession 實作方式,傳送至任何其他應用程式。
處理串流媒體指令
如要導入伺服器端媒體串流,VIA 本身必須成為媒體來源,並導入 MediaBrowse 和 MediaSession API。請參閱「打造車用媒體應用程式」。實作這些 API 後,語音控制應用程式就能執行下列動作 (但不限於此):
- 順暢參與媒體來源選取程序
- 車輛重新啟動後,系統會自動繼續播放
- 使用媒體中心 UI 提供播放和瀏覽控制選項
- 接收標準硬體媒體按鈕事件
執行導覽指令
與所有導航應用程式互動的方式不盡相同。 如要與 Google 地圖整合,請參閱「適用於 Android Automotive 的 Google 地圖意圖」。如要與其他應用程式整合,請直接與應用程式開發人員聯絡。啟動任何應用程式 (包括 Google 地圖) 的 Intent 前,請先確認 Intent 可以解析 (請參閱 Google 地圖 Intent)。如果目標應用程式無法使用,您就能藉此機會通知使用者。
執行車輛指令
透過 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 可以使用 SmsManager (android.telephony 套件的一部分),直接從車輛或透過藍牙撰寫及傳送簡訊。
處理通話指令
同樣地,VIA 可以使用 TelephonyManager
撥打電話,以及撥打使用者的語音信箱號碼。在這些情況下,VIA 會直接與電話堆疊或 Car Dialer 應用程式互動。無論如何,Car Dialer 應用程式都應向使用者顯示語音通話相關的 UI。
執行其他指令
如需 VIA 與系統之間其他可能的整合點清單,請參閱這份 Android Intent 清單。許多使用者指令都可以在伺服器端解決 (例如讀取使用者的電子郵件和日曆活動),除了語音互動本身之外,不需要與系統進行任何互動。
沉浸式動作 (顯示視覺內容)
如果能提升使用者操作或理解能力,VIA 可以在車輛螢幕上提供補充視覺內容。為盡量減少駕駛人分心的情況,請確保這類內容簡單、簡短且實用。如要進一步瞭解沉浸式動作的使用者介面/使用者體驗指南,請參閱預先載入的 Google 助理:使用者體驗指南。
如要自訂 VIA 並與其餘車機 (HU) 設計保持一致,VIA 應使用車輛 UI 程式庫元件來處理大部分的 UI 元素。詳情請參閱「自訂」。