要实现语音互动应用 (VIA),您需要完成以下步骤
- 创建 VIA 框架。
- (可选)实现设置/登录流程。
- (可选)实现“设置”屏幕。
- 在清单文件中声明所需的权限。
- 实现语音面板 UI。
- 实现语音识别(必须包含 RecognitionService API 实现)。
- 实现发声(可选,您可以实现 TextToSpeech API)。
- 实现命令实现。请参阅实现命令中的相关内容。
以下部分介绍了如何完成上述每个步骤。
创建 VIA 框架
清单
当清单中包含以下内容时,应用会被检测为具有语音互动功能的应用
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myvoicecontrol"> ... <application ... > <service android:name=".MyInteractionService" android:label="@string/app_name" android:permission="android.permission.BIND_VOICE_INTERACTION" android:process=":interactor"> <meta-data android:name="android.voice_interaction" android:resource="@xml/interaction_service" /> <intent-filter> <action android:name= "android.service.voice.VoiceInteractionService" /> </intent-filter> </service> </application> </manifest>
在此示例中
- VIA 必须公开一个扩展
VoiceInteractionService
的服务,并带有用于操作VoiceInteractionService.SERVICE_INTERFACE ("android.service.voice.VoiceInteractionService")
的 Intent 过滤器。 - 此服务必须持有
BIND_VOICE_INTERACTION
系统签名权限。 - 此服务应包含一个
android.voice_interaction
元数据文件以包含以下内容res/xml/interaction_service.xml
<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android" android:sessionService= "com.example.MyInteractionSessionService" android:recognitionService= "com.example.MyRecognitionService" android:settingsActivity= "com.example.MySettingsActivity" android:supportsAssist="true" android:supportsLaunchVoiceAssistFromKeyguard="true" android:supportsLocalInteraction="true" />
有关每个字段的详细信息,请参阅 R.styleable#VoiceInteractionService
。鉴于所有 VIA 也是语音识别器服务,您还必须在清单中包含以下内容
AndroidManifest.xml
<manifest ...> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <application ...> ... <service android:name=".RecognitionService" ...> <intent-filter> <action android:name="android.speech.RecognitionService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="android.speech" android:resource="@xml/recognition_service" /> </service> </application> </manifest>
语音识别服务还需要以下元数据
res/xml/recognition_service.xml
<recognition-service xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="com.example.MyRecognizerSettingsActivity" />
VoiceInteractionService、VoiceInteractionSessionService 和 VoiceInteractionSession
下图描述了每个实体的生命周期
图 1. 生命周期
如前所述,VoiceInteractionService
是 VIA 的入口点。此服务的主要职责是
- 初始化应保持运行状态的任何进程,只要此 VIA 是活跃的 VIA。例如,热词检测。
- 报告支持的语音操作(请参阅语音助手点按朗读)。
- 从锁屏(密钥保护)启动语音互动会话。
在其最简单的形式中,VoiceInteractionService
实现如下所示
public class MyVoiceInteractionService extends VoiceInteractionService { private static final List<String> SUPPORTED_VOICE_ACTIONS = Arrays.asList( CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION, CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION, CarVoiceInteractionSession.VOICE_ACTION_HANDLE_EXCEPTION ); @Override public void onReady() { super.onReady(); // TODO: Setup hotword detector } @NonNull @Override public Set<String> onGetSupportedVoiceActions( @NonNull Set<String> voiceActions) { Set<String> result = new HashSet<>(voiceActions); result.retainAll(SUPPORTED_VOICE_ACTIONS); return result; } ... }
需要实现 VoiceInteractionService#onGetSupportedVoiceActions()
以处理 语音助手点按朗读。系统使用 VoiceInteractionSessionService 来创建 VoiceInteractionSession 并与之互动。它只有一个职责,即在请求时启动新会话。
public class MyVoiceInteractionSessionService extends VoiceInteractionSessionService { @Override public VoiceInteractionSession onNewSession(Bundle args) { return new MyVoiceInteractionSession(this); } }
最后,VoiceInteractionSession 是完成大部分工作的地方。单个会话实例可能会被重复使用以完成多次用户互动。在 AAOS 中,存在一个辅助类 CarVoiceInteractionSession
,可帮助实现一些汽车特有的功能。
public class MyVoiceInteractionSession extends CarVoiceInteractionSession { public InteractionSession(Context context) { super(context); } @Override protected void onShow(String action, Bundle args, int showFlags) { closeSystemDialogs(); // TODO: Unhide UI and update UI state // TODO: Start processing audio input } ... }
VoiceInteractionSession
有大量的回调方法,将在以下部分中进行说明。有关完整列表,请参阅 VoiceInteractionSession
的文档。
实现设置/登录流程
设置和登录可能发生在
- 设备启动期间(设置向导)。
- 语音互动服务交换期间(设置)。
- 首次启动应用时(当应用被选中时)。
有关推荐的用户体验和视觉指南的详细信息,请参阅 预加载助手:用户体验指南。
语音服务交换期间的设置
用户始终可以选择尚未正确配置的 VIA。这可能是因为
- 用户完全跳过了设置向导,或者用户跳过了语音互动配置步骤。
- 用户选择的 VIA 与设备启动期间配置的 VIA 不同。
在任何情况下,VoiceInteractionService
都有多种方式来鼓励用户完成设置
- 通知提醒。
- 当用户尝试使用时,自动语音回复。
注意:强烈建议不要在没有明确用户请求的情况下呈现 VIA 设置流程。这意味着 VIA 应避免在设备启动期间或由于用户切换或解锁而自动在 HU 上显示内容。
通知提醒
通知提醒是一种非侵入式的方式,用于指示需要设置,并为用户提供导航到助手设置流程的入口。
图 2. 通知提醒
以下是此流程的工作方式
图 3. 通知提醒流程
语音回复
这是最简单的实现流程,在 VoiceInteractionSession#onShow()
回调中启动语音,向用户解释需要做什么,然后询问他们(如果在用户体验限制状态下允许设置)是否要启动设置流程。如果此时无法进行设置,也请解释这种情况。
首次使用时进行设置
用户始终可能触发尚未正确配置的 VIA。在这种情况下
- 口头告知用户这种情况(例如,“为了正常工作,我需要您完成几个步骤……”)。
- 如果用户体验限制引擎允许(请参阅 UX_RESTRICTIONS_NO_SETUP),请询问用户是否要启动设置过程,然后打开 VIA 的“设置”屏幕。
- 否则(例如,如果用户正在驾驶),请留下通知,让用户在安全时点击该选项。
构建语音互动设置屏幕
设置和登录屏幕应作为常规Activity进行开发。有关 UI 开发的用户体验和视觉指南,请参阅 预加载助手:用户体验指南。
一般指南
- VIA 应允许用户随时中断和恢复设置。
- 如果
UX_RESTRICTIONS_NO_SETUP
限制生效,则不应允许设置。有关详细信息,请参阅 驾驶员分心指南。 - 设置屏幕应与每辆车的设计系统相匹配。常规屏幕布局、图标、颜色和其他方面应与 UI 的其余部分保持一致。有关详细信息,请参阅 自定义。
实现设置屏幕
图 4. 设置集成
“设置”屏幕是常规 Android Activity。如果已实现,则必须在 VIA 清单的 res/xml/interaction_service.xml
中声明其入口点(请参阅 清单)。“设置”部分是继续设置和登录(如果用户未完成)或根据需要提供注销或切换用户选项的好地方。与上述“设置”屏幕类似,这些屏幕应
在清单文件中声明所需的权限
VIA 所需的权限可以分为三类
- 系统签名权限。 这些权限仅授予预安装的、系统签名的 APK。用户无法授予这些权限,只有 OEM 在构建其系统映像时才能授予这些权限。有关获取签名权限的更多信息,请参阅 授予系统特权权限。
- 危险权限。 这些权限是用户必须使用 PermissionsController 对话框授予的权限。OEM 可以预先将其中一些权限授予默认的 VoiceInteractionService。但是,鉴于此默认设置可能会因设备而异,应用应能够在需要时请求这些权限。
- 其他权限。 这些是不需要用户干预的所有其他权限。这些权限由系统自动授予。
鉴于以上所述,以下部分仅关注请求危险权限。权限应仅在用户位于登录或设置屏幕时请求。
如果应用没有运行所需的权限,建议的流程是使用语音来向用户解释情况,并使用通知来提供用户可以用来导航回 VIA 设置屏幕的入口。有关详细信息,请参阅1. 通知提醒。
在设置屏幕中请求权限
危险权限是使用常规 ActivityCompat#requestPermission()
方法(或等效方法)请求的。有关如何请求权限的详细信息,请参阅 请求应用权限。
图 5. 请求权限
通知监听器权限
为了实现 TTR 流程,VIA 必须被指定为通知监听器。这本身不是权限,而是一种配置,允许系统将通知发送到注册的监听器。要了解 VIA 是否被授予访问此信息的权限,应用可以
- (可选)通过使用
CarAssistUtils#assistantIsNotificationListener()
提前检查是否有通知监听器。例如,可以在设置流程期间完成此操作。 - (强制性)通过操作
VOICE_ACTION_HANDLE_EXCEPTION
和异常EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING
对处理CarVoiceInteractionSession#onShow()
做出反应。
如果未预先授予此访问权限,VIA 应使用语音和通知的组合,引导用户转到“车载设置”的“通知访问权限”部分。以下代码可用于打开设置应用的相应部分
private void requestNotificationListenerAccess() { Intent intent = new Intent(Settings .ACTION_NOTIFICATION_LISTENER_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); startActivity(intent); }
实现语音面板 UI
当 VoiceInteractionSession
接收到 onShow()
回调时,它可以呈现语音面板 UI。有关语音面板实现的视觉和用户体验指南,请参阅 预加载助手:用户体验指南。
图 6. 显示语音面板
实现此 UI 有两种选择
- 替换
VoiceInteractionSession#onCreateContentView()
- 使用
VoiceInteractionSession#startAssistantActivity()
启动 Activity
使用 onCreateContentView()
这是呈现语音面板的默认方式。VoiceInteractionSession
基类会创建一个窗口并管理其生命周期,只要语音会话处于活动状态。应用必须替换 VoiceInteractionSession#onCreateContentView()
并返回一个视图,该视图在会话创建后立即附加到该窗口。此视图最初应为不可见。当语音互动开始时,应在 VoiceInteractionSession#onShow()
上使此视图可见,然后在 VoiceInteractionSession#onHide()
上再次使其不可见。
public class MyVoiceInteractionSession extends CarVoiceInteractionSession { private View mVoicePlate; … @Override public View onCreateContentView() { mVoicePlate = inflater.inflate(R.layout.voice_plate, null); … } @Override protected void onShow(String action, Bundle args, int showFlags) { // TODO: Update UI state to "listening" mVoicePlate.setVisibility(View.VISIBLE); } @Override public void onHide() { mVoicePlate.setVisibility(View.GONE); } … }
使用此方法时,您可能需要调整 VoiceInteractionSession#onComputeInsets()
以考虑 UI 的遮挡区域。
使用 startAssistantActivity()
在这种情况下,VoiceInteractionSession
将语音面板 UI 的处理委托给常规 Activity。使用此选项时,VoiceInteractionSession
实现必须在 onPrepareShow()
回调中禁用其默认内容窗口的创建(请参阅使用 onCreateContentView())。在 VoiceInteractionSession#onShow()
时,会话将使用 VoiceInteractionSession#startAssistantActivity()
启动语音面板 Activity。此方法使用正确的窗口设置和 Activity 标志启动 UI。
public class MyVoiceInteractionSession extends CarVoiceInteractionSession { … @Override public void onPrepareShow(Bundle args, int showFlags) { super.onPrepareShow(args, showFlags); setUiEnabled(false); } @Override protected void onShow(String action, Bundle args, int showFlags) { closeSystemDialogs(); Intent intent = new Intent(getContext(), VoicePlateActivity.class); intent.putExtra(VoicePlateActivity.EXTRA_ACTION, action); intent.putExtra(VoicePlateActivity.EXTRA_ARGS, args); startAssistantActivity(intent); } … }
为了维护此 Activity 和 VoiceInteractionSession
之间的通信,可能需要一组内部 Intent 或服务绑定。例如,当调用 VoiceInteractionSession#onHide()
时,会话必须能够将此请求传递给 Activity。
重要提示。在 Automotive 中,只有特别注释的 Activity 或 UXR“许可名单”中列出的 Activity 才能在驾驶时显示。这同样适用于使用 VoiceInteractionSession#startAssistantActivity()
启动的 Activity。请务必使用 <meta-data android:name="distractionOptimized" android:value="true"/>
注释您的 Activity,或将此 Activity 包含在 /packages/services/Car/service/res/values/config.xml
文件的 systemActivityWhitelist
键中。有关详细信息,请参阅 驾驶员分心指南。
实现语音识别
在本节中,您将学习如何通过检测和识别热词来实现语音识别。热词是用于通过语音启动新查询或操作的触发词。例如,“OK Google”或“Hey Google”。
DSP 热词检测
Android 通过 AlwaysOnHotwordDetector
提供对 DSP 级别始终开启的热词检测器的访问权限。以低 CPU 实现热词检测的方式。此功能的使用分为两个部分
- 实例化
AlwaysOnHotwordDetector
。 - 注册热词检测声音模型。
VoiceInteractionService
实现可以使用 VoiceInteractionService#createAlwaysOnHotwordDetector()
创建热词检测器,传递他们希望用于检测的关键词组和语言区域。因此,应用会收到一个 onAvailabilityChanged()
回调,其中包含以下可能的值之一
STATE_HARDWARE_UNAVAILABLE
。DSP 功能在设备上不可用。在这种情况下,将使用软件热词检测。STATE_HARDWARE_UNSUPPORTED
。DSP 支持通常不可用,但 DSP 不支持给定的关键词组和语言区域组合。应用可以选择使用 软件热词检测。STATE_HARDWARE_ENROLLED
。热词检测已准备就绪,可以通过调用startRecognition()
方法启动。STATE_HARDWARE_UNENROLLED
。请求的关键词组的声音模型不可用,但可以注册。
可以使用 IVoiceInteractionManagerService#updateKeyphraseSoundModel()
注册热词检测声音模型。可以在系统中同时注册多个模型,但只有一个模型与 AlwaysOnHotwordDetector
关联。DSP 热词检测可能并非在所有设备上都可用。VIA 开发人员应使用 getDspModuleProperties()
方法检查硬件功能。有关显示如何注册声音模型的示例代码,请参阅 VoiceEnrollment/src/com/android/test/voiceenrollment/EnrollmentUtil.java
。有关并发热词识别,请参阅 并发捕获。
软件热词检测
如上所述,DSP 热词检测可能并非在所有设备上都可用(例如,Android 模拟器不提供 DSP 模拟)。在这种情况下,软件语音识别是唯一的替代方案。为了避免干扰可能需要访问麦克风的其他应用,VIA 必须使用以下方式访问音频输入
- 音频捕获必须使用 MediaRecorder.AudioSource.HOTWORD。
- 持有
android.Manifest.permission.CAPTURE_AUDIO_HOTWORD
权限。
这两个常量都是 @hide
,并且仅适用于捆绑应用。
管理音频输入和语音识别
音频输入将使用 MediaRecorder 类实现。有关如何使用此 API 的更多信息,请参阅 MediaRecorder 概览。语音互动服务也应是 RecognitionService
类的实现。系统中任何需要语音识别的应用都使用它来访问此功能。要进行语音识别并访问麦克风,VIA 必须持有 android.permission.RECORD_AUDIO
。访问 RecognitionService
实现的应用也应持有此权限。
在 Android 10 之前,麦克风访问权限一次只授予一个应用(热词检测除外,见上文)。从 Android 10 开始,麦克风访问权限可以共享。有关更多信息,请参阅 共享音频输入。
访问音频输出
当 VIA 准备好提供口头回复时,务必遵循以下一组指南
- 在请求音频焦点或管理音频输出时,应用必须使用
AudioAttributes#USAGE_ASSISTANT
和AudioAttributes#CONTENT_TYPE_SPEECH
作为音频属性。 - 在语音识别期间,必须使用
AudioManage#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
请求音频焦点。请注意,当某些媒体应用的音频焦点被移除时,它们可能无法对媒体命令做出正确响应(请参阅实现媒体命令)。