Apps siempre encendidas y modo ambiente del sistema

En esta guía, se explica cómo hacer que tu app esté siempre activa, cómo reaccionar a las transiciones de estado de energía y cómo administrar el comportamiento de la aplicación para brindar una buena experiencia del usuario y, al mismo tiempo, conservar la batería.

Hacer que una app sea visible constantemente afecta significativamente la duración de la batería, por lo que debes tener en cuenta el impacto en el consumo de energía cuando agregues esta función.

Conceptos clave

Cuando una app para Wear OS se muestra en pantalla completa, se encuentra en uno de los siguientes dos estados de energía:

  • Interactivo: Es un estado de alta potencia en el que la pantalla tiene el brillo máximo, lo que permite la interacción completa del usuario.
  • Ambiental: Es un estado de bajo consumo en el que la pantalla se atenúa para ahorrar batería. En este estado, la IU de tu app sigue ocupando toda la pantalla, pero el sistema podría alterar su apariencia desenfocándola o mostrando contenido superpuesto, como la hora. Esto también se conoce como Modo ambiente.

El sistema operativo controla la transición entre estos estados.

Una app siempre activa es una aplicación que muestra contenido en los estados Interactivo y Ambiente.

Cuando una app siempre activa sigue mostrando su propia IU mientras el dispositivo está en el estado de Ambient de bajo consumo, se describe que está en modo ambiactivo.

Transiciones del sistema y comportamiento predeterminado

Cuando una app está en primer plano, el sistema administra las transiciones de estado de energía según dos tiempos de espera que se activan por la inactividad del usuario.

  • Tiempo de espera 1: Estado interactivo a ambiente: Después de un período de inactividad del usuario, el dispositivo entra en el estado Ambiente.
  • Tiempo de espera núm. 2: Regreso a la cara de reloj: Después de un período más prolongado de inactividad, es posible que el sistema oculte la app actual y muestre la cara de reloj.

Inmediatamente después de que el sistema pasa por la primera transición al estado Ambient, el comportamiento predeterminado depende de la versión de Wear OS y la configuración de tu app:

  • En Wear OS 5 y versiones anteriores, el sistema muestra una captura de pantalla desenfocada de la aplicación en pausa, con la hora superpuesta. Este estado se representa con el nodo "AOD Lite" en el siguiente diagrama de flujo.
  • En Wear OS 6 y versiones posteriores, si una app se segmenta para el SDK 36 o versiones posteriores, se considera que está siempre activa. La pantalla se atenúa, pero la aplicación sigue ejecutándose y permanece visible. (Las actualizaciones pueden ser tan poco frecuentes como una vez por minuto). Este estado se representa con el nodo "Global AOD" en el siguiente diagrama de flujo.

Personaliza el comportamiento del estado Ambiente

Independientemente del comportamiento predeterminado del sistema, en todas las versiones de Wear OS, puedes personalizar la apariencia o el comportamiento de tu app mientras se encuentra en el estado Ambient con LocalAmbientModeManager para observar las transiciones de estado en Jetpack Compose en Wear OS. Este estado se representa con el nodo "Modo ambiactivo" en el siguiente diagrama de flujo.

Observa el estado ambiente

Para reaccionar a los eventos del modo ambiente en Jetpack Compose en Wear OS, haz lo siguiente:

  1. Usa LocalAmbientModeManager para observar los cambios en el estado del modo ambiente del sistema y ajustar los elementos componibles según corresponda. Configura tu componente para que se ajuste a un modo de menor consumo de energía en el estado AmbientMode.Ambient y restablécelo a una pantalla interactiva completa en el estado AmbientMode.Interactive.

    // In a production application, the AmbientModeManager should be instantiated and provided at
    // the highest level of the Compose hierarchy (typically in the host Activity's setContent
    // block) using a CompositionLocalProvider. This ensures proper lifecycle management and
    // broad accessibility.
    
    // For this self-contained demo, AmbientModeManager is created and provided locally:
    val activityAmbientModeManager = rememberAmbientModeManager()
    CompositionLocalProvider(LocalAmbientModeManager provides activityAmbientModeManager) {
        val ambientModeManager = LocalAmbientModeManager.current
        val ambientMode = ambientModeManager?.currentAmbientMode
    
        if (ambientModeManager != null) {
            ambientModeManager.AmbientTickEffect {
                // While device is in ambient mode, update properties every minute or so
                // ...
            }
        }
    
        // ...
            val ambientModeName =
                when (ambientMode) {
                    is AmbientMode.Interactive -> "Interactive"
                    is AmbientMode.Ambient -> "Ambient"
                    else -> "Unknown"
                }
    
            Text(text = "$ambientModeName Mode")
            // ...
    }

Diagrama de flujo del comportamiento ambiental

En el siguiente diagrama de flujo, se ilustra cómo el sistema determina el comportamiento en modo ambiente según la versión de Wear OS del dispositivo, el targetSdkVersion de tu app y si observa transiciones de estado en modo ambiente (con LocalAmbientModeManager o AmbientLifecycleCallback).

Diagrama de flujo que ilustra la lógica de decisión para el modo ambiente de Wear OS. Muestra cómo la versión del SO del dispositivo y la configuración de la app determinan uno de los tres resultados posibles: una superposición borrosa, la función Pantalla siempre activa global o el modo Ambiactivo administrado por la app.
Figura 1: Diagrama de flujo que ilustra la lógica de decisión para el modo ambiente de Wear OS.

Cómo controlar la duración de la pantalla encendida

En las siguientes secciones, se describe cómo administrar el tiempo que tu app permanece en la pantalla.

Cómo evitar volver a la cara de reloj con una actividad en curso o una actualización en vivo

Después de un período en el estado Ambiental (Tiempo de espera núm. 2), el sistema suele volver a la cara de reloj. El usuario puede configurar la duración del tiempo de espera en la configuración del sistema. En algunos casos de uso, como cuando un usuario hace un seguimiento de un entrenamiento, es posible que una app deba permanecer visible durante más tiempo.

En Wear OS 5 y versiones posteriores, puedes evitar esto implementando una Ongoing Activity. Si tu app muestra información sobre una tarea del usuario en curso, como una sesión de entrenamiento, puedes usar la API de Ongoing Activity para mantener la app visible hasta que finalice la tarea. Si un usuario regresa manualmente a la cara del reloj, el indicador de actividad en curso le proporciona una forma de regresar a tu app con un solo toque.

Como alternativa, en Wear OS 7 y versiones posteriores, puedes usar una Actualización en vivo en lugar de una Actividad en curso. Para garantizar la retrocompatibilidad, se seguirá admitiendo la Actividad en curso en dispositivos que ejecuten Wear OS 6 o versiones anteriores.

Para implementar esto, el intent de toque de la notificación continua debe apuntar a tu actividad siempre activa, como se muestra en el siguiente fragmento de código:

val activityIntent =
    Intent(this, AlwaysOnActivity::class.java).apply {
        flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
    }

val pendingIntent =
    PendingIntent.getActivity(
        this,
        0,
        activityIntent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

val notificationBuilder =
    NotificationCompat.Builder(this, CHANNEL_ID)
        // ...
        // ...
        .setOngoing(true)

// ...

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // ...
        // ...
        .setTouchIntent(pendingIntent)
        .build()

ongoingActivity.apply(applicationContext)

val notification = notificationBuilder.build()

Mantener la pantalla encendida y evitar el estado de ambiente

En casos excepcionales, es posible que debas impedir por completo que el dispositivo entre en el estado Ambient. Es decir, para evitar el tiempo de espera n° 1. Para ello, puedes usar el modificador keepScreenOn. Esto funciona como un bloqueo de activación, lo que mantiene el dispositivo en el estado Interactivo. Usa esta opción con extrema precaución, ya que afecta gravemente la duración de la batería.

Recomendaciones para el Modo ambiente

Para proporcionar la mejor experiencia del usuario y ahorrar energía en el modo Ambient, sigue estos lineamientos de diseño. Estas recomendaciones priorizan una experiencia del usuario clara, ya que evitan la información engañosa y reducen el desorden visual, a la vez que optimizan la potencia de visualización.

  • Reduce el desorden visual y muestra la potencia. Una IU limpia y minimalista le indica al usuario que la app se encuentra en un estado de bajo consumo y ahorra una cantidad significativa de batería limitando los píxeles brillantes.
    • Mantén al menos el 85% de la pantalla en negro (una proporción de píxeles encendidos del 15% o menos).
    • Muestra solo la información más importante y traslada los detalles secundarios a la pantalla interactiva.
    • Usa contornos para los íconos o botones grandes en lugar de rellenos sólidos, y ajusta el contraste del contorno para que los elementos sigan siendo legibles sin riesgo de parpadeo o quemado de la pantalla.
    • Evita los bloques grandes de color sólido y las imágenes de fondo o de desarrollo de la marca que no funcionen.
  • Cómo controlar datos dinámicos y animaciones obsoletos
    • La devolución de llamada AmbientTickEffect se invoca solo de forma periódica (por lo general, una vez por minuto) para ahorrar energía. Debido a esta limitación, cualquier dato que cambie con frecuencia, como un cronómetro, el ritmo cardíaco o la distancia de entrenamiento, se vuelve obsoleto entre las actualizaciones. Para evitar mostrar información incorrecta y engañosa, reemplaza estos valores dinámicos por contenido de marcador de posición estático, como --.
    • Quita los indicadores de progreso que requieren actualizaciones frecuentes para ser útiles en el contexto o la situación actual del usuario, como los anillos de progreso para sesiones multimedia cortas. Puedes conservar los indicadores de progreso en los casos en que las actualizaciones cada minuto sigan siendo útiles, como en el caso de los temporizadores largos.
    • Pausar o quitar todas las animaciones en el modo ambiente, incluidos los indicadores de progreso animados y el texto de marquesina desplazable
  • Mantén un diseño coherente
    • Mantén los elementos en la misma posición en los modos Interactivo y Ambiente para crear una transición fluida. Los elementos de IU interactivos, como los botones, pueden permanecer visibles porque los dispositivos Wear OS modernos admiten la entrada táctil durante las sesiones en modo ambiente.
    • Siempre muestra la hora actual. En Jetpack Compose para Wear OS, TimeText (incluido de forma predeterminada en ScreenScaffold) reconoce el modo ambiente y se actualiza automáticamente una vez por minuto en ese modo.
  • Ten en cuenta el contexto
    • Si el usuario estaba en una pantalla de configuración cuando el dispositivo entró en el modo ambiente, considera mostrar una pantalla más relevante de tu app en lugar de la vista de configuración.
  • Cómo controlar los requisitos específicos del dispositivo
    • Cuando currentAmbientMode devuelve AmbientMode.Ambient:
      • Si isLowBitAmbientSupported es true, la pantalla admite menos bits por color en el modo ambiente. Inhabilita el suavizado de contorno (por ejemplo, en operaciones de dibujo personalizadas de Canvas o Paint) y evita los gradientes de color siempre que sea posible.
      • Si isBurnInProtectionRequired es true, el sistema desplaza periódicamente el contenido de la pantalla para evitar el quemado de píxeles. No desplaces manualmente los elementos de la IU en tu app. En cambio, evita las áreas blancas sólidas y coloca los elementos de la IU importantes a, al menos, 10 píxeles del borde de la pantalla para que no se recorten cuando el sistema desplace la pantalla. (Los diseños estándar de Wear Compose, como ScreenScaffold, ya proporcionan suficiente relleno en los bordes).

Depuración y pruebas

Estos comandos de adb pueden ser útiles cuando desarrollas o pruebas el comportamiento de tu app cuando el dispositivo está en modo ambiente:

# put device in ambient mode if the always on display is enabled in settings
# (and not disabled by other settings, such as theatre mode)
$ adb shell input keyevent KEYCODE_SLEEP

# put device in interactive mode
$ adb shell input keyevent KEYCODE_WAKEUP

Ejemplo: App de entrenamiento

Considera una app de entrenamiento que necesita mostrar métricas al usuario durante toda la sesión de ejercicio. La app debe permanecer visible durante las transiciones de estado de Ambient y no debe reemplazarse por la cara del reloj.

Para lograrlo, el desarrollador debe hacer lo siguiente:

  1. Usa LocalAmbientModeManager para controlar los cambios en la IU entre los estados Interactivo y Ambiental, como atenuar la pantalla y quitar los datos no esenciales.
  2. Crea un nuevo diseño de bajo consumo para el estado Ambiente que siga las prácticas recomendadas.
  3. Usa la API de Ongoing Activity (o Actualizaciones en vivo en Wear OS 7 y versiones posteriores) durante el entrenamiento para evitar que el sistema vuelva a la cara del reloj.

Para ver una implementación completa, consulta la muestra de ejercicio basada en Compose en GitHub. En este ejemplo, también se muestra el uso del elemento LocalAmbientModeManager componible para simplificar el control del modo ambiente en Compose.