将与应用有关的 Action 与 Android widget 集成

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。
图 1. 启动 GET_EXERCISE_OBSERVATION 的 widget。

对于许多 intent,最佳的响应就是向用户提供简单的回答、简要的确认或快速的互动体验。您可以在 Google 助理中显示 Android 应用 widget 来执行这些类型的 intent。

本指南介绍如何使用 widget 执行 Google 助理用户查询,以及如何利用与应用有关的 Action 的 Widgets Extension 库来改善 Google 助理的 widget 体验。

优点

widget 是可以嵌入到 Android Surface(如启动器或锁定屏幕)上的微型应用视图。借助与应用有关的 Action,您可以通过让 widget 能够在 Google 助理中显示来提高其使用率:

  1. 发现:主动显示 widget 来响应用户的自然语言查询。
  2. 互动:在免触摸上下文中显示 widget,例如当 Google 助理在锁定屏幕和 Android Auto 上提供个人信息相关结果时。
  3. 保留:允许用户将 Google 助理中显示的 widget 固定到其启动器上。固定功能需要用到 Widgets Extension 库

Google 助理如何显示 widget

用户可以通过两种方式在 Google 助理上调用 widget:

  • 通过名称明确请求 widget
  • 向 Google 助理说出查询来触发为 widget 执行方式配置的内置 intent (BII) 或自定义 intent

显式调用

如需显式调用任何已安装应用的 widget,用户可以向 Google 助理发出诸如以下的请求:

  • “Hey Google, show ExampleApp widget.”
  • “Widgets from ExampleApp”

Google 助理会显示这些 widget 并提供大致的介绍:“ExampleApp says, here's a widget”。虽然 Google 助理原生就会以这种方式返回请求的 widget,而无需应用开发者做任何工作,但这种调用方法要求用户明确知道要请求的 widget。如需简化 widget 发现过程,请使用下一部分中详细介绍的 intent 执行方式。

intent 执行方式

通过使用 widget 来执行用户在 Google 助理上进行的自然语言查询,使 widget 更容易被找到。例如,您可以在用户通过询问“Hey Google, how many miles have I run this week on ExampleApp?”在健身应用中触发 GET_EXERCISE_OBSERVATION BII 时,返回 widget。除了简化发现过程之外,将与应用有关的 Action 和 widget 集成还有以下优势:

  • 参数访问:Google 助理会将从用户查询中提取的 intent 参数提供给您的 widget,从而实现贴合用户需求的响应
  • 自定义 TTS 简介:您可以提供文字转语音 (TTS) 字符串,以供 Google 助理在显示 widget 时读出。
  • widget 固定:Google 助理会在 widget 旁边显示添加此 widget 按钮,以便用户轻松将 widget 固定到其启动器。

实现 widget

如需为您的 intent 实现 widget 执行方式,请按以下步骤操作:

  1. 按照创建简单 widget 中所述的步骤实现 Android widget。
  2. 在应用的 shortcuts.xml 资源文件中,向包含执行方式详细信息和 BII <parameter> 标记的 capability 中添加 <app-widget> 元素。请更新您的 widget 以处理这些参数。
  3. (必须)添加 Widgets Extension 库,以允许 Google 助理将 BII 名称和参数传递给 widget。此外,该库还支持自定义 TTS 简介和 widget 固定功能。

以下部分介绍 shortcuts.xml<app-widget> 架构。

widget 架构

<app-widget> 元素在 shortcuts.xml 中的 <capability> 元素内定义为执行方式。必须提供以下属性,但注明为“可选”的属性除外:

Shortcuts.xml 标记包含于属性
<app-widget> <capability>
  • android:identifier
  • android:targetClass
<parameter> <app-widget>
<extra> <app-widget>
  • android:name(仅适用于 TTS)
  • android:value(可选)

widget 架构说明

<app-widget>

顶层 widget 执行方式元素。

属性:

  • android:identifier:此执行方式的唯一标识符。此值在 <capability> 中定义的 <app-widget><intent> 执行方式元素之间应具有唯一性。
  • android:targetClass:用于处理 intent 的 AppWidgetProvider 的完整类名。

<parameter>

将 BII 参数映射到 intent <parameter> 值。您可以为每个 <app-widget> 元素定义零个或多个参数。在执行过程中,Google 助理通过以键值对的形式更新 widget 实例的 extra 来传递参数,格式如下:

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

您可以通过对关联的 AppWidgetManager 对象调用 getAppWidgetOptions() 来访问这些 extra,此调用会返回包含触发 BII 的名称及其参数的 Bundle。如需了解详情,请参阅提取参数值

如需详细了解 BII 参数匹配,请参阅参数数据和匹配

<extra>

可选标记,用于声明应为此 widget 使用自定义 TTS 简介。此标记需要以下属性值:

  • android:name:“hasTts”
  • android:value:“true”

示例代码

以下 XML 展示了 GET_EXERCISE_OBSERVATION BII capability 的 widget 执行方式配置:

shortcuts.xml

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

您可以为每个 capability 指定多个 <app-widget> 元素或结合使用 <app-widget><intent> 元素。这种做法可让您根据用户提供的不同参数组合来提供自定义体验。例如,如果用户没有在其查询中指定下车点,您就可以将用户引导到应用中显示上车点和下车点设置选项的 activity。如需详细了解如何定义回退 intent,请参阅回退 intent 部分。

提取参数值

在以下示例 AppWidgetProvider 类中,使用私有函数 updateAppWidget() 从 widget 选项 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 may 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 may 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);
    }
}

Widgets Extension 库

与应用有关的 Action 的 Widgets Extension 库可增强您的 widget,以实现语音优先的 Google 助理体验。此库可让 widget 接收触发的 BII 中的重要执行方式信息,包括 BII 名称和从用户查询中提取的所有 intent 参数。借助此 Maven 库,您可以为每个 widget 提供自定义文字转语音 (TTS) 简介,以便 Google 助理向用户读出正在视觉呈现的内容的摘要。该库还支持实现启动器固定功能,以便用户能够轻松将 Google 助理中显示的 widget 保存到其启动器屏幕。

首先,将该库添加到应用模块的 build.gradle 文件的依赖项部分:

app/build.gradle

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

自定义简介

导入 Widgets Extension 库后,就可以为 widget 提供自定义 TTS 简介。如需将定义添加到 widget 的 AppWidgetProvider,请在 IDE 中打开该类并导入 Widgets Extension 库:

Kotlin

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

Java

import com.google.assistant.appactions.widgets.AppActionsWidgetExtension;
接下来,使用该库定义简介字符串并更新 widget:
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);
    }

}

启动器固定

该库支持在 Google 助理中的 widget 旁边显示添加此 widget 按钮。需要将以下接收器定义添加到 AndroidManifest.xml 才能实现固定功能:

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 可以将这两种目录扩展到您的 widget 执行方式。

内嵌目录

以下代码示例展示了为内嵌目录和 widget 执行方式配置的 START_EXERCISE BII capability:

shortcuts.xml

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

在上述示例中,当用户通过向 Google 助理发出“Start running with ExampleApp”请求来触发此 capability 时,<app-widget> 执行方式的选项 bundle 就会包含以下键值对:

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

网站目录

网站目录和 widget 执行方式启用的 capability 的示例代码如下:

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>

测试

与应用有关的 Action 测试工具是适用于 Android Studio 的 Google 助理插件的一项功能,可用于在实体设备或虚拟设备上测试 widget。如要使用该测试工具,请按以下步骤操作:

  1. 连接运行您的应用的测试设备。
  2. 在 Android Studio 中,依次转到 Tools > App Actions > App Actions Test Tool
  3. 点击 Create preview
  4. 在 Android Studio 中,在测试设备上运行您的应用。
  5. 使用测试设备上的 Google 助理应用测试与应用有关的 Action。例如,您可以这样说:“Hey Google, how many miles have I run this week on ExampleApp?”。
  6. 观察应用的行为,或使用 Android Studio 调试程序来验证是否取得了所需的操作结果。

质量指南

本部分重点介绍将与应用有关的 Action 与 widget 集成的关键要求和最佳做法。

widget 中的内容

  • 必需)不得在 widget 中展示广告。
  • widget 内容应完全专注于执行 intent,不要试图用一个 widget 执行多个 intent,也不要添加不相关的内容。

处理身份验证

  • 必需)如果需要对用户进行身份验证才能完成用户流,则应返回一个 widget,用于说明用户需要在应用中继续操作。与应用有关的 Action 不支持 Google 助理中的内嵌用户身份验证。
  • 如果用户允许您的应用通过 widget 显示数据,您可以在运行时为未经授权的用户返回错误 widget。

回退 intent

  • 必需)在 shortcuts.xml 中,除 widget 执行方式外,还应始终为给定 capability 提供一个回退 <intent>。回退 intent 是不含必需的 <parameter> 值的 <intent> 元素。这样一来,当用户查询中不包含该 capability 中定义的其他执行方式元素所需的参数时,Google 助理仍可执行操作。对此有一个例外,那就是该 capability 没有必需的参数,在这种情况下,只要有 widget 执行方式就够了。
  • 回退 intent 应在应用中打开相关屏幕,而不是主屏幕。

以下示例代码展示了具有支持主要 <app-widget> 执行方式的回退 <intent><capability>

shortcuts.xml

<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 Extension 库会收集的最终用户数据。

此 SDK 会发送开发者提供的文字转语音 (TTS) 响应,这些响应将由 Google 助理使用 Google 助理的语音技术向用户读出。Google 不会存储这些信息。

与应用有关的 Action 也可能会出于以下目的收集客户端应用元数据:

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