本页面介绍了如何通过语音互动履行命令。
履行媒体命令
媒体相关命令可以分为三个不同的组
- 外部媒体来源(例如 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; }
请注意,媒体来源可能随时安装或卸载。为了维护准确的列表,建议为 intent 操作 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 服务使用特定的约定来组织其信息和媒体命令。
处理无线电
无线电 MediaBrowseService 可以通过 ACTION_PLAY_BROADCASTRADIO
intent 过滤器来识别。它们应遵循 实施无线电中所述的播放控制和媒体浏览结构。AAOS 提供 car-broadcastradio-support
库,其中包含常量和方法,以帮助 OEM 为他们自己的无线电服务创建遵循已定义协议的 MediaBrowseService 实现,并为使用其浏览树的应用(例如,VIA)提供支持。
处理辅助输入、CD 音频和 USB 媒体
AOSP 中不包含这些媒体来源的默认实现。建议的方法是
- 让 OEM 为它们各自实现媒体服务。有关详情,请参阅为车载设备构建媒体应用。
- 这些 MediaBrowseService 实现将在 通用播放 Intent 中定义的 intent 操作中被识别和响应。
- 这些服务将公开遵循 其他来源类型中所述指南的浏览树。
处理蓝牙
蓝牙媒体内容通过 AVRCP 蓝牙配置文件公开。为了方便访问此功能,AAOS 包括一个 MediaBrowserService 和 MediaSession 实现,它们抽象出通信详情(请参阅 packages/apps/Bluetooth)。
相应的媒体浏览器树结构在 BrowseTree 类中定义。播放控制命令可以像任何其他应用一样通过使用其 MediaSession 实现来传递。
处理流式媒体命令
要实现服务器端媒体流式传输,VIA 必须自身成为媒体来源,实现 MediaBrowse 和 MediaSession API。请参阅为车载设备构建媒体应用。通过实现这些 API,语音控制应用将能够(除其他外)
- 无缝参与媒体来源选择
- 在汽车重启后自动恢复
- 使用媒体中心 UI 提供播放和浏览控制
- 接收标准硬件媒体按钮事件
履行导航命令
与所有导航应用互动没有标准化的方法。如需与 Google 地图集成,请参阅 适用于 Android Automotive Intent 的 Google 地图。如需与其他应用集成,请直接与应用开发者联系。在启动任何应用的 intent(包括 Google 地图)之前,请验证该 intent 是否可以解析(请参阅 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 将直接与电话堆栈或车载拨号器应用互动。在任何情况下,车载拨号器应用都应该是向用户显示与语音通话相关的 UI 的应用。
履行其他命令
如需查看 VIA 与系统之间其他可能的集成点的列表,请查看著名的 Android Intent 列表。许多用户命令可以在服务器端解析(例如,读取用户电子邮件和日历事件),并且除了语音互动本身之外,不需要与系统进行任何互动。
沉浸式操作(显示视觉内容)
在增强用户操作或理解的情况下,VIA 可以在车载屏幕上提供补充视觉内容。为了最大限度地减少驾驶员分心,请使此类内容保持简单、简短且可操作。如需详细了解有关沉浸式操作的 UI/UX 指南,请参阅预加载助理:用户体验指南。
为了实现自定义并与车载信息娱乐系统 (HU) 设计的其余部分保持一致,VIA 应将 Car UI 库组件用于大多数 UI 元素。有关详情,请参阅自定义。