Actividad en curso

En Wear OS, cuando se vincula una actividad en curso con una notificación continua, se agrega esa notificación a las plataformas adicionales dentro de la interfaz de usuario de Wear OS, lo que permite a los usuarios mantener una mayor participación con actividades prolongadas.

Por lo general, las notificaciones continuas se usan para indicar que una notificación tiene una tarea en segundo plano con la que el usuario interactúa de manera activa o está pendiente de alguna manera y, por lo tanto, ocupa el dispositivo.

Por ejemplo, un usuario de Wear OS podría usar una app de entrenamiento para registrar un ejercicio, como correr, a partir de una actividad y, luego, salir de esa app para iniciar otra tarea. Cuando el usuario salga de la app de entrenamiento, esta pasará a una notificación continua vinculada a algunas tareas en segundo plano para mantener al usuario informado sobre su ejecución. La notificación le proporciona al usuario actualizaciones y una manera fácil de volver a la app.

Sin embargo, para ver la notificación, el usuario tiene que deslizar el dedo en la bandeja de notificaciones que se encuentra debajo de la cara de reloj y buscar la correcta. No es tan conveniente como otras plataformas.

Con la API de Ongoing Activity, la notificación continua de una app puede mostrar información a varias plataformas nuevas y prácticas en Wear OS para mantener la participación del usuario.

Por ejemplo, en el caso de la app de entrenamiento, la información puede aparecer en la cara de reloj del usuario como un ícono de una persona corriendo que se puede presionar:

running-icon

Figura 1: Indicador de actividad.

La sección Recientes del selector global de aplicaciones también enumera las actividades en curso:

selector

Figura 2: Selector global.

Los siguientes son escenarios aptos para usar una notificación continua vinculada a una actividad en curso:

temporizador

Figura 3: Temporizador: Realiza una cuenta regresiva de forma activa y finaliza cuando se pausa o detiene.

mapa

Figura 4: Navegación paso a paso: Anuncia las instrucciones para llegar a un destino. Finaliza cuando el usuario llega a la ubicación deseada o detiene la navegación.

música

Figura 5: Contenido multimedia: Reproduce música durante una sesión. Finaliza inmediatamente después de que el usuario pausa la sesión.

Wear crea actividades en curso automáticamente para las apps de contenido multimedia.

Consulta el codelab de Ongoing Activity para ver un ejemplo detallado de cómo crear actividades en curso para otros tipos de apps.

Configuración

Para comenzar a usar la API de Ongoing Activity en tu app, agrega las siguientes dependencias al archivo build.gradle de tu app:

dependencies {
  implementation "androidx.wear:wear-ongoing:1.0.0"
  // Includes LocusIdCompat and new Notification categories for Ongoing Activity.
  implementation "androidx.core:core:1.6.0"
}

Inicia una actividad en curso

Para comenzar, crea una notificación continua y, luego, una actividad en curso.

Crea una notificación continua

Una actividad en curso está estrechamente relacionada con una notificación continua. Ambas funcionan en conjunto para informar a los usuarios sobre una tarea con la que están interactuando de manera activa, o bien que está pendiente de alguna manera y, por lo tanto, ocupa el dispositivo.

Debes vincular una actividad en curso con una notificación continua. La vinculación de tu actividad en curso a una notificación genera muchos beneficios, entre los que se incluyen los siguientes:

  • Las notificaciones son el resguardo de los dispositivos que no admiten actividades en curso. La notificación es la única plataforma que muestra tu app mientras se ejecuta en segundo plano.
  • En Android 11 y versiones posteriores, Wear OS oculta la notificación en la bandeja de notificaciones cuando la app se puede ver como actividad en curso en plataformas adicionales.
  • La implementación actual usa el mismo elemento Notification como mecanismo de comunicación.

Crea una notificación continua con Notification.Builder.setOngoing.

Inicia una actividad en curso

Una vez que tengas una notificación continua, crea una actividad en curso como se muestra en el siguiente ejemplo. Revisa los comentarios incluidos para comprender el comportamiento de cada propiedad.

Kotlin

var notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
      …
      .setSmallIcon(..)
      .setOngoing(true)

val ongoingActivityStatus = Status.Builder()
    // Sets the text used across various surfaces.
    .addTemplate(mainText)
    .build()

val ongoingActivity =
    OngoingActivity.Builder(
        applicationContext, NOTIFICATION_ID, notificationBuilder
    )
        // Sets the animated icon that will appear on the watch face in
        // active mode.
        // If it isn't set, the watch face will use the static icon in
        // active mode.
        .setAnimatedIcon(R.drawable.ic_walk)
        // Sets the icon that will appear on the watch face in ambient mode.
        // Falls back to Notification's smallIcon if not set.
        // If neither is set, an Exception is thrown.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap/touch event so users can re-enter your app from the
        // other surfaces.
        // Falls back to Notification's contentIntent if not set.
        // If neither is set, an Exception is thrown.
        .setTouchIntent(activityPendingIntent)
        // Here, sets the text used for the Ongoing Activity (more
        // options are available for timers and stopwatches).
        .setStatus(ongoingActivityStatus)
        .build()

ongoingActivity.apply(applicationContext)

notificationManager.notify(NOTIFICATION_ID, builder.build())

Java

NotificationCompat.Builder notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
      …
      .setSmallIcon(..)
      .setOngoing(true);

OngoingActivityStatus ongoingActivityStatus = OngoingActivityStatus.Builder()
    // Sets the text used across various surfaces.
    .addTemplate(mainText)
    .build();

OngoingActivity ongoingActivity =
    OngoingActivity.Builder(
        applicationContext, NOTIFICATION_ID, notificationBuilder
    )
        // Sets the animated icon that will appear on the watch face in
        // active mode.
        // If it isn't set, the watch face will use the static icon in
        // active mode.
        .setAnimatedIcon(R.drawable.ic_walk)
        // Sets the icon that will appear on the watch face in ambient mode.
        // Falls back to Notification's smallIcon if not set.
        // If neither is set, an Exception is thrown.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap/touch event so users can re-enter your app from the
        // other surfaces.
        // Falls back to Notification's contentIntent if not set.
        // If neither is set, an Exception is thrown.
        .setTouchIntent(activityPendingIntent)
        // Here, sets the text used for the Ongoing Activity (more
        // options are available for timers and stopwatches).
        .setStatus(ongoingActivityStatus)
        .build();

ongoingActivity.apply(applicationContext);

notificationManager.notify(NOTIFICATION_ID, builder.build());

En los siguientes pasos, se indica la parte más importante del ejemplo anterior:

  1. Llama a .setOngoing(true) en NotificationCompat.Builder y configura los campos opcionales.

  2. Crea un objeto OngoingActivityStatus, o bien otra opción de estado, según se describe en la siguiente sección, para representar el texto.

  3. Crea un objeto OngoingActivity y configura un ID de notificación.

  4. Llama a apply() en OngoingActivity con el contexto.

  5. Llama a notificationManager.notify() y pasa el mismo ID de notificación que se establece en la actividad en curso para vincularlos.

Estado

Usa Status para exponer el estado activo actual de OngoingActivity al usuario en plataformas nuevas, como la sección Recientes del selector. Para usar la función, utiliza la subclase Status.Builder.

En la mayoría de los casos, solo necesitas agregar una plantilla que represente el texto que quieres que aparezca en la sección Recientes del selector de aplicaciones.

Luego, puedes personalizar la forma en que aparece el texto con intervalos a través del método addTemplate() y la especificación de cualquier parte dinámica del texto como un Status.Part.

En el siguiente ejemplo, se muestra la forma para hacer que la palabra "tiempo" aparezca en rojo. En el ejemplo, se usa un objeto Status.StopwatchPart para representar un cronómetro en la sección Recientes del selector de aplicaciones.

Kotlin

val htmlStatus =
        "<p>The <font color=\"red\">time</font> on your current #type# is #time#.</p>"

val statusTemplate =
        Html.fromHtml(
                htmlStatus,
                Html.FROM_HTML_MODE_COMPACT
        )

// Creates a 5 minute timer.
// Note the use of SystemClock.elapsedRealtime(), not System.currentTimeMillis().
val runStartTime = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(5)

val status = new Status.Builder()
   .addTemplate(statusTemplate)
   .addPart("type", Status.TextPart("run"))
   .addPart("time", Status.StopwatchPart(runStartTime)
   .build()

Java

String htmlStatus =
        "<p>The <font color=\"red\">time</font> on your current #type# is #time#.</p>";

Spanned statusTemplate =
        Html.fromHtml(
                htmlStatus,
                Html.FROM_HTML_MODE_COMPACT
        );

// Creates a 5 minute timer.
// Note the use of SystemClock.elapsedRealtime(), not System.currentTimeMillis().
Long runStartTime = SystemClock.elapsedRealtime() + TimeUnit.MINUTES.toMillis(5);

Status status = new Status.Builder()
   .addTemplate(statusTemplate)
   .addPart("type", new Status.TextPart("run"))
   .addPart("time", new Status.StopwatchPart(runStartTime)
   .build();

Para hacer referencia a una parte de la plantilla, utiliza el nombre que aparece entre #. Para producir # en el resultado, usa ## en la plantilla.

En el ejemplo anterior, se usa HTMLCompat para generar un objeto CharSequence para pasar a la plantilla, lo que es más fácil que definir un objeto Spannable de forma manual.

Personalizaciones adicionales

Más allá de Status, puedes personalizar tu actividad en curso o tus notificaciones de las siguientes maneras. Sin embargo, es posible que estas personalizaciones no se usen, según la implementación del OEM.

Notificación continua

  • El conjunto de category determina la prioridad de la actividad en curso.
    • CATEGORY_CALL: Una llamada de voz o video entrante, o una solicitud de comunicación síncrona similar
    • CATEGORY_NAVIGATION: Un mapa o navegación paso a paso
    • CATEGORY_TRANSPORT: Control de transporte de contenido multimedia para la reproducción
    • CATEGORY_ALARM: Una alarma o un temporizador
    • CATEGORY_WORKOUT: Un entrenamiento (nueva categoría)
    • CATEGORY_LOCATION_SHARING: Uso compartido temporario de ubicación (nueva categoría)
    • CATEGORY_STOPWATCH: Cronómetro (nueva categoría)

Actividad en curso

  • Ícono animado: Un vector en blanco y negro, preferentemente con un fondo transparente. Se muestra en la cara de reloj en el modo ambiente. Si no se proporciona, se usa el ícono de notificación predeterminado. (El ícono de notificación predeterminado es diferente para cada aplicación).

  • Ícono estático: Un ícono vectorial con fondo transparente. Se muestra en la cara de reloj en el modo ambiente. Si no se configura el ícono animado, se usa el ícono estático en la cara de reloj en el modo activo. Si no se proporciona, se usa el ícono de notificación. Si no se establece ninguno, se arroja una excepción. (El selector de aplicaciones todavía utiliza el ícono de la app).

  • OngoingActivityStatus: Texto sin formato o un Chronometer. Se muestra en la sección Recientes del selector de aplicaciones. Si no se proporciona, se usa la notificación "texto de contexto".

  • Intent táctil: Se usa un PendingIntent para volver a la app si el usuario presiona el ícono de la actividad en curso. Se muestra en la cara de reloj o en el elemento del selector. Puede ser diferente del intent original que se usó para iniciar la app. Si no se proporciona, se usa el intent de contenido de la notificación. Si no se establece ninguno, se arroja una excepción.

  • LocusId: Es un ID que asigna el acceso directo del selector al que corresponde la actividad en curso. Se muestra en el selector en la sección Recientes mientras la actividad está en curso. Si no se proporciona, el selector oculta todos los elementos de la app de la sección Recientes del mismo paquete y solo muestra la actividad en curso.

  • ID de actividad en curso: Es un ID que se usa para desambiguar las llamadas a fromExistingOngoingActivity() cuando una aplicación tiene más de una actividad en curso.

Actualiza una actividad en curso

En la mayoría de los casos, los desarrolladores crean una nueva notificación continua y una nueva actividad en curso cuando necesitan actualizar los datos en la pantalla. Sin embargo, la API de Ongoing Activity también ofrece métodos auxiliares para actualizar OngoingActivity si quieres conservar una instancia en lugar de volver a crearla.

Si la app se ejecuta en segundo plano, puede enviar actualizaciones a la API de Ongoing Activity. Sin embargo, no hagas esto con demasiada frecuencia, ya que el método de actualización ignora las llamadas que están demasiado cerca entre sí. Algunas actualizaciones por minuto son razonables.

Para actualizar la actividad en curso y la notificación publicada, usa el objeto que creaste antes y llama a update(), como se muestra en el siguiente ejemplo:

Kotlin

ongoingActivity.update(context, newStatus)

Java

ongoingActivity.update(context, newStatus);

Por comodidad, existe un método estático para crear una actividad en curso.

Kotlin

OngoingActivity.recoverOngoingActivity(context)
               .update(context, newStatus)

Java

OngoingActivity.recoverOngoingActivity(context)
               .update(context, newStatus);

Detén una actividad en curso

Cuando la app termina de ejecutarse como una actividad en curso, solo necesita cancelar la notificación continua.

También puedes cancelar la notificación o la actividad en curso cuando pase a primer plano y, luego, volver a crearlas cuando vuelvan a segundo plano, aunque esto no es obligatorio.

Pausa una actividad en curso

Si tu app tiene una acción explícita de detener, continúa con la actividad en curso después de reanudarla. En el caso de las apps sin una acción de detención explícita, finaliza la actividad cuando esté en pausa.

Prácticas recomendadas

Recuerda lo siguiente cuando trabajes con la API de Ongoing Activity:

  • Llama a ongoingActivity.apply(context) antes de llamar a notificationManager.notify(...).
  • Configura un ícono estático para tu actividad en curso, ya sea de forma explícita o como resguardo a través de la notificación. De lo contrario, obtendrás un elemento IllegalArgumentException.

  • Usa íconos vectoriales en blanco y negro con fondos transparentes.

  • Configura un intent táctil para tu actividad en curso, ya sea de forma explícita o como resguardo a través de la notificación. De lo contrario, obtendrás un elemento IllegalArgumentException.

  • Para NotificationCompat, usa la biblioteca principal de AndroidX core:1.5.0-alpha05+, que incluye LocusIdCompat y las categorías nuevas de entrenamiento, cronómetro y uso compartido de la ubicación.

  • Si la app tiene más de una actividad MAIN LAUNCHER declarada en el manifiesto, publica un acceso directo dinámico y asócialo a tu actividad en curso con LocusId.

Publica notificaciones de contenido multimedia cuando se reproduce contenido multimedia en dispositivos Wear OS

Si se reproduce contenido multimedia en un dispositivo Wear OS, publica una notificación multimedia. Esto le permite al sistema crear la actividad en curso correspondiente.

Si usas Media3, la notificación se publica automáticamente. Si creas una notificación de forma manual, debes usar MediaStyleNotificationHelper.MediaStyle, y el MediaSession correspondiente debe tener su actividad de la sesión propagada.