Cómo crear tu primera tarjeta en Wear OS

1. Introducción

reloj animado; usuario que desliza la cara de reloj hasta la primera tarjeta que es una previsión, luego, a una tarjeta del temporizador y de vuelta a la primera

Las tarjetas de Wear OS brindan un acceso fácil a la información y a las acciones que los usuarios necesitan para realizar tareas. Con solo deslizar el dedo sobre la cara de reloj, un usuario puede consultar el pronóstico actualizado o iniciar un temporizador.

Una tarjeta se ejecuta como parte de la IU del sistema en lugar de ejecutarse en el propio contenedor de la aplicación. Usamos un Servicio para describir el diseño y el contenido de la tarjeta. Luego, la IU del sistema renderizará la tarjeta cuando sea necesario.

Actividades

35a459b77a2c9d52.png

Compilarás una tarjeta para una app de mensajería que muestra conversaciones recientes. Desde esta superficie, el usuario puede saltar directamente a 1 de 3 tareas comunes:

  • Abrir una conversación
  • Buscar una conversación
  • Redactar un mensaje nuevo

Qué aprenderás

En este codelab, aprenderás a escribir tu propia tarjeta de Wear OS, incluido lo siguiente:

  • Cómo crear un TileService
  • Cómo probar una tarjeta en un dispositivo
  • Cómo obtener una vista previa de la IU de una tarjeta en Android Studio
  • Cómo desarrollar la IU para una tarjeta
  • Cómo agregar imágenes
  • Cómo controlar interacciones

Requisitos previos

  • Conocimientos básicos sobre Kotlin

2. Cómo prepararte

En este paso, configurarás tu entorno y descargarás un proyecto inicial.

Lo que necesitarás

  • Android Studio Dolphin (2021.3.1) o una versión más reciente
  • Emulador o dispositivo Wear OS

Si desconoces el uso de Wear OS, lee esta guía rápida antes de comenzar. Se incluyen instrucciones para configurar un emulador de Wear OS y se describe cómo navegar por el sistema.

Cómo descargar el código

Si ya instalaste git, solo ejecuta el siguiente comando para clonar el código de este repositorio. Para comprobarlo, escribe "git –version" en la terminal o línea de comandos, y verifica que se ejecute correctamente.

git clone https://github.com/android/codelab-wear-tiles.git
cd wear-tiles

Si no tienes Git, puedes hacer clic en el siguiente botón para descargar todo el código de este codelab:

Abre el proyecto en Android Studio

En la ventana "Welcome to Android Studio", selecciona c01826594f360d94.png Open an Existing Project o File > Open y selecciona la carpeta [Ubicación de descarga].

3. Cómo crear una tarjeta básica

El punto de entrada para una tarjeta es el servicio de mosaicos para mapas. En este paso, registrarás un servicio de este tipo y definirás un diseño para la tarjeta.

HelloWorldTileService

Una clase que implementa TileService debe especificar dos funciones:

  • onResourcesRequest(requestParams: ResourcesRequest): ListenableFuture<Resources>
  • onTileRequest(requestParams: TileRequest): ListenableFuture<Tile>

El primero asigna los IDs de string a un recurso de imagen. Aquí es donde se proporcionan los recursos de imagen que usaremos en nuestra tarjeta.

El segundo muestra la descripción de una tarjeta, incluido su diseño. Aquí es donde definimos el diseño de la tarjeta y cómo se vinculan los datos a ella.

Abre HelloWorldTileService.kt desde el módulo start. Todos los cambios que hagas estarán en este módulo. También hay un módulo finished si quieres ver el resultado de este codelab.

HelloWorldTileService extiende CoroutinesTileService, un wrapper compatible con corrutinas de Kotlin de la biblioteca de tarjetas de Horologist. Horologist es un grupo de bibliotecas de Google que tiene como objetivo aportar a los desarrolladores de Wear OS un complemento con funciones que comúnmente requieren, pero que aún no están disponibles en Jetpack.

CoroutinesTileService proporciona dos funciones de suspensión, que son versiones de corrutinas de las funciones de TileService:

  • suspend resourcesRequest(requestParams: ResourcesRequest): Resources
  • suspend tileRequest(requestParams: TileRequest): Tile

Si deseas obtener más información sobre las corrutinas, consulta la documentación sobre corrutinas de Kotlin en Android.

HelloWorldTileService aún no está completo. Debemos registrar el servicio en nuestro manifiesto y también proporcionar una implementación para tileLayout.

Cómo registrar el servicio de mosaicos para mapas

Es necesario registrar el servicio de mosaicos para mapas en el manifiesto de modo que el sistema tenga esta información. Una vez registrado, aparecerá en la lista de tarjetas disponibles para que el usuario lo agregue.

Agrega el <service> dentro del elemento <application>:

start/src/main/AndroidManifest.xml

<service
    android:name="com.example.wear.tiles.hello.HelloWorldTileService"
    android:icon="@drawable/ic_waving_hand_24"
    android:label="@string/hello_tile_label"
    android:description="@string/hello_tile_description"
    android:exported="true"
    android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">

    <intent-filter>
        <action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
    </intent-filter>

    <!-- The tile preview shown when configuring tiles on your phone -->
    <meta-data
        android:name="androidx.wear.tiles.PREVIEW"
        android:resource="@drawable/tile_hello" />
</service>

El ícono y la etiqueta se usan (como marcador de posición) cuando la tarjeta se carga por primera vez o si hay un error al momento de cargarla. Los metadatos al final definen una imagen de vista previa que se muestra en el carrusel cuando el usuario agrega una tarjeta.

Cómo definir un diseño para la tarjeta

HelloWorldTileService tiene una función llamada tileLayout con un TODO() como cuerpo. Ahora vamos a reemplazar eso con una implementación en la que definimos el diseño de nuestra tarjeta y vincularemos datos:

start/src/main/java/com/example/wear/tiles/hello/HelloWorldTileService.kt

private fun tileLayout(): LayoutElement {
    val text = getString(R.string.hello_tile_body)
    return LayoutElementBuilders.Box.Builder()
        .setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
        .setWidth(DimensionBuilders.expand())
        .setHeight(DimensionBuilders.expand())
        .addContent(
            LayoutElementBuilders.Text.Builder()
                .setText(text)
                .build()
        )
        .build()
}

Creamos un elemento Text y lo configuramos dentro de un Box para poder realizar una alineación básica.

Ya creaste tu primera tarjeta de Wear OS. Instalemos esta tarjeta y veamos cómo se ve.

4. Cómo probar tu tarjeta en un dispositivo

Con el módulo de inicio seleccionado en el menú desplegable de configuración de ejecución, puedes instalar la app (el módulo start) en tu dispositivo o emulador y, luego, instalar manualmente la tarjeta, como lo haría un usuario.

En su lugar, usemos Direct Surface Launch, una función que se introdujo en Android Studio Dolphin, para crear una nueva configuración de ejecución y lanzar la tarjeta directamente desde Android Studio. Selecciona "Edit Configurations…" en el menú desplegable del panel superior.

Menú desplegable de configuración de ejecución desde el panel superior de Android Studio. La opción Edit configurations aparece destacada.

Haz clic en el botón "Add new configuration" y elige "Wear OS Tile". Agrega un nombre descriptivo y, luego, selecciona el módulo Tiles_Code_Lab.start y la tarjeta HelloWorldTileService.

Presiona "OK" para terminar.

Menú de Edit Configuration desde el que se puede configurar una tarjeta de Wear OS llamada HelloTile.

Direct Surface Launch nos permite probar rápidamente tarjetas en un emulador de Wear OS o en un dispositivo físico. Ejecuta "HelloTile" para probarlo. Debería verse como en la siguiente captura de pantalla.

Reloj redondo que muestra el mensaje "Time to create a tile!" escrito en blanco sobre un fondo negro

5. Cómo crear una tarjeta de mensajes

Reloj redondo que muestra 5 botones redondos dispuestos en una pirámide de 2 x 3. El primer y el tercer botón muestran las iniciales en letras de color violeta, el segundo y el cuarto muestran las fotos de perfil, y el último botón es un ícono de búsqueda. Debajo de los botones, hay un pequeño chip violeta que dice "New" en letras negras.

La tarjeta de mensajería que vamos a compilar se parece más a una tarjeta del mundo real. A diferencia del ejemplo de HelloWorld, esta carga datos desde un repositorio local, recupera imágenes para mostrar desde la red y administra interacciones a fin de abrir la app, y lo hace directamente desde la tarjeta.

MessagingTileService

MessagingTileService extiende la clase CoroutinesTileService que vimos antes.

La principal diferencia entre este ejemplo y el anterior es que ahora se observan datos del repositorio y también se obtienen datos de imágenes de la red.

Para cualquier trabajo de larga duración (p. ej., llamadas de red), sería más apropiado usar algo como WorkManager, ya que las funciones de servicio de mosaicos para mapas tienen tiempos de espera relativamente cortos. En este codelab, no presentaremos WorkManager. Si deseas probarlo por tu cuenta, consulta este codelab.

MessagingTileRenderer

MessagingTileRenderer extiende la clase TileRenderer (otra abstracción de las tarjetas de Horologist). Es completamente síncrono: el estado se pasa a las funciones del renderizador, lo que facilita su uso en pruebas y vistas previas de Android Studio.

En el siguiente paso, veremos cómo agregar vistas previas de Android Studio para tarjetas.

6. Cómo agregar funciones de vista previa

Podemos obtener una vista previa de la IU de tarjetas en Android Studio usando TileLayoutPreview (y similares) de las tarjetas de Horology. Esto reduce el ciclo de reacción cuando se desarrolla la IU, lo que hace que la iteración sea mucho más rápida.

Usaremos herramientas de Jetpack Compose para generar esta vista previa. Por eso, verás la anotación @Composable en la siguiente función de vista previa. Puedes obtener más información sobre las vistas previas que admiten composición, pero no es necesario completar este codelab.

Agrega una vista previa componible para el elemento MessagingTileRenderer al final del archivo.

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileRenderer.kt

@WearDevicePreview
@Composable
fun MessagingTileRendererPreview() {
    TileLayoutPreview(
        state = MessagingTileState(MessagingRepo.knownContacts),
        resourceState = emptyMap(),
        renderer = MessagingTileRenderer(LocalContext.current)
    )
}

Ten en cuenta que la función de componibilidad usa TileLayoutPreview. No podemos obtener una vista previa de los diseños de las tarjetas de forma directa.

Usa el modo de editor "Pantalla dividida" para obtener una vista previa de la tarjeta:

Vista de pantalla dividida de Android Studio con el código de la vista previa a la izquierda y una imagen de la tarjeta a la derecha.

Pasamos datos artificiales a MessagingTileState y no tenemos ningún estado de recursos (todavía), por lo que podemos pasar un mapa vacío.

En el siguiente paso, usaremos Tiles Material a fin de actualizar el diseño.

7. Cómo agregar Tiles Material

Tiles Material proporciona componentes de Material y diseños precompilados, lo que te permite crear tarjetas que contengan lo último en Material Design para Wear OS.

Agrega la dependencia de Tiles Material a tu archivo build.gradle:

start/build.gradle

implementation "androidx.wear.tiles:tiles-material:$tilesVersion"

Según la complejidad de tu diseño, puede ser útil ubicar el código de diseño con el renderizador mediante el uso de funciones de nivel superior en el mismo archivo para encapsular una unidad lógica de la IU.

Agrega el código del botón en la parte inferior del archivo del renderizador y también en la vista previa:

start/src/main/java/MessagingTileRenderer.kt

private fun searchLayout(
    context: Context,
    clickable: ModifiersBuilders.Clickable,
) = Button.Builder(context, clickable)
    .setContentDescription(context.getString(R.string.tile_messaging_search))
    .setIconContent(MessagingTileRenderer.ID_IC_SEARCH)
    .setButtonColors(ButtonColors.secondaryButtonColors(MessagingTileTheme.colors))
    .build()

@IconSizePreview
@Composable
private fun SearchButtonPreview() {
    LayoutElementPreview(
        searchLayout(
            context = LocalContext.current,
            clickable = emptyClickable
        )
    ) {
        addIdToImageMapping(
            MessagingTileRenderer.ID_IC_SEARCH,
            drawableResToImageResource(R.drawable.ic_search_24)
        )
    }
}

LayoutElementPreview es similar a TileLayoutPreview, pero se usa para componentes individuales, como un botón, un chip o una etiqueta. La lambda final nos permite especificar la asignación del ID de recursos (a recursos de imagen), por lo que asignaremos ID_IC_SEARCH al recurso de imagen de búsqueda.

Con el modo de edición de "Pantalla dividida", podemos obtener una vista previa del botón de búsqueda:

Un conjunto de vistas previas apiladas verticalmente, una tarjeta en la parte superior y un botón de ícono de búsqueda debajo.

También podemos hacer algo similar para compilar el diseño de los contactos:

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileRenderer.kt

private fun contactLayout(
    context: Context,
    contact: Contact,
    clickable: ModifiersBuilders.Clickable,
) = Button.Builder(context, clickable)
    .setContentDescription(contact.name)
    .apply {
        if (contact.avatarUrl != null) {
            setImageContent(contact.imageResourceId())
        } else {
            setTextContent(contact.initials)
            setButtonColors(ButtonColors.secondaryButtonColors(MessagingTileTheme.colors))
        }
    }
    .build()

Tiles Material no solo incluye componentes. En lugar de usar una serie de columnas y filas anidadas, podemos usar diseños de Tiles Material a fin de lograr el aspecto deseado rápidamente.

Aquí podemos usar PrimaryLayout y MultiButtonLayout para ordenar 4 contactos y el botón de búsqueda. Actualiza la función messagingTileLayout() en MessagingTileRenderer con estos diseños:

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileRenderer.kt

private fun messagingTileLayout(
    context: Context,
    deviceParameters: DeviceParametersBuilders.DeviceParameters,
    state: MessagingTileState
) = PrimaryLayout.Builder(deviceParameters)
    .setContent(
        MultiButtonLayout.Builder()
            .apply {
                // In a PrimaryLayout with a compact chip at the bottom, we can fit 5 buttons.
                // We're only taking the first 4 contacts so that we can fit a Search button too.
                state.contacts.take(4).forEach { contact ->
                    addButtonContent(
                        contactLayout(
                            context = context,
                            contact = contact,
                            clickable = emptyClickable
                        )
                    )
                }
            }
            .addButtonContent(searchLayout(context, emptyClickable))
            .build()
    )
    .build()

Vista previa de la tarjeta de 5 botones en una pirámide de 2 x 3. El segundo y tercer botón son círculos rellenos de color azul, que indican que faltan imágenes.

MultiButtonLayout admite hasta 7 botones y los distribuirá con el espaciado adecuado para ti. Agreguemos el chip "New" a PrimaryLayout también, en el compilador PrimaryLayout de la función messagingTileLayout():

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileRenderer.kt

.setPrimaryChipContent(
    CompactChip.Builder(
        /* context = */ context,
        /* text = */ context.getString(R.string.tile_messaging_create_new),
        /* clickable = */ emptyClickable,
        /* deviceParameters = */ deviceParameters
    )
        .setChipColors(ChipColors.primaryChipColors(MessagingTileTheme.colors))
        .build()
)

vista previa de la tarjeta con 5 botones y un chip compacto debajo que dice "new"

En el siguiente paso, corregiremos las imágenes faltantes.

8. Cómo agregar imágenes

Mostrar una imagen local en una tarjeta es una tarea sencilla: proporciona la asignación de un ID de string (que uses en tu diseño) a la imagen usando la función conveniente de las tarjetas de Horologist a fin de cargar el elemento de diseño y transformarlo en un recurso de imagen. Hay un ejemplo disponible en SearchButtonPreview:

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileRenderer.kt

addIdToImageMapping(
    ID_IC_SEARCH,
    drawableResToImageResource(R.drawable.ic_search_24)
)

Para la tarjeta de mensajería, también necesitamos cargar imágenes de la red (no solo de los recursos locales) y, para ello, usamos Coil, un cargador de imágenes basado en corrutinas de Kotlin.

El código ya está escrito para esto:

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileService.kt

override suspend fun resourcesRequest(requestParams: ResourcesRequest): Resources {
    val avatars = imageLoader.fetchAvatarsFromNetwork(
        context = this@MessagingTileService,
        requestParams = requestParams,
        tileState = latestTileState()
    )
    return renderer.produceRequestedResources(avatars, requestParams)
}

Como el renderizador de tarjetas es completamente síncrono, el servicio de mosaicos para mapas es el que recupera mapas de bits de la red. Al igual que antes, según el tamaño de la imagen, podría resultar más apropiado usar WorkManager con el fin de recuperar las imágenes con anticipación, pero para este codelab las recuperamos de forma directa.

Pasamos el mapa de avatars (de Contact a Bitmap) al renderizador como "estado" para los recursos. Ahora, el renderizador puede transformar estos mapas de bits en recursos de imagen para tarjetas.

Este código también está escrito:

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileRenderer.kt

override fun ResourceBuilders.Resources.Builder.produceRequestedResources(
    resourceState: Map<Contact, Bitmap>,
    deviceParameters: DeviceParametersBuilders.DeviceParameters,
    resourceIds: MutableList<String>
) {
    addIdToImageMapping(
        ID_IC_SEARCH,
        drawableResToImageResource(R.drawable.ic_search_24)
    )

    resourceState.forEach { (contact, bitmap) ->
        addIdToImageMapping(
            /* id = */ contact.imageResourceId(),
            /* image = */ bitmap.toImageResource()
        )
    }
}

Entonces, si el servicio recupera los mapas de bits y el renderizador los transforma en recursos de imagen, ¿por qué la tarjeta no muestra imágenes?

¡Sí las muestra! Si ejecutas la tarjeta en un dispositivo (con acceso a Internet), deberías ver que las imágenes efectivamente se cargan. El problema se encuentra solo en nuestra vista previa, porque todavía estamos pasando un emptyMap() para el resourceState.

Para la tarjeta real, recuperaremos mapas de bits de la red y los asignaremos a diferentes contactos, pero no es necesario acceder a la red para las vistas previas ni las pruebas.

Actualiza MessagingTileRendererPreview() para proporcionar mapas de bits para los dos contactos que necesitan uno:

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileRenderer.kt

@WearDevicePreview
@Composable
fun MessagingTileRendererPreview() {
    val state = MessagingTileState(MessagingRepo.knownContacts)
    val context = LocalContext.current
    TileLayoutPreview(
        state = state,
        resourceState = mapOf(
            state.contacts[1] to (context.getDrawable(R.drawable.ali) as BitmapDrawable).bitmap,
            state.contacts[2] to (context.getDrawable(R.drawable.taylor) as BitmapDrawable).bitmap,
        ),
        renderer = MessagingTileRenderer(context)
    )
}

Ahora, si actualizamos la vista previa, las imágenes deberían mostrar lo siguiente:

vista previa de la tarjeta con 5 botones, esta vez con fotos en los dos botones que eran círculos azules

En el siguiente paso, controlaremos los clics en cada uno de los elementos.

9. Cómo controlar interacciones

Una de las cosas más útiles que podemos hacer con una tarjeta es proporcionar accesos directos a los recorridos críticos del usuario. Esto es diferente del selector de aplicaciones, que solo abre la app. Aquí tenemos espacio para brindar accesos directos contextuales a una pantalla específica de tu app.

Hasta ahora, usamos emptyClickable para el chip y cada uno de los botones. Esto está bien para las vistas previas, que no son interactivas, pero veamos cómo agregar acciones para los elementos.

Dos compiladores de la clase "ActionBuilders" definen las acciones en las que se puede hacer clic: LoadAction y LaunchAction.

LoadAction

Se puede usar LoadAction si deseas realizar una lógica en el servicio de mosaicos para mapas cuando el usuario hace clic en un elemento, p. ej., incrementando un contador.

.setClickable(
    Clickable.Builder()
        .setId(ID_CLICK_INCREMENT_COUNTER)
        .setOnClick(ActionBuilders.LoadAction.Builder().build())
        .build()
    )
)

Cuando hagas clic en esta opción, se llamará a onTileRequest en tu servicio (tileRequest en CoroutinesTileService), por lo que es una buena oportunidad para actualizar la IU de la tarjeta:

override suspend fun tileRequest(requestParams: TileRequest): Tile {
    if (requestParams.state.lastClickableId == ID_CLICK_INCREMENT_COUNTER) {
        // increment counter
    }
    // return an updated tile
}

LaunchAction

Se puede usar LaunchAction para iniciar una actividad. En MessagingTileRenderer, actualicemos el botón de búsqueda en el que se puede hacer clic.

El botón de búsqueda se define con la función searchLayout() en MessagingTileRenderer. Ya toma un Clickable como parámetro, pero hasta ahora hemos pasado emptyClickable, una implementación no-op que no hace nada cuando se hace clic en el botón.

Actualicemos messagingTileLayout() para que pase una acción de clic real. Agrega el parámetro searchButtonClickable y pásalo a searchLayout():

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileRenderer.kt

private fun messagingTileLayout(
    context: Context,
    deviceParameters: DeviceParametersBuilders.DeviceParameters,
    state: MessagingTileState,
    searchButtonClickable: ModifiersBuilders.Clickable
...
    .addButtonContent(searchLayout(context, searchButtonClickable))

También necesitamos actualizar renderTile, que es donde llamamos a messagingTileLayout, ya que agregamos un parámetro nuevo (searchButtonClickable). Usaremos la función launchActivityClickable() para crear un nuevo elemento en el que se pueda hacer clic y pasar openSearch() ActionBuilder como acción:

start/src/main/java/com/example/wear/tiles/messaging/tile/MessagingTileRenderer.kt

override fun renderTile(
    state: MessagingTileState,
    deviceParameters: DeviceParametersBuilders.DeviceParameters
): LayoutElementBuilders.LayoutElement {
    return messagingTileLayout(
        context = context,
        deviceParameters = deviceParameters,
        state = state,
        searchButtonClickable = launchActivityClickable("search_button", openSearch())
    )
}

Abre launchActivityClickable para ver cómo funcionan estas funciones (ya definidas):

start/src/main/java/com/example/wear/tiles/messaging/tile/ClickableActions.kt

internal fun launchActivityClickable(
    clickableId: String,
    androidActivity: ActionBuilders.AndroidActivity
) = ModifiersBuilders.Clickable.Builder()
    .setId(clickableId)
    .setOnClick(
        ActionBuilders.LaunchAction.Builder()
            .setAndroidActivity(androidActivity)
            .build()
    )
    .build()

Es muy similar a LoadAction. La diferencia principal es que llamamos a setAndroidActivity. En el mismo archivo, tenemos varios ejemplos de ActionBuilder.AndroidActivity.

En el caso de openSearch, que usamos para este elemento en el que se puede hacer clic, llamamos a setMessagingActivity y pasamos una cadena adicional para identificar con qué botón se hizo clic.

start/src/main/java/com/example/wear/tiles/messaging/tile/ClickableActions.kt

internal fun openSearch() = ActionBuilders.AndroidActivity.Builder()
    .setMessagingActivity()
    .addKeyToExtraMapping(
        MainActivity.EXTRA_JOURNEY,
        ActionBuilders.stringExtra(MainActivity.EXTRA_JOURNEY_SEARCH)
    )
    .build()

...

internal fun ActionBuilders.AndroidActivity.Builder.setMessagingActivity(): ActionBuilders.AndroidActivity.Builder {
    return setPackageName("com.example.wear.tiles")
        .setClassName("com.example.wear.tiles.messaging.MainActivity")
}

Ejecuta la tarjeta y haz clic en el botón de búsqueda. Debería abrirse la MainActivity y mostrar texto para confirmar que se hizo clic en el botón de búsqueda.

Agregar acciones para los demás botones es similar. ClickableActions contiene las funciones que necesitas. Si necesitas una pista, consulta MessagingTileRenderer en el módulo finished.

10. Felicitaciones

¡Felicitaciones! Ya aprendiste a compilar una tarjeta para Wear OS.

¿Qué sigue?

Si deseas obtener más información, consulta las implementaciones de Golden Tiles en GitHub y la guía de tarjetas de Wear OS.