Голосовизация приложения (beta)
ТВ приложение может быть интегрировано с голосовым помощником Алисой, что позволяет пользователю управлять интерфейсом с помощью голосовых команд: нажимать на элементы приложения, управлять воспроизведением и вызывать функции приложений (например, поиск).
Возможность пока не поддерживается для всех разработчиков приложений. Если вы хотите протестировать голосовизацию приложений — подайте заявку.
- Для корректной работы голосового управления приложение должно обязательно поддерживать спецификацию доступности (accessibility).
- Приложения с проигрываемым контентом (музыка, видео, трансляции) должны поддерживать медиа-сессии.
- Реализовать продвинутые возможности для управления голосом (поиск, переходы между разделами и т. д.) можно с помощью технологии AppFunctions.
Спецификация доступности (accessibility)
Чтобы пользователи могли нажимать на кнопки голосом, переключаться по разделам, управлять воспроизведением и т. д., приложение должно быть адаптировано в соответствии с рекомендациями по разметке доступности от Google. Это необходимо для того, чтобы голосовой помощник мог корректно обнаруживать и взаимодействовать с элементами интерфейса.
Что необходимо реализовать
Из рекомендаций по доступности особенно важно реализовать следующие требования:
-
Пользовательский элемент управления
View, на который можно нажимать или переводить на него фокус, должен корректно сообщать об этом службе доступности (Accessibility). Это позволяет голосовому помощнику правильно распознавать и управлять такими элементами. -
Все кликабельные или фокусируемые изображения и иконки должны иметь заданный
contentDescription. Например, иконка поиска в верхнем меню должна иметьcontentDescription="Search button"или аналогичное описание. Название фильма или сериала, отображаемое в интерфейсе, должно быть также доступно черезcontentDescription.
Подробнее о том, как реализовать доступность в приложении, можно узнать в официальных гайдах Google для View, Custom View или Jetpack Compose.
Внимание
Явное или неявное использование AndroidView из Compose нарушает совместимость с нашей технологией.
Как проверить
Чтобы проверить текущую разметку в приложении через UIAutomator:
-
Запустите приложение.
-
Откройте нужный экран.
-
Выполните следующую команду:
adb shell uiautomator dump -
Откройте сгенерированный файл
/sdcard/window_dump.xmlи изучите разметку.
Примеры корректной реализации карусели с карточками фильмов
Откроем экран приложения, на котором отображается карусель с карточками фильмов.
Запустим команду uiautomator, получим XML-файл с описанием интерфейса и найдем в нем, например, второй элемент из карусели:
<node index="1" text="" resource-id="" class="android.widget.FrameLayout" package="ru.kinopoisk.yandex.tv" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[708,614][1214,1016]">
<node index="0" text="" resource-id="ru.kinopoisk.yandex.tv:id/composeView" class="androidx.compose.ui.platform.ComposeView" package="ru.kinopoisk.yandex.tv" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[708,614][1214,1016]">
<node index="0" text="" resource-id="" class="android.view.View" package="ru.kinopoisk.yandex.tv" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[708,614][1214,1016]">
<node index="0" text="" resource-id="" class="android.view.View" package="ru.kinopoisk.yandex.tv" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[710,616][1212,1014]">
<node index="0" text="" resource-id="" class="android.view.View" package="ru.kinopoisk.yandex.tv" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[710,616][1212,870]"/>
<node index="1" text="1 ч 53 мин" resource-id="" class="android.widget.TextView" package="ru.kinopoisk.yandex.tv" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[1081,849][1200,870]"/>
<node index="2" text="" resource-id="" class="android.widget.ProgressBar" package="ru.kinopoisk.yandex.tv" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[710,870][1212,918]"/>
<node index="3" text="Джентльмены" resource-id="" class="android.widget.TextView" package="ru.kinopoisk.yandex.tv" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[710,914][1188,946]"/>
</node>
</node>
</node>
</node>
Можно сделать следующие выводы:
-
элемент кликабелен (
clickable="true"); -
у карточки присутствует текстовое описание.
Это означает, что элемент корректно передает информацию в службу доступности, и Алиса сможет его распознать и активировать.
Пример некорректной реализации карусели с карточками фильмов
Откроем экран приложения, на котором отображается карусель с карточками фильмов.
Запустим команду uiautomator, получим XML-файл с описанием интерфейса и найдем в нем, например, первый элемент из карусели:
<node NAF="true" index="5" text="" resource-id="" class="android.widget.FrameLayout" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[1910,824][1920,1080]">
<node index="0" text="" resource-id="ru.amediateka:id/poster" class="android.widget.FrameLayout" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[1910,868][1920,1080]">
<node index="0" text="" resource-id="ru.amediateka:id/main_image" class="android.widget.ImageView" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[1910,868][1920,1080]"/>
</node>
</node>
Можно сделать следующие выводы:
-
элемент кликабелен (
clickable="true"); -
у карточки отсутствует текстовое описание
contentDescriptionилиtext.
Это означает, что элемент не передает необходимую информацию в службу доступности, и Алиса не сможет его распознать или активировать.
Пример некорректной реализации элементов бокового меню
Откроем любой экран приложения, на котором отображается боковое меню.
Запустим команду uiautomator, получим XML-файл с описанием интерфейса и найдем в нем элементы бокового меню:
<node index="0" text="" resource-id="ru.amediateka:id/main_menu_fragment" class="android.widget.FrameLayout" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][84,1080]">
<node index="0" text="" resource-id="ru.amediateka:id/list" class="androidx.recyclerview.widget.RecyclerView" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,162][84,918]">
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,198][84,282]">
<node index="0" text="" resource-id="ru.amediateka:id/icon" class="android.widget.ImageView" package="ru.amediateka" content-desc="Amediateka" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[18,222][66,258]"/></node>
<node index="1" text="" resource-id="" class="android.widget.FrameLayout" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,318][84,402]">
<node index="0" text="" resource-id="ru.amediateka:id/icon" class="android.widget.ImageView" package="ru.amediateka" content-desc="Amediateka" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[18,342][66,378]"/></node>
<node index="2" text="" resource-id="" class="android.widget.FrameLayout" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,438][84,522]">
<node index="0" text="" resource-id="ru.amediateka:id/icon" class="android.widget.ImageView" package="ru.amediateka" content-desc="Amediateka" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[18,462][66,498]"/></node>
<node index="3" text="" resource-id="" class="android.widget.FrameLayout" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,558][84,642]">
<node index="0" text="" resource-id="ru.amediateka:id/icon" class="android.widget.ImageView" package="ru.amediateka" content-desc="Amediateka" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[18,582][66,618]"/></node>
<node index="4" text="" resource-id="" class="android.widget.FrameLayout" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,678][84,762]">
<node index="0" text="" resource-id="ru.amediateka:id/icon" class="android.widget.ImageView" package="ru.amediateka" content-desc="Amediateka" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[18,702][66,738]"/></node>
<node index="5" text="" resource-id="" class="android.widget.FrameLayout" package="ru.amediateka" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,798][84,882]">
<node index="0" text="" resource-id="ru.amediateka:id/icon" class="android.widget.ImageView" package="ru.amediateka" content-desc="Amediateka" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[18,822][66,858]"/>
</node>
</node>
</node>
Можно сделать следующие выводы:
-
элементы некликабельны (
clickable="false"); -
у элементов отсутствует текстовое описание
contentDescriptionилиtext.
Элементы нельзя отличить друг от друга и понять, для чего они нужны. Они не передают необходимую информацию в службу доступности, и Алиса не сможет их распознать или активировать.
Медиа-сессии
Для видео-контента необходимо реализовать поддержку медиа-сессии. Это позволяет пользователям Яндекс ТВ Станции управлять воспроизведением — ставить контент на паузу, возобновлять просмотр и перематывать видео.
Чтобы реализовать медиа-сессию перед началом воспроизведения видео-контента, создайте MediaSession и установите callback и флаги:
session = MediaSession(this, "MusicService").apply {
setCallback(MediaSessionCallback())
setFlags(
MediaSession.FLAG_HANDLES_MEDIA_BUTTONS or MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS
)
}
Подробнее о том, как создавать медиа-сессии и управлять мультимедиа с их помощью, можно узнать в официальном гайде Google.
Технология AppFunctions
В настоящее время разрабатывается SDK, предназначенный для передачи функций из приложения в YaOS. Это позволит вызывать их по запросу пользователя. Например, при просмотре фильма пользователь хочет перейти на главную страницу или изменить качество изображения, а элементы для перехода в эти разделы не представлены на текущем экране.
Важно
API находится в активной разработке и может меняться. Ниже представлен обзор реализации технологии AppFunctions.
Доступ к SDK будет предоставлен после одобрения заявки на бета-тестирование.
Описание функций и примеры реализации
Перед реализацией AppFunctions рекомендуется заранее продумать сценарии использования функций. Возможности практически не ограничены и определяются потребностями пользователей и продуктового видения.
Ниже приведены примеры основных функций и их описания для онлайн-кинотеатров:
| Вызываемая функция | Описание |
|---|---|
| Поиск контента | Выполняет поиск аудио, радио, телепередач, каналов, видеоконтента внутри приложения <название приложения>. Если пользователь явно указывает в запросе тип контента (видео, музыка, канал, фильм, сериал, песня, альбом, мультфильм, подкаст, аудиокнига, радио и др.) или провайдера (Кинопоиск, ivi, YouTube и др.), то эта информация должна остаться в тексте запроса. Открывает экран поисковой выдачи, если конкретное видео не может быть воспроизведено или открыто с помощью другой функции. |
| Включить/выключить субтитры | Включает или выключает субтитры во время проигрывания контента в текущем приложении. |
| Переход в <название раздела> | Переключает на раздел <название раздела> в приложении <название приложения>. |
| Добавить фильм в «Избранное» | Добавляет пометку «Избранное» для проигрываемого фильма. |
Примечание
Приведенные выше описания функций и сценариев использования являются примерами. Окончательный перечень доступных команд определяется разработчиками приложения.
Формат описания должен быть таким, чтобы LLM-модель могла его интерпретировать и соотнести с голосовым запросом пользователя. Основной принцип — исчерпывающее описание с понятным содержанием. При проектировании рекомендуется ориентироваться на приведенные выше примеры, которые использовались при обучении модели и подходят для параметра Description.
Технические требования
- compileSdk: версия 36 или выше
- Kotlin: версия 2.0 или выше
- Android Gradle plugin: версия 8.9.1 или выше
- KSP (Kotlin Symbol Processing): должен быть подключен и корректно настроен
Как добавить SDK в проект
Файл SDK поставляется по запросу после одобрения заявки на бета-тестирование. Он представляет собой .aar файл и не содержит внешних зависимостей, поэтому их нужно добавить в проект вручную.
Чтобы реализовать AppFunctions в приложении, необходимо подключить файл SDK к проекту, библиотеку androidx.appfunctions:appfunctions, а также другие внешние зависимости, необходимые для работы SDK.
-
Поместите
.aarфайл библиотеки в папкуlibs, которая должна находиться на одном уровне с папкойsrcвнутри вашего проекта. -
Определите версии зависимостей:
# versions.toml [versions] appfunctions = "1.0.0-alpha03" coroutines = "1.7.3" annotation = "1.7.0" appsearch = "1.1.0-beta01" lifecycle = "2.5.1" [libraries] # androidx appfunctions = { module = "androidx.appfunctions:appfunctions", version.ref = "appfunctions" } appfunctions-compiler = { module = "androidx.appfunctions:appfunctions-compiler", version.ref = "appfunctions" } appfunctions-service = { module = "androidx.appfunctions:appfunctions-service", version.ref = "appfunctions" } annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } appsearch = { module = "androidx.appsearch:appsearch", version.ref = "appsearch" } lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" } # other coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } -
Добавьте зависимости в
build.gradle.kts:dependencies { implementation(fileTree("libs")) ksp(libs.appfunctions.compiler) implementation(libs.appfunctions) implementation(libs.appfunctions.service) implementation(libs.appfunctions.compiler) implementation(libs.appsearch) implementation(libs.coroutines.core) implementation(libs.coroutines.android) implementation(libs.lifecycle.runtime.ktx) implementation(libs.lifecycle.process) } -
В
build.gradle.ktsприложения включите аггрегацию AppFunctions через настройкиksp:android { ksp { arg("appfunctions:aggregateAppFunctions", "true") } }Подробнее про библиотеку
androidx.appfunctionsможно почитать здесь.
Как добавить AppFunctions в приложение
Добавьте интерфейсы для функций приложения и их реализации. Ниже приведены примеры функций и их интерфейсов для различных приложений.
Описание функции и ее параметров извлекается из KDoc к функции, которая аннотирована @AppFunction.
Важно
Используйте русский язык для описания функций.
Примеры функций для экрана видеоплеера
-
Определите интерфейсы
AddVideoToFavoritesAppFunction,ToggleSubtitlesForVideoAppFunctionиChangeVideoQualityAppFunctionдля функцийaddVideoToFavorites,toggleSubtitlesForVideoиchangeVideoQualityсоответственно:VideoPlayerFunctions.kt
import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.AppFunctionSchemaDefinition const val APP_FUNCTION_SCHEMA_CATEGORY_VIDEO_PLAYER: String = "video_player" @AppFunctionSchemaDefinition( name = "Добавляет текущее видео в избранное.", version = AddVideoToFavoritesAppFunction.SCHEMA_VERSION, category = APP_FUNCTION_SCHEMA_CATEGORY_VIDEO_PLAYER, ) interface AddVideoToFavoritesAppFunction { suspend fun addVideoToFavorites( appFunctionContext: AppFunctionContext, ): Boolean companion object { /** Current schema version. */ internal const val SCHEMA_VERSION: Int = 1 } } @AppFunctionSchemaDefinition( name = "Включает/отключает субтитры в текущем видео.", version = ToggleSubtitlesForVideoAppFunction.SCHEMA_VERSION, category = APP_FUNCTION_SCHEMA_CATEGORY_VIDEO_PLAYER, ) interface ToggleSubtitlesForVideoAppFunction { suspend fun toggleSubtitlesForVideo( appFunctionContext: AppFunctionContext, ): Boolean companion object { /** Current schema version. */ internal const val SCHEMA_VERSION: Int = 1 } } @AppFunctionSchemaDefinition( name = "Меняет качество текущего видео.", version = ChangeVideoQualityAppFunction.SCHEMA_VERSION, category = APP_FUNCTION_SCHEMA_CATEGORY_VIDEO_PLAYER, ) interface ChangeVideoQualityAppFunction { suspend fun changeVideoQuality( appFunctionContext: AppFunctionContext, newQuality: String, ): Boolean companion object { /** Current schema version. */ internal const val SCHEMA_VERSION: Int = 1 } }AddVideoToFavoritesAppFunction
Добавляет текущее видео в список избранного.
Параметры:
Параметр
Описание
appFunctionContextAppFunctionContext
Контекст выполнения функции. Предоставляет доступ к системным ресурсам и текущему состоянию приложения.
Возвращаемое значение:
Признак успешного выполнения операции (Boolean):
true— видео успешно добавлено в список избранного,false— произошла ошибка.ToggleSubtitlesForVideoAppFunction
Включает или выключает субтитры для текущего видео.
Параметры:
Параметр
Описание
appFunctionContextAppFunctionContext
Контекст выполнения функции. Предоставляет доступ к системным ресурсам и текущему состоянию приложения.
Возвращаемое значение:
Признак успешного выполнения операции (Boolean):
true— субтитры успешно переключены,false— произошла ошибка.ChangeVideoQualityAppFunction
Изменяет качество текущего видео на указанное. Если выбранное качество недоступно, используется ближайшее доступное значение.
Параметры:
Параметр
Описание
appFunctionContextAppFunctionContext
Контекст выполнения функции. Предоставляет доступ к системным ресурсам и текущему состоянию приложения.
newQualityString
Качество видео, которое нужно установить. Доступные значения: «240p», «360p», «480p», «720p», «1080p», «2k», «4k».
Возвращаемое значение:
Признак успешного выполнения операции (Boolean):
true— качество видео успешно изменено,false— произошла ошибка. -
Определите класс
VideoPlayerFunctionsImpl, который реализует функциональность видеоплеера:VideoPlayerFunctionsImpl.kt
import android.content.Context import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.service.AppFunction class VideoPlayerFunctionsImpl: AddVideoToFavoritesAppFunction, ToggleSubtitlesForVideoAppFunction, ChangeVideoQualityAppFunction { /** * Добавление пометки "Избранное" для проигрываемого контента. Позволяет добавить фильм, cериал, мультфильм или любой * другой проигрываемый сейчас контент в "Избранное". * * @return Успешно ли видео добавлено в список избранного. * * */ @AppFunction(isEnabled = false, isDescribedByKdoc = true) override suspend fun addVideoToFavorites( appFunctionContext: AppFunctionContext, ): Boolean { TODO() } /** * Включение или выключение субтитров во время проигрывания контента в текущем приложении. Функция может * включить или отключить субтитры у текущего видео, фильма, сериала или другого видеоконтента. * * @return Успешно ли получилось переключить субтитры. * */ @AppFunction(isEnabled = false, isDescribedByKdoc = true) override suspend fun toggleSubtitlesForVideo( appFunctionContext: AppFunctionContext, ): Boolean { TODO() } /** * Смена качества для текущего проигрываемого контента (видео, сериала, фильма или другого контента). Позволяет поменять * качество на минимальное, максимальное или конкретное. Любой запрос стоит приводить к ближайшему * значению из списка. * * @param newQuality Новое качество видео, которое нужно установить. * Может быть строго одним из значений: [240p, 360p, 480p, 720p, 1080p, 2k, 4k]. * @return Успешно ли получилось переключить качество видео. * */ @AppFunction(isEnabled = false, isDescribedByKdoc = true) override suspend fun changeVideoQuality( appFunctionContext: AppFunctionContext, newQuality: String, ): Boolean { TODO() } }Когда система вызовет какую-либо функцию, будет выполнен код из класса
VideoPlayerFunctionsImpl, который помечен аннотацией@AppFunction.
Примеры функций для навигации по разделам
-
Определите интерфейс
OpenMenuTabByNameAppFunctionдля функцииopenMenuTabByName:NavigationFunctions.kt
import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.AppFunctionSchemaDefinition const val APP_FUNCTION_SCHEMA_CATEGORY_NAVIGATION: String = "navigation" @AppFunctionSchemaDefinition( name = "Открывает указанный раздел в меню приложения.", version = OpenMenuTabByNameAppFunction.SCHEMA_VERSION, category = APP_FUNCTION_SCHEMA_CATEGORY_NAVIGATION, ) interface OpenMenuTabByNameAppFunction { suspend fun openMenuTabByName( appFunctionContext: AppFunctionContext, tabName: String, ): Boolean companion object { /** Current schema version. */ internal const val SCHEMA_VERSION: Int = 1 } }OpenMenuTabByNameAppFunction
Открывает указанный раздел в меню приложения.
Параметры:
Параметр
Описание
appFunctionContextAppFunctionContext
Контекст выполнения функции. Предоставляет доступ к системным ресурсам и текущему состоянию приложения.
tabNameString
Название раздела, который нужно открыть. Возможные значения: «Главная», «Подписки», «Еще», «Спорт», «Киберспорт».
Возвращаемое значение:
Признак успешного выполнения операции (Boolean):
true— раздел меню успешно открыт,false— произошла ошибка. -
Определите класс
NavigationFunctionsImpl, который реализует возможность навигации по разделам меню приложения:NavigationFunctionsImpl.kt
import android.content.Context import android.util.Log import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.service.AppFunction class NavigationFunctionsImpl: OpenMenuTabByNameAppFunction, OpenContentByItemSelectionNumber { /** * Открывает вкладку меню или раздел приложения по указанному названию. * * @param tabName Название раздела, который нужно открыть. Может быть строго одним из значений: [Главная, Подписки, Еще, * Спорт, Киберспорт]. * @return Успешно ли получилось открыть указанный раздел. * */ @AppFunction(isEnabled = true, isDescribedByKdoc = true) override suspend fun openMenuTabByName( appFunctionContext: AppFunctionContext, tabName: String, ): Boolean { TODO() } }Когда система вызовет какую-либо функцию, будет выполнен код из класса
NavigationFunctionsImpl, который помечен аннотацией@AppFunction.
Примеры функций для поиска контента в приложении
-
Определите интерфейс
SearchAppFunctionдля функцииcontentSearchByQuery:SearchFunctions.kt
import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.AppFunctionSchemaDefinition const val APP_FUNCTION_SCHEMA_CATEGORY_SEARCH: String = "search" @AppFunctionSchemaDefinition( name = "Выполняет поиск видео по указанному названию.", version = SearchAppFunction.SCHEMA_VERSION, category = APP_FUNCTION_SCHEMA_CATEGORY_SEARCH, ) interface SearchAppFunction { suspend fun contentSearchByQuery( appFunctionContext: AppFunctionContext, query: String, ) companion object { /** Current schema version. */ internal const val SCHEMA_VERSION: Int = 1 } }SearchAppFunction
Выполняет поиск видео по указанному названию.
Параметры:
Параметр
Описание
appFunctionContextAppFunctionContext
Контекст выполнения функции. Предоставляет доступ к системным ресурсам и текущему состоянию приложения.
queryString
Текст запроса для поиска.
-
Определите класс
SearchFunctionsImpl, который реализует возможность поиска видео по названию:SearchFunctionsImpl.kt
import android.content.Context import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.service.AppFunction class SearchFunctionsImpl: SearchAppFunction { /** * Поиск аудио, радио, телепередач, каналов, видеоконтента внутри приложения <название приложения>. * Если пользователь явно указывает в запросе тип контента (видео, музыка, канал, фильм, сериал, песня, альбом, * мультфильм, подкаст, аудиокнига, радио и др.), то его необходимо оставить в тексте запроса. * Открывает экран поисковой выдачи, если конкретное видео не будет включено или открыто в рамках другой функции. * * @param query Текст запроса, по которому нужно произвести поиск. * */ @AppFunction(isEnabled = true, isDescribedByKdoc = true) override suspend fun contentSearchByQuery( appFunctionContext: AppFunctionContext, query: String, ) { TODO() } }Когда система вызовет какую-либо функцию, будет выполнен код из класса
SearchFunctionsImpl, который помечен аннотацией@AppFunction.
Примеры функций для создания и хранения заметок
Важно
Передача объектов в функции поддерживается только начиная с Android 12. Поскольку устройства на базе YaOS в настоящее время используют Android 11, данный пример приложения пока не работает.
-
Определите интерфейс
ShowNoteAppFunctionдля функцииshowNote, а также интерфейсы для ее параметров:ShowNoteFunction.kt
import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.AppFunctionSchemaDefinition @AppFunctionSchemaDefinition( name = "Отображает заметку с заданными параметрами.", version = ShowNoteAppFunction.SCHEMA_VERSION, category = APP_FUNCTION_SCHEMA_CATEGORY_NOTES ) interface ShowNoteAppFunction< Parameters : ShowNoteAppFunction.Parameters, Response : ShowNoteAppFunction.Response > { suspend fun showNote( appFunctionContext: AppFunctionContext, showNoteParams: Parameters ): Response interface Parameters { val noteId: String } interface Response { val isSuccess: Boolean } companion object { internal const val SCHEMA_VERSION: Int = 1 } }ShowNoteAppFunction
Отображает заметку с заданными параметрами.
Параметр
Описание
Определяет структуру входных данных для функции
showNote. Должен реализовывать вложенный интерфейсShowNoteAppFunction.Parameters.Определяет структуру выходных данных функции
showNote. Должен реализовывать вложенный интерфейсShowNoteAppFunction.Response.Parameters
Определяет структуру входных данных для функции
showNote. Должен реализовывать вложенный интерфейсShowNoteAppFunction.Parameters.Параметр
Описание
noteIdString
Уникальный идентификатор заметки.
Response
Определяет структуру выходных данных функции
showNote. Должен реализовывать вложенный интерфейсShowNoteAppFunction.Response.Параметр
Описание
isSuccessBoolean
Флаг успешного выполнения операции:
true— заметка успешно отображена,false— произошла ошибка при выполнении. -
Определите классы
ShowNoteParametersиShowNoteResponseдля конкретной реализации параметров интерфейса:ShowNoteParams.kt
@AppFunctionSerializable data class ShowNoteParameters( override val noteId: String ): ShowNoteAppFunction.Parameters @AppFunctionSerializable data class ShowNoteResponse( override val isSuccess: Boolean ): ShowNoteAppFunction.Response -
Определите класс
NoteFunctionsImpl, который реализует функциональность отображения заметок в приложении:NoteFunctionsImpl.kt
import android.content.Context import androidx.appfunctions.AppFunctionContext import androidx.appfunctions.service.AppFunction class NoteFunctionsImpl: ShowNoteAppFunction<ShowNoteParameters, ShowNoteResponse> { /** * Отображает заметку с заданными параметрами. * * @param showNoteParams Входные данные: уникальный идентификатор заметки. * @return Успешно ли отображена заметка. * */ @AppFunction(isEnabled = true) override suspend fun showNote( appFunctionContext: AppFunctionContext, showNoteParams: ShowNoteParameters, ): ShowNoteResponse { TODO() } }Когда система вызовет какую-либо функцию, будет выполнен код из класса
NoteFunctionsImpl, который помечен аннотацией@AppFunction.
Как управлять функциями приложения
Чтобы управлять функциями приложения, используйте предоставляемый SDK класс AppFunctionSdk.
Важно
Экземпляр класса com.yandex.tv.features.appfunctions.sdk.AppFunctionSdk необходимо создать, даже если не планируется управление AppFunctions (т. е. присутствуют только функции, которые isEnabled=true по умолчанию).
С помощью класса AppFunctionSdk можно вызывать следующие методы:
- Управление конкретной функцией
suspend fun enableAppFunction(functionIdentifier: String)
suspend fun disableAppFunction(functionIdentifier: String)
|
Параметр |
Описание |
|
|
String Уникальный идентификатор функции (например, |
- Групповое управление по категориям
suspend fun enableAppFunctionsBySchemaCategory(schemaCategory: String)
suspend fun disableAppFunctionsBySchemaCategory(schemaCategory: String)
|
Параметр |
Описание |
|
|
String Категория для группы связанных функций (например, |
- Управление функциями или группой функций с привязкой к конкретному
LifecycleOwner
fun AppFunctionSdk.bindFunctionToLifecycle(functionIdentifier: String, lifecycleOwner: LifecycleOwner)
fun AppFunctionSdk.bindFunctionsToLifecycle(schemaCategory: String, lifecycleOwner: LifecycleOwner)
|
Параметр |
Описание |
|
|
String Уникальный идентификатор функции (например, |
|
|
String Категория для группы связанных функций (например, |
|
|
LifecycleOwner Компонент имеющий жизненный цикл (Activity или Fragment). |
Функции автоматически включаются и выключаются в состоянии Lifecycle.State.STARTED. Например, для Activity это происходит после вызова onStart() и перед вызовом onPause().
Примечание
Все идентификаторы функций functionIdentifier будут доступны в сгенерированном статичном объекте с названием <Название класса с аннотациями @AppFunction>Ids. Например, для класса с функциями видеоплеера будет сгенерирован следующий объект:
@Generated("androidx.appfunctions.compiler.AppFunctionCompiler")
public object VideoPlayerFunctionsImplIds {
public const val ADD_VIDEO_TO_FAVORITES_ID: String =
"com.yandex.tv.features.appfunctions.sdk.sample.appfunctions.player.VideoPlayerFunctionsImpl#addVideoToFavorites"
public const val TOGGLE_SUBTITLES_FOR_VIDEO_ID: String =
"com.yandex.tv.features.appfunctions.sdk.sample.appfunctions.player.VideoPlayerFunctionsImpl#toggleSubtitlesForVideo"
public const val CHANGE_VIDEO_QUALITY_ID: String =
"com.yandex.tv.features.appfunctions.sdk.sample.appfunctions.player.VideoPlayerFunctionsImpl#changeVideoQuality"
}