Как интегрировать действия в приложении с виджетами Android

Рисунок 1. Запуск виджета для GET_EXERCISE_OBSERVATION.

Во многих случаях лучшим ответом будет простое подтверждение или краткое интерактивное взаимодействие. Для выполнения таких намерений можно показывать в Google Ассистенте виджет приложения Android.

В этом руководстве рассказывается, как выполнять запросы пользователей Ассистента с помощью виджетов и как улучшить работу виджетов для Ассистента с помощью библиотеки расширений виджетов для действий в приложении.

Преимущества

Виджеты – это миниатюрные представления приложений, которые можно встраивать в интерфейс Android, например на главный или заблокированный экран. Действия в приложениях повышают эффективность виджетов, позволяя показывать их в Ассистенте.

  1. Обнаружение. Виджеты показываются в ответ на запросы пользователей на естественном языке.
  2. Вовлеченность. Виджеты можно показывать в ситуациях, когда используется голосовое управление, например когда Ассистент предоставляет персональные результаты на заблокированном экране или в Android Auto.
  3. Удержание. Позвольте пользователям закреплять виджеты, которые показываются в Ассистенте, на панели запуска. Для закрепления виджетов требуется библиотека расширений виджетов.

Как Ассистент показывает виджеты

Пользователи могут вызывать виджеты в Ассистенте двумя способами:

  • Явный запрос виджета по названию.
  • Пользователь произносит запрос, который активирует встроенное или специальное намерение, настроенное для выполнения виджета.

Явный вызов

Чтобы явно вызвать виджеты для любого установленного приложения, пользователи могут сказать Ассистенту, например:

  • "Окей, Google, покажи виджет ExampleApp".
  • "Виджеты из ExampleApp".

Ассистент показывает эти виджеты со стандартным вступлением: "Вот виджет приложения ExampleApp". Ассистент возвращает виджеты, запрошенные таким образом, без каких-либо действий со стороны разработчика приложения, но для этого способа вызова пользователю необходимо знать о виджете, чтобы запросить его. Чтобы упростить поиск виджетов, используйте метод выполнения намерений, описанный в следующем разделе.

Выполнение намерения

Чтобы ваши виджеты было проще найти, используйте их для выполнения запросов на естественном языке, которые пользователи отправляют Ассистенту. Например, вы можете настроить показ виджета, когда пользователь активирует встроенное намерение GET_EXERCISE_OBSERVATION в фитнес-приложении, спрашивая: Окей, Google, сколько километров я пробежал на этой неделе в ExampleApp? Помимо упрощения поиска, интеграция виджетов с действиями в приложении дает следующие преимущества:

  • Доступ к параметрам. Ассистент передает виджету параметры намерения, извлеченные из запроса пользователя, что позволяет персонализировать ответы.
  • Собственные вступления TTS. Вы можете указать строку преобразования текста в речь, которую Ассистент будет озвучивать при показе виджета.
  • Закрепление виджетов. Рядом с вашим виджетом Ассистент показывает кнопку Добавить этот виджет, чтобы пользователи могли легко закрепить его на панели запуска.

Как реализовать выполнение виджета

Чтобы реализовать выполнение виджетов для ваших намерений, выполните следующие действия:

  1. Реализуйте виджет Android, следуя инструкциям в статье Как создать простой виджет.
  2. В файле ресурсов shortcuts.xml добавьте элемент <app-widget> к функции, содержащей сведения о выполнении и теги BII <parameter>. Обновите виджет, чтобы он мог обрабатывать параметры.
  3. Добавьте необходимую библиотеку расширения виджетов, которая позволяет Ассистенту передавать названия и параметры встроенных намерений в виджеты. Также вы можете настроить приветствие и закрепить виджет.

В следующем разделе описана схема <app-widget> для shortcuts.xml.

Схема виджета

Элементы <app-widget> определяются как способы выполнения в элементах <capability> в shortcuts.xml. Для них требуются следующие атрибуты (если не указано, что они необязательные):

Тег shortcuts.xmlВходит вАтрибуты
<app-widget> <capability>
  • android:identifier
  • android:targetClass
<parameter> <app-widget>
<extra> <app-widget>
  • android:name (только для TTS)
  • android:value (необязательно)

Описание схемы виджета

<app-widget>

Элемент выполнения виджета верхнего уровня.

Атрибуты:

  • android:identifier – идентификатор выполнения. Это значение должно быть уникальным для элементов выполнения <app-widget> и <intent>, определенных в <capability>.
  • android:targetClass – полное название класса AppWidgetProvider, который должен обработать намерение.

<parameter>

Сопоставляет параметр встроенного интента со значением <parameter> интента. Для каждого элемента <app-widget> можно задать любое количество параметров. При выполнении Ассистент передает параметры, обновляя дополнительные данные для экземпляра виджета в виде пар "ключ-значение" в следующем формате:

  • Ключ – android:key, заданное для параметра.
  • Value – значение, которое встроенный интент извлекает из голосового ввода пользователя.

Чтобы получить доступ к этим дополнительным данным, вызовите getAppWidgetOptions() для связанного объекта AppWidgetManager. Это вернет Bundle с названием встроенного намерения, которое активировало действие, и его параметрами. Подробнее о том, как извлекать значения параметров…

Подробнее о сопоставлении параметров встроенных намерений…

<extra>

Необязательный тег, который указывает, что для виджета используется собственное вступление, озвучиваемое синтезатором речи. Для этого тега требуются следующие значения атрибутов:

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

Пример кода

В следующем примере из файла shortcuts.xml показана конфигурация выполнения виджета для функции BII GET_EXERCISE_OBSERVATION.

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

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);
    }
}

Библиотека расширений виджетов

Библиотека расширений виджетов для действий в приложении позволяет улучшить виджеты для голосового управления Ассистентом. Эта библиотека позволяет виджетам получать важную информацию о выполнении от встроенного намерения, которое их активировало, включая название намерения и любые параметры, извлеченные из запроса пользователя.

Эта библиотека Maven позволяет вам предоставить собственное введение для преобразования текста в речь (TTS) для каждого виджета, позволяя Ассистенту объявлять пользователям сводку визуально отображаемого контента. Кроме того, пользователи могут закреплять виджеты, которые показывает Ассистент, на экране запуска.

Для начала добавьте библиотеку в раздел зависимостей файла build.gradle для модуля приложения:

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

Специальные вступления

После импорта библиотеки расширения виджетов вы можете предоставить собственные вступления TTS для своих виджетов. Чтобы добавить определение в AppWidgetProvider виджета, откройте класс в IDE и импортируйте библиотеку Widgets Extension:

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. Сообщения без сокращений звучат неестественно и роботизированно. Такие слова, как "нельзя" и "не делай", могут звучать как наказание и восприниматься слишком резко.
ResponseSpeech (TTS)
К сожалению, я не могу найти бронирование.

ResponseText
Извините, я не могу найти бронирование.
ResponseSpeech (TTS)
К сожалению, я не могу найти бронирование.

ResponseText
К сожалению, я не могу найти бронирование.
Запятые
Используйте серийные запятые в списках из трех и более элементов, чтобы сделать текст понятнее. Без нее отдельные элементы списка могут быть неправильно восприняты на слух или прочитаны как группы. Например, в предложении "нарциссы, маргаритки и подсолнухи" слова "маргаритки и подсолнухи" звучат так, будто они связаны. В примере "нарциссы, маргаритки и подсолнухи" все три элемента четко разделены.
ResponseSpeech (TTS)
Самые популярные – желтые розы, нарциссы, маргаритки и подсолнухи.

ResponseText
Самые популярные – желтые розы, нарциссы, маргаритки и подсолнухи.
ResponseSpeech (TTS)
Самые популярные – желтые розы, нарциссы, маргаритки и подсолнухи.

ResponseText
Самые популярные из них – желтые розы, нарциссы, маргаритки и подсолнухи.
Цифры
Используйте цифры вместо текста, чтобы визуальный контент было проще воспринимать.
ResponseSpeech (TTS)
Ваше артериальное давление – 100 на 80.

ResponseText
Ваше артериальное давление – 100 на 80.
ResponseSpeech (TTS)
Ваше артериальное давление – 100 на 80.

ResponseText
Ваше артериальное давление – 100 на 80.
Символы
Используйте специальные символы вместо текста, чтобы сделать визуальный контент более наглядным.
ResponseSpeech (TTS)
Ваша последняя покупка была на сумму 24,65 доллара США.

ResponseText
Ваша последняя покупка была на сумму 24,65 доллара США.
ResponseSpeech (TTS)
Your last purchase was for twenty-four dollars and sixty-five cents.

ResponseText
Ваша последняя покупка была на сумму двадцать четыре доллара и шестьдесят пять центов.
Избегайте любезностей
Из-за любезностей ответы кажутся отстраненными и формальными. Избегайте их и придерживайтесь дружелюбного и неформального тона.
ResponseSpeech (TTS)
Ваш заказ доставлен.

ResponseText
Ваш заказ доставлен.
ResponseSpeech (TTS)
Конечно, я могу рассказать вам об этом. Ваш заказ доставлен.

ResponseText
Конечно, я могу рассказать вам об этом. Ваш заказ доставлен.
Не используйте восклицательные знаки.
Они могут восприниматься как крик.
ResponseSpeech (TTS)
Сегодня вы пробежали 1,5 мили.

ResponseText
Сегодня вы пробежали 1,5 мили.
ResponseSpeech (TTS)
Вы пробежали 1,5 мили сегодня!

ResponseText
Сегодня вы пробежали 1,5 мили!
Время
Используйте цифры: "5:15" вместо "пять пятнадцать" или "четверть шестого". Для 12-часового формата используйте AM или PM.
ResponseSpeech (TTS)
Your delivery should arrive by 8:15 AM.

ResponseText
Your delivery should arrive by 8:15 AM.
ResponseSpeech (TTS)
Your delivery should arrive by 15 mins past 8 in the morning today.

ResponseText
Доставка должна прибыть сегодня к 8:15.
Не начинайте монолог
Отвечай информативно, но кратко. Не вдавайтесь в подробности, если это не принесет пользователю явной пользы.
ResponseSpeech (TTS)
В прошлом месяце вы использовали 159 часов энергии.

ResponseText
В прошлом месяце вы использовали 159 часов энергии.
ResponseSpeech (TTS)
Экономия энергии очень важна для планеты и окружающей среды. В прошлом месяце вы использовали 159 часов энергии. В этом месяце вы использовали 58 часов энергии.

ResponseText
Экономия энергии очень важна для планеты и окружающей среды. В прошлом месяце вы использовали 159 часов энергии. В этом месяце вы использовали 58 часов энергии.
Используйте короткие и простые слова.
Простой и понятный язык привлекает самую широкую аудиторию, делая контент доступным для людей с любым уровнем подготовки.
ResponseSpeech (TTS)
Последний показатель содержания сахара в крови – 126.

ResponseText
Последний показатель содержания сахара в крови – 126 мг/дл.
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>

Доступность инвентаря

Встроенные интенты, поддерживающие встроенный инвентарь или веб-инвентарь, могут расширить этот инвентарь до выполнения виджетов.

Встроенный инвентарь

В приведенном ниже примере кода из файла shortcuts.xml показана функция встроенных намерений START_EXERCISE, настроенная для встроенного инвентаря и выполнения виджета.

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

Тестирование действий в приложении

Чтобы протестировать виджеты на физическом или виртуальном устройстве, используйте инструмент тестирования действий в приложении, который входит в плагин Google Ассистента для Android Studio. Чтобы воспользоваться инструментом тестирования, выполните следующие действия:

  1. Подключите тестовое устройство с запущенным приложением.
  2. В Android Studio выберите Tools (Инструменты) > App Actions (Действия в приложении) > App Actions Test Tool (Инструмент тестирования действий в приложении).
  3. Нажмите Создать предпросмотр.
  4. Запустите приложение на тестовом устройстве с помощью Android Studio.
  5. Протестируйте действие в приложении с помощью приложения "Ассистент" на тестовом устройстве. Например, вы можете сказать: "Окей, Google, сколько километров я пробежал на этой неделе по данным ExampleApp?"
  6. Проверьте, как работает приложение, или используйте отладчик Android Studio, чтобы убедиться, что результат действия соответствует ожидаемому.

Рекомендации по обеспечению качества

В этом разделе описаны основные требования и рекомендации по интеграции действий в приложении с виджетами.

Контент в виджетах

  • Обязательное требование. Не показывайте рекламу в виджетах.
  • Контент виджета должен быть полностью сосредоточен на выполнении намерения. Не пытайтесь реализовать несколько намерений в одном виджете или добавить нерелевантный контент.

Как настроить аутентификацию

  • (Обязательно.) Если для завершения пути пользователя требуется аутентификация, верните виджет, в котором объясняется, что пользователю нужно продолжить в приложении. Встроенная аутентификация пользователя в Google Ассистенте не поддерживается для действий в приложениях.
  • Если пользователи разрешают вашему приложению показывать данные с помощью виджетов, вы можете возвращать виджет ошибки во время выполнения для неавторизованных пользователей.

Резервные намерения

  • (Обязательно) В файле shortcuts.xml всегда указывайте резервный вариант <intent> в дополнение к выполнению виджета для определенной функции. Резервное намерение – это элемент <intent> без обязательных значений <parameter>.

    Это позволяет Ассистенту выполнить действие, когда запрос пользователя не содержит параметров, необходимых для других элементов выполнения, определенных в функции. Исключение составляют случаи, когда для функции не требуются обязательные параметры. Тогда нужно только выполнение виджета.

  • Используйте резервный интент, чтобы открывать нужный экран приложения, а не главный.

В приведенном ниже фрагменте кода из файла shortcuts.xml показан элемент <capability> с резервным элементом <intent>, который поддерживает выполнение основного элемента <app-widget>:

<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

Ниже вы найдете сведения о том, какие данные о конечных пользователях собирает последняя версия библиотеки расширения виджетов.

Этот SDK отправляет ответы, которые Google Ассистент озвучивает пользователю с помощью технологии синтеза речи. Google не хранит эту информацию.

Действия в приложениях также могут собирать метаданные клиентских приложений для следующих целей:

  • Отслеживать популярность разных версий SDK.
  • Чтобы оценить использование функций SDK в приложениях.