应用开发

要实现语音互动应用 (VIA),您需要完成以下步骤

  1. 创建 VIA 框架。
  2. 可选)实现设置/登录流程。
  3. 可选)实现“设置”屏幕。
  4. 在清单文件中声明所需的权限。
  5. 实现语音面板 UI。
  6. 实现语音识别(必须包含 RecognitionService API 实现)。
  7. 实现发声(可选,您可以实现 TextToSpeech API)。
  8. 实现命令实现。请参阅实现命令中的相关内容。

以下部分介绍了如何完成上述每个步骤。

创建 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

下图描述了每个实体的生命周期

Lifecycles

图 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 上显示内容。

通知提醒

通知提醒是一种非侵入式的方式,用于指示需要设置,并为用户提供导航到助手设置流程的入口。

Notification reminder

图 2. 通知提醒

以下是此流程的工作方式

Notification reminder flow

图 3. 通知提醒流程

语音回复

这是最简单的实现流程,在 VoiceInteractionSession#onShow() 回调中启动语音,向用户解释需要做什么,然后询问他们(如果在用户体验限制状态下允许设置)是否要启动设置流程。如果此时无法进行设置,也请解释这种情况。

首次使用时进行设置

用户始终可能触发尚未正确配置的 VIA。在这种情况下

  1. 口头告知用户这种情况(例如,“为了正常工作,我需要您完成几个步骤……”)。
  2. 如果用户体验限制引擎允许(请参阅 UX_RESTRICTIONS_NO_SETUP),请询问用户是否要启动设置过程,然后打开 VIA 的“设置”屏幕。
  3. 否则(例如,如果用户正在驾驶),请留下通知,让用户在安全时点击该选项。

构建语音互动设置屏幕

设置和登录屏幕应作为常规Activity进行开发。有关 UI 开发的用户体验和视觉指南,请参阅 预加载助手:用户体验指南

一般指南

  • VIA 应允许用户随时中断和恢复设置。
  • 如果 UX_RESTRICTIONS_NO_SETUP 限制生效,则不应允许设置。有关详细信息,请参阅 驾驶员分心指南
  • 设置屏幕应与每辆车的设计系统相匹配。常规屏幕布局、图标、颜色和其他方面应与 UI 的其余部分保持一致。有关详细信息,请参阅 自定义

实现设置屏幕

Settings integration

图 4. 设置集成

“设置”屏幕是常规 Android Activity。如果已实现,则必须在 VIA 清单的 res/xml/interaction_service.xml 中声明其入口点(请参阅 清单)。“设置”部分是继续设置和登录(如果用户未完成)或根据需要提供注销切换用户选项的好地方。与上述“设置”屏幕类似,这些屏幕应

  • 提供退出返回到屏幕堆栈中上一屏幕的选项(例如,返回到“车载设置”)。
  • 在驾驶时不允许使用。有关详细信息,请参阅 驾驶员分心指南
  • 与每辆车的设计系统相匹配。有关详细信息,请参阅 自定义

在清单文件中声明所需的权限

VIA 所需的权限可以分为三类

  • 系统签名权限。 这些权限仅授予预安装的、系统签名的 APK。用户无法授予这些权限,只有 OEM 在构建其系统映像时才能授予这些权限。有关获取签名权限的更多信息,请参阅 授予系统特权权限
  • 危险权限。 这些权限是用户必须使用 PermissionsController 对话框授予的权限。OEM 可以预先将其中一些权限授予默认的 VoiceInteractionService。但是,鉴于此默认设置可能会因设备而异,应用应能够在需要时请求这些权限。
  • 其他权限。 这些是不需要用户干预的所有其他权限。这些权限由系统自动授予。

鉴于以上所述,以下部分仅关注请求危险权限。权限应仅在用户位于登录或设置屏幕时请求。

如果应用没有运行所需的权限,建议的流程是使用语音来向用户解释情况,并使用通知来提供用户可以用来导航回 VIA 设置屏幕的入口。有关详细信息,请参阅1. 通知提醒

在设置屏幕中请求权限

危险权限是使用常规 ActivityCompat#requestPermission() 方法(或等效方法)请求的。有关如何请求权限的详细信息,请参阅 请求应用权限

Request permissions

图 5. 请求权限

通知监听器权限

为了实现 TTR 流程,VIA 必须被指定为通知监听器。这本身不是权限,而是一种配置,允许系统将通知发送到注册的监听器。要了解 VIA 是否被授予访问此信息的权限,应用可以

如果未预先授予此访问权限,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。有关语音面板实现的视觉和用户体验指南,请参阅 预加载助手:用户体验指南

Displaying the voice plate

图 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 实现热词检测的方式。此功能的使用分为两个部分

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 必须使用以下方式访问音频输入

这两个常量都是 @hide,并且仅适用于捆绑应用。

管理音频输入和语音识别

音频输入将使用 MediaRecorder 类实现。有关如何使用此 API 的更多信息,请参阅 MediaRecorder 概览。语音互动服务也应是 RecognitionService 类的实现。系统中任何需要语音识别的应用都使用它来访问此功能。要进行语音识别并访问麦克风,VIA 必须持有 android.permission.RECORD_AUDIO。访问 RecognitionService 实现的应用也应持有此权限。

在 Android 10 之前,麦克风访问权限一次只授予一个应用(热词检测除外,见上文)。从 Android 10 开始,麦克风访问权限可以共享。有关更多信息,请参阅 共享音频输入

访问音频输出

当 VIA 准备好提供口头回复时,务必遵循以下一组指南