将 App Actions 与 Android 小部件集成

图 1. 启动 GET_EXERCISE_OBSERVATION 的小部件。

对于许多意图,最佳响应是向用户提供简单的答案、简短的确认或快速的交互式体验。您可以在 Google 助理中显示一个 Android 应用程序小部件 来满足这些类型的意图。

本指南介绍了如何使用小部件来满足助理用户查询,以及如何使用 App Actions 小部件扩展库 增强您的助理小部件体验。

优点

小部件是可以在 Android 表面上(例如启动器或锁定屏幕)嵌入的微型应用程序视图。使用 App Actions,您可以通过让小部件有资格在助理中显示来提高小部件的影响力。

  1. 发现:主动显示小部件以响应用户的自然语言查询。
  2. 互动:在免提环境中显示小部件,例如当助理在锁定屏幕上提供 个人结果 时,以及在 Android Auto 上。
  3. 保留:允许用户将助理中显示的小部件固定到他们的启动器。固定功能需要 小部件扩展库

助理如何显示小部件

用户可以通过两种方式在助理上调用小部件

  • 明确按名称请求小部件。
  • 对助理说出查询,该查询会触发为小部件实现配置的 内置意图 (BII) 或 自定义意图

显式调用

要明确调用任何已安装应用程序的小部件,用户可以向助理询问以下内容

  • "嘿 Google,显示 ExampleApp 小部件。"
  • "来自 ExampleApp 的小部件。"

助理会使用通用的介绍语显示这些小部件:"ExampleApp 说,这里有一个小部件。" 虽然助理本机返回以这种方式请求的小部件,而无需应用程序开发人员进行任何操作,但这种调用方法要求用户明确了解要请求的小部件。要简化小部件发现,请使用以下部分中详细介绍的意图实现方法。

意图实现

使用小部件来满足用户在助理上执行的自然语言查询,从而使您的部件更容易找到。例如,当用户在您的健身应用程序中触发 GET_EXERCISE_OBSERVATION BII 时,您可能会返回一个部件,例如,询问,"嘿 Google,我在 ExampleApp 上跑了几英里?" 除了简化发现外,将小部件与 App Actions 集成还具有以下优势

  • 参数访问:助理会将从用户查询中提取的 意图参数 提供给您的部件,从而使您可以 定制响应
  • 自定义 TTS 介绍语:您可以为助理提供文本转语音 (TTS) 字符串,以便在显示您的部件时宣布。
  • 小部件固定:助理会在您的部件附近显示一个添加此部件按钮,让用户可以轻松地将您的部件固定到他们的启动器。

实施小部件实现

要为您的意图实施小部件实现,请按照以下步骤操作

  1. 按照 创建简单小部件 中描述的步骤实施 Android 小部件。
  2. 在应用程序的 shortcuts.xml 资源文件中,将一个 <app-widget> 元素添加到您的功能中,其中包含实现详细信息和 BII <parameter> 标签。更新您的部件以处理参数。
  3. 添加必需的 小部件扩展库,该库允许助理将 BII 名称和参数传递给您的部件。它还支持 自定义 TTS 介绍语 和部件 固定 功能。

以下部分描述了 shortcuts.xml<app-widget> 架构。

小部件架构

<app-widget> 元素定义为 shortcuts.xml<capability> 元素内的实现。除非另有说明,否则它们需要以下属性

`shortcuts.xml` 标签包含在属性
<app-widget> <capability>
  • android:identifier
  • android:targetClass
<参数> <app-widget>
<额外> <app-widget>
  • android:name(仅适用于 TTS)
  • android:value(可选)

小部件模式说明

<app-widget>

顶级小部件完成元素。

属性

  • android:identifier:此完成的标识符。此值在 <app-widget><intent> 完成元素中必须唯一,这些元素在 <capability> 中定义。
  • android:targetClass:要处理该意图的 AppWidgetProvider 的完整类名。

<参数>

将 BII 参数映射到意图 <parameter> 值。您可以为每个 <app-widget> 元素定义零个或多个参数。在完成过程中,助理通过以键值对的形式更新小部件实例的额外信息来传递参数,格式如下:

  • 键:为该参数定义的 android:key
  • 值:BII 从用户的语音输入中提取的值。

您可以通过在关联的 AppWidgetManager 对象上调用 getAppWidgetOptions() 来访问这些额外信息,它将返回一个包含触发 BII 的名称及其参数的 Bundle。有关详细信息,请参见 提取参数值

有关 BII 参数匹配的更多信息,请参见 参数数据和匹配

<额外>

可选标签声明为该小部件使用 自定义 TTS 简介。此标签需要以下属性值:

  • android:name"hasTts"
  • android:value"true"

示例代码

以下来自 shortcuts.xml 文件的示例演示了用于 GET_EXERCISE_OBSERVATION BII 功能的小部件完成配置。

<capability android:name="actions.intent.GET_EXERCISE_OBSERVATION">
  <app-widget
    android:identifier="GET_EXERCISE_OBSERVATION_1"
    android:targetClass="com.exampleapp.providers.exampleAppWidgetProvider"
    android:targetPackage="com.exampleapp">
    <parameter
      android:name="exerciseObservation.aboutExercise.name"
      android:key="exercisename">
    </parameter>
    <extra android:name="hasTts" android:value="true"/>
  </app-widget>
</capability>

您可以指定多个 <app-widget> 元素,或对每个功能使用 <app-widget><intent> 元素的组合。这种方法使您可以根据用户提供的不同参数组合提供定制的体验。例如,如果用户在查询中未指定下车地点,则可以将他们引导至应用中的显示设定上下车地点选项的活动。有关定义回退意图的更多信息,请参见 回退意图 部分。

提取参数值

在以下示例 AppWidgetProvider 类中,私有函数 updateAppWidget() 用于从小部件选项 Bundle 中提取 BII 名称和参数

Kotlin

package com.example.exampleapp

//... Other module imports
import com.google.assistant.appactions.widgets.AppActionsWidgetExtension

/**
 * Implementation of App Widget functionality.
 */
class MyAppWidget : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        // There might be multiple widgets active, so update all of them
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    private fun updateAppWidget(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int
    ) {
        val widgetText: CharSequence = context.getString(R.string.appwidget_text)

        // Construct the RemoteViews object
        val views = RemoteViews(context.packageName, R.layout.my_app_widget)
        views.setTextViewText(R.id.appwidget_text, widgetText)

        // Extract the name and parameters of the BII from the widget options
        val optionsBundle = appWidgetManager.getAppWidgetOptions(appWidgetId)
        val bii = optionsBundle.getString(AppActionsWidgetExtension.EXTRA_APP_ACTIONS_BII) // "actions.intent.CREATE_TAXI_RESERVATION"
        val params = optionsBundle.getBundle(AppActionsWidgetExtension.EXTRA_APP_ACTIONS_PARAMS)
        if (params != null && params.containsKey("dropoff")) {
            val dropoffLocation = params.getString("dropoff")
            // Build your RemoteViews with the extracted BII parameter
            // ...
        }
        appWidgetManager.updateAppWidget(appWidgetId, views)
    }
}

Java

package com.example.exampleapp;

//... Other module imports
import com.google.assistant.appactions.widgets.AppActionsWidgetExtension;

/**
 * Implementation of App Widget functionality.
 */
public class MyAppWidget extends AppWidgetProvider {

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There might be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    private static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {

        CharSequence widgetText = context.getString(R.string.appwidget_text);

        // Construct the RemoteViews object
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget);
        views.setTextViewText(R.id.appwidget_text, widgetText);

        // Extract the name and parameters of the BII from the widget options
        Bundle optionsBundle = appWidgetManager.getAppWidgetOptions(appWidgetId);
        String bii =
                optionsBundle.getString(AppActionsWidgetExtension.EXTRA_APP_ACTIONS_BII); // "actions.intent.CREATE_TAXI_RESERVATION"
        Bundle params =
                optionsBundle.getBundle(AppActionsWidgetExtension.EXTRA_APP_ACTIONS_PARAMS);

        if (params != null && params.containsKey(("dropoff"))){
            String dropoffLocation = params.getString("dropoff");
            // Build your RemoteViews with the extracted BII parameter
            // ...
        }

        appWidgetManager.updateAppWidget(appWidgetId, views);
    }
}

小部件扩展库

应用操作小部件扩展库增强了您的面向语音的助理体验的小部件。这个库使您的应用能够从触发 BII 中接收重要的完成信息,包括 BII 名称和从用户查询中提取的任何意图参数。

这个 Maven 库使您能够为每个小部件提供自定义的文本转语音 (TTS) 简介,使助理能够宣布向用户视觉呈现的内容摘要。它还支持 启动器固定,使用户可以轻松地将显示在助理中的小部件保存到启动器屏幕。

通过将该库添加到应用模块的 build.gradle 文件的依赖项部分来开始。

dependencies {
    //...
    implementation "com.google.assistant.appactions:widgets:0.0.1"
}

自定义简介

导入小部件扩展库后,您可以为小部件提供自定义 TTS 简介。要将您的定义添加到小部件的 AppWidgetProvider 中,请在您的 IDE 中打开该类,然后导入小部件扩展库

Kotlin

import com.google.assistant.appactions.widgets.AppActionsWidgetExtension

Java

import com.google.assistant.appactions.widgets.AppActionsWidgetExtension;
接下来,使用该库定义您的简介字符串并更新小部件,如以下 `ExampleAppWidget` 中所示。

Kotlin

package com.example.exampleapp

//... Other module imports
import com.google.assistant.appactions.widgets.AppActionsWidgetExtension

/**
 * Implementation of App Widget functionality.
 */
object MyAppWidget : AppWidgetProvider() {
    fun updateAppWidget(
        context: Context?,
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int
    ) {
        val appActionsWidgetExtension = AppActionsWidgetExtension.newBuilder(appWidgetManager)
            .setResponseSpeech("Hello world") // TTS to be played back to the user
            .setResponseText("Hello world!") // Response text to be displayed in Assistant
            .build()

        // Update widget with TTS
        appActionsWidgetExtension.updateWidget(appWidgetId)

        // Update widget UI
        appWidgetManager.updateAppWidget(appWidgetId, views)
    }
}

Java

package com.example.exampleapp;

//... Other module imports
import com.google.assistant.appactions.widgets.AppActionsWidgetExtension;

/**
 * Implementation of App Widget functionality.
 */
public class MyAppWidget extends AppWidgetProvider {

  static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
    int appWidgetId) {

    AppActionsWidgetExtension appActionsWidgetExtension = AppActionsWidgetExtension.newBuilder(appWidgetManager)
      .setResponseSpeech("Hello world")  // TTS to be played back to the user
      .setResponseText("Hello world!")  // Response text to be displayed in Assistant
      .build();

      // Update widget with TTS
      appActionsWidgetExtension.updateWidget(appWidgetId);

      // Update widget UI
      appWidgetManager.updateAppWidget(appWidgetId, views);
    }

}

TTS 样式建议

使用以下样式建议来优化您的自定义小部件简介,以用于 TTS 和显示的提示。

建议 推荐 不推荐
缩略词
在 TTS 提示中使用缩略词。没有缩略词的消息听起来很僵硬和机械,而不是自然和对话。说出诸如“cannot”和“do not”之类的词听起来很惩罚性和苛刻。
ResponseSpeech (TTS)
抱歉,我找不到预订。

ResponseText
抱歉,我找不到预订。
ResponseSpeech (TTS)
抱歉,我找不到预订。

ResponseText
抱歉,我找不到预订。
逗号
通过在三个或更多项的列表中使用串行逗号来增加清晰度。如果没有串行逗号,列表中的各个项目可能会被错误地听到,或者被读作组。例如,在“水仙花、雏菊和向日葵”中,“雏菊和向日葵”听起来好像是一起的。在“水仙花、雏菊和向日葵”中,所有三个都是明显分开的。
ResponseSpeech (TTS)
我们最受欢迎的包括黄玫瑰、水仙花、雏菊和向日葵。

ResponseText
我们最受欢迎的包括黄玫瑰、水仙花、雏菊和向日葵。
ResponseSpeech (TTS)
我们最受欢迎的包括黄玫瑰、水仙花、雏菊和向日葵。

ResponseText
我们最受欢迎的包括黄玫瑰、水仙花、雏菊和向日葵。
数字
使用数字而不是文字,使视觉内容更容易一目了然。
ResponseSpeech (TTS)
您的血压是 100/80。

ResponseText
您的血压是 100/80。
ResponseSpeech (TTS)
您的血压是 100/80。

ResponseText
您的血压是一百除以八十。
符号
使用专门的符号而不是文字,使视觉内容更容易一目了然。
ResponseSpeech (TTS)
您上次购买的商品价格为 24.65 美元。

ResponseText
您上次购买的商品价格为 24.65 美元。
ResponseSpeech (TTS)
您上次购买的商品价格为 24 美元和 65 美分。

ResponseText
您上次购买的商品价格为 24 美元和 65 美分。
避免客套话
客套话使回复听起来很疏远和正式。放弃它们,保持对话友好和非正式。
ResponseSpeech (TTS)
您的订单已送达。

ResponseText
您的订单已送达。
ResponseSpeech (TTS)
当然,我可以告诉您。您的订单已送达。

ResponseText
当然,我可以告诉您。您的订单已送达。
避免使用感叹号
它们可能被认为是在喊叫。
ResponseSpeech (TTS)
您今天跑了 1.5 英里。

ResponseText
您今天跑了 1.5 英里。
ResponseSpeech (TTS)
您今天跑了 1.5 英里!

ResponseText
您今天跑了 1.5 英里!
时间
使用数字:“5:15”,而不是“five-fifteen”或“quarter after five”。对于 12 小时制时钟,使用 AM 或 PM。
ResponseSpeech (TTS)
您的送货预计将在上午 8:15 到达。

ResponseText
您的送货预计将在上午 8:15 到达。
ResponseSpeech (TTS)
您的送货预计将在今天早上 8 点 15 分到达。

ResponseText
您的送货预计将在今天早上 8 点 15 分到达。
不要进行长篇大论
要有信息量,但要保持简明扼要。不要在没有明显的用户益处的情况下进行过多的详细说明。
ResponseSpeech (TTS)
您上个月使用了 159 小时的能量。

ResponseText
您上个月使用了 159 小时的能量。
ResponseSpeech (TTS)
节约能源对地球和环境非常重要。您上个月使用了 159 小时的能量。本月您已经使用了 58 小时的能量。

ResponseText
节约能源对地球和环境非常重要。您上个月使用了 159 小时的能量。本月您已经使用了 58 小时的能量。
使用简短、简单的词语
简单明了的语言具有最广泛的吸引力,使其对所有背景的人来说都很容易理解。
ResponseSpeech (TTS)
您上次血糖读数为 126。

ResponseText
您上次血糖读数为 126 mg/dL。
ResponseSpeech (TTS)
倒数第二次血糖水平为 126。

ResponseText
倒数第二次血糖水平为 126。

启动器固定

小部件扩展库使“添加此小部件”按钮可以与您的小部件一起在助理中显示。要启用固定,请将以下接收器定义添加到 AndroidManifest.xml

<application>
  <receiver android:name="com.google.assistant.appactions.widgets.pinappwidget.PinAppWidgetBroadcastReceiver"
    android:exported="false">
    <intent-filter>
      <action android:name="com.google.assistant.appactions.widgets.COMPLETE_PIN_APP_WIDGET" />
    </intent-filter>
  </receiver>
  <service
    android:name=
    "com.google.assistant.appactions.widgets.pinappwidget.PinAppWidgetService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
      <action
        android:name="com.google.assistant.appactions.widgets.PIN_APP_WIDGET" />
    </intent-filter>
  </service>
</application>

库存可用性

支持 内联库存网页库存 的 BII 可以将这些库存扩展到您的应用小部件完成。

内联库存

以下来自示例 shortcuts.xml 文件的代码演示了为内联库存和小部件完成配置的 START_EXERCISE BII 功能。

<capability
  android:name="actions.intent.START_EXERCISE">
  <app-widget
    android:identifier="START_EXERCISE_1"
    android:targetClass="com.example.exampleapp.StartExerciseAppWidgetProvider">
    <parameter
      android:name="exercise.name"
      android:key="exerciseName"
      app:shortcutMatchRequired="true">
    </parameter>
  </app-widget>
</capability>

<shortcut android:shortcutId="RunningShortcut">
  <intent
    android:action="android.intent.action.VIEW"
    android:targetClass="com.example.exampleapp.StartExcerciseActivity" />
  <capability-binding
    android:capability="actions.intent.START_EXERCISE"
    android:parameter="exercise.name"
    android:value="running;runs" />
</shortcut>

在前面的示例中,当用户通过询问助理“使用 ExampleApp 开始跑步”来触发此功能时,<app-widget> 完成的选项包将包含以下键值对:

  • 键 = “exerciseName”
  • 值 = “RunningShortcut”

网页库存

以下来自示例 shortcuts.xml 文件的代码显示了为网页库存和小部件完成启用的功能。

<shortcuts>
  <capability
    android:name="actions.intent.START_EXERCISE">
    <app-widget
      android:identifier="START_EXERCISE_1"
      android:targetClass="com.example.exampleapp.CreateTaxiAppWidgetProvider">
      <parameter
        android:name="exercise.name"
        android:key="exerciseName"
        android:mimeType="text/*">
        <data android:pathPattern="https://exampleapp.com/exercise/.*" />
      </parameter>
    </app-widget>
  </capability>
</shortcuts>

测试应用操作

使用应用操作测试工具(Android Studio 的 Google 助理插件 的一项功能)在物理设备或虚拟设备上测试小部件。要使用该测试工具,请执行以下步骤

  1. 将您的测试设备连接到运行您的应用的设备。
  2. 在 Android Studio 中,转到工具 > 应用操作 > 应用操作测试工具
  3. 单击创建预览
  4. 使用 Android Studio,在您的测试设备上运行您的应用。
  5. 使用测试设备上的助理应用来测试您的应用操作。例如,您可以说“嘿 Google,本周我在 ExampleApp 上跑了多少英里?”
  6. 观察应用的行为,或使用 Android Studio 调试器 来验证所需的操作结果。

质量指南

本节重点介绍将应用操作与小部件集成时的关键要求和最佳实践。

小部件中的内容

  • 必需)不要在您的应用小部件中显示广告。
  • 使小部件内容完全专注于完成意图。不要尝试用一个小部件完成多个意图,也不要添加无关的内容。

处理身份验证

  • 必需)在需要用户身份验证才能完成用户流程的地方,返回一个小部件,说明用户需要在应用中继续。Google 助理中不支持应用操作的内联用户身份验证。
  • 如果用户允许您的应用使用小部件显示数据,则可以在运行时为未经授权的用户返回错误小部件。

回退意图

  • 必需)在您的 shortcuts.xml 中,始终为给定功能提供回退 <intent> 以及您的应用小部件完成。回退意图是一个没有必需 <parameter> 值的 <intent> 元素。

    这使助理能够在用户查询不包含功能中定义的其他完成元素所需的参数时完成操作。唯一的例外是,当该功能没有必需参数时,在这种情况下,只需要应用小部件完成。

  • 使用回退意图打开应用的相应屏幕,而不是主屏幕。

以下来自示例 shortcuts.xml 文件的代码演示了具有支持主要 <app-widget> 完成的回退 <intent><capability>

<shortcuts>
  <capability
    android:name="actions.intent.CREATE_TAXI_RESERVATION">
    <!-- Widget with required parameter, specified using the "android:required" attribute. -->
    <app-widget
      android:identifier="CREATE_TAXI_RESERVATION_1"
      android:targetClass="com.example.myapplication.CreateTaxiAppWidgetProvider">
      <parameter
        android:name="taxiReservation.dropoffLocation.name"
        android:key="dropoff"
        android:required="true">
      </parameter>
    </app-widget>
    <!-- Fallback intent with no parameters required to successfully execute. -->
    <intent
      android:identifier="CREATE_TAXI_RESERVATION_3"
      android:action="myapplication.intent.CREATE_TAXI_RESERVATION_1"
      android:targetClass="com.example.myapplication.TaxiReservationActivity">
    </intent>
  </capability>
</shortcuts>

Google Play 数据披露

本节列出了最新版 Widgets 扩展库收集的最终用户数据。

此 SDK 发送开发人员提供的文本转语音 (TTS) 响应,这些响应由 Google 助理使用其语音技术宣布给用户。 Google 不会存储此信息。

应用操作 还可能收集客户端应用元数据,用于以下目的:

  • 监控不同 SDK 版本的采用率。
  • 量化应用中的 SDK 功能使用情况。