Apps sempre ativados e modo ambiente do sistema

Este guia explica como deixar seu app sempre ativo, como reagir a transições de estado de energia e como gerenciar o comportamento do aplicativo para oferecer uma boa experiência do usuário enquanto economiza bateria.

Tornar um app constantemente visível afeta significativamente a duração da bateria. Por isso, considere o impacto no consumo de energia ao adicionar esse recurso.

Conceitos principais

Quando um app do Wear OS é mostrado em tela cheia, ele está em um de dois estados de energia:

  • Interativo: um estado de alta potência em que a tela está com brilho máximo, permitindo interação total do usuário.
  • Ambiente: um estado de baixo consumo de energia em que a tela escurece para economizar energia. Nesse estado, a interface do app ainda ocupa a tela inteira, mas o sistema pode alterar a aparência dela desfocando ou sobrepondo conteúdo, como a hora. Isso também é chamado de Modo ambiente.

O sistema operacional controla a transição entre esses estados.

Um app sempre ativo é um aplicativo que mostra conteúdo nos estados Interativo e Ambiente.

Quando um app sempre ativado continua mostrando a própria interface enquanto o dispositivo está no estado de baixo consumo de energia ambiente, ele é descrito como estando no modo ambiactivo.

Transições do sistema e comportamento padrão

Quando um app está em primeiro plano, o sistema gerencia as transições de estado de energia com base em dois tempos limite acionados pela inatividade do usuário.

  • Tempo limite 1: estado interativo para ambiente:após um período de inatividade do usuário, o dispositivo entra no estado Ambiente.
  • Tempo limite nº 2: voltar ao mostrador do relógio:após um período adicional de inatividade, o sistema pode ocultar o app atual e mostrar o mostrador do relógio.

Logo após o sistema passar pela primeira transição para o estado Ambient, o comportamento padrão depende da versão do Wear OS e da configuração do app:

  • No Wear OS 5 e versões anteriores, o sistema mostra uma captura de tela desfocada do aplicativo pausado, com o tempo sobreposto na parte de cima.
  • No Wear OS 6 e versões mais recentes, se um app segmentar o SDK 36 ou mais recente, ele será considerado sempre ativo. A tela fica esmaecida, mas o aplicativo continua em execução e visível. As atualizações podem ser tão raras quanto uma vez por minuto.

Personalizar o comportamento do estado Ambiente

Independente do comportamento padrão do sistema, em todas as versões do Wear OS, é possível personalizar a aparência ou o comportamento do app no estado Ambiental usando AmbientLifecycleObserver para detectar callbacks em transições de estado.

Usar AmbientLifecycleObserver

Para reagir a eventos do modo ambiente, use a classe AmbientLifecycleObserver:

  1. Implemente a interface AmbientLifecycleObserver.AmbientLifecycleCallback. Use o método onEnterAmbient() para ajustar a interface ao estado de baixo consumo de energia e onExitAmbient() para restaurar a tela interativa completa.

    val ambientCallback = object : AmbientLifecycleObserver.AmbientLifecycleCallback {
        override fun onEnterAmbient(ambientDetails: AmbientLifecycleObserver.AmbientDetails) {
            // ... Called when moving from interactive mode into ambient mode.
            // Adjust UI for low-power state: dim colors, hide non-essential elements.
        }
    
        override fun onExitAmbient() {
            // ... Called when leaving ambient mode, back into interactive mode.
            // Restore full UI.
        }
    
        override fun onUpdateAmbient() {
            // ... Called by the system periodically (typically once per minute)
            // to allow the app to update its display while in ambient mode.
        }
    }
    
  2. Crie um AmbientLifecycleObserver e registre-o com o ciclo de vida da atividade ou elemento combinável.

    private val ambientObserver = AmbientLifecycleObserver(activity, ambientCallback)
    
    override fun onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(ambientObserver)
    
        // ...
    }
    
  3. Chame removeObserver() para remover o observador em onDestroy().

Para desenvolvedores que usam o Jetpack Compose, a biblioteca Horologist oferece uma utilidade útil, o elemento combinável AmbientAware, que simplifica a implementação desse padrão.

TimeText com reconhecimento de ambiente

Como exceção à necessidade de um observador personalizado, no Wear OS 6, o widget TimeText é compatível com o modo ambiente. Ele é atualizado automaticamente uma vez por minuto quando o dispositivo está no estado Ambiente sem nenhum código adicional.

Controlar a duração da tela ativada

As seções a seguir descrevem como gerenciar o tempo que seu app fica na tela.

Evitar voltar ao mostrador do relógio com uma atividade em andamento

Após um período no estado Ambiente (tempo limite nº 2), o sistema normalmente volta ao mostrador do relógio. O usuário pode configurar a duração do tempo limite nas configurações do sistema. Em alguns casos de uso, como um usuário monitorando um treino, um app pode precisar ficar visível por mais tempo.

No Wear OS 5 e em versões mais recentes, é possível evitar isso implementando uma Atividade em andamento. Se o app estiver mostrando informações sobre uma tarefa em andamento do usuário, como uma sessão de treino, use a API Ongoing Activity para manter o app visível até o fim da tarefa. Se um usuário voltar manualmente ao mostrador do relógio, o indicador de atividade em andamento vai oferecer uma maneira de retornar ao seu app com um toque.

Para implementar isso, a intent de toque da notificação em andamento precisa apontar para sua atividade sempre ativa, conforme mostrado no snippet de código a seguir:

private fun createNotification(): Notification {
    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)

    return notificationBuilder.build()
}

Manter a tela ativada e evitar o estado ambiente

Em casos raros, talvez seja necessário impedir completamente que o dispositivo entre no estado Ambiente. Ou seja, para evitar o tempo limite 1. Para fazer isso, use a flag de janela FLAG_KEEP_SCREEN_ON. Isso funciona como um bloqueio de despertar, mantendo o dispositivo no estado Interativo. Use com muito cuidado, porque isso afeta muito a duração da bateria.

Recomendações para o Modo ambiente

Para oferecer a melhor experiência do usuário e economizar energia no modo Ambiente, siga estas diretrizes de design. Essas recomendações priorizam uma experiência do usuário clara, evitando informações enganosas e reduzindo a poluição visual, ao mesmo tempo em que otimizam o poder de exibição.

  • Reduza a poluição visual e a potência da tela. Uma interface limpa e minimalista indica ao usuário que o app está em um estado de baixo consumo de energia e economiza bateria significativamente ao limitar pixels brilhantes.
    • Mantenha pelo menos 85% da tela preta.
    • Mostre apenas as informações mais importantes, movendo os detalhes secundários para a tela interativa.
    • Use contornos em vez de preenchimentos sólidos para ícones ou botões grandes.
    • Evite grandes blocos de cores sólidas e imagens de marca ou de fundo não funcionais.
  • Processar dados dinâmicos desatualizados
    • O callback onUpdateAmbient() é invocado apenas periodicamente, geralmente uma vez por minuto, para economizar energia. Devido a essa limitação, qualquer dado que mude com frequência, como um cronômetro, frequência cardíaca ou distância do treino, fica desatualizado entre as atualizações. Para evitar mostrar informações enganosas e incorretas, ouça o callback onEnterAmbient e substitua esses valores dinâmicos por conteúdo de marcador estático, como --.
  • Mantenha um layout consistente
    • Mantenha os elementos na mesma posição nos modos Interativo e Ambiente para criar uma transição suave.
    • Sempre mostrar a hora.
  • Esteja ciente do contexto
    • Se o usuário estiver em uma tela de configurações ou configuração quando o dispositivo entrar no modo ambiente, considere mostrar uma tela mais relevante do seu app em vez da visualização de configurações.
  • Processar requisitos específicos do dispositivo
    • No objeto AmbientDetails transmitido para onEnterAmbient():
      • Se deviceHasLowBitAmbient for true, desative a suavização sempre que possível.
      • Se burnInProtectionRequired for true, mova os elementos da interface ligeiramente de forma periódica e evite áreas brancas sólidas para evitar a queima da tela.

Depuração e testes

Estes comandos adb podem ser úteis ao desenvolver ou testar o comportamento do app quando o dispositivo está no 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

Exemplo: app de treino

Considere um app de treino que precisa mostrar métricas ao usuário durante toda a sessão de exercícios. O app precisa permanecer visível durante as transições de estado Ambient e não pode ser substituído pelo mostrador do relógio.

Para isso, o desenvolvedor precisa fazer o seguinte:

  1. Implemente um AmbientLifecycleObserver para processar mudanças na interface entre os estados Interativo e Ambiente, como escurecer a tela e remover dados não essenciais.
  2. Crie um novo layout de baixo consumo de energia para o estado Ambiente que siga as práticas recomendadas.
  3. Use a API Ongoing Activity durante todo o treino para evitar que o sistema volte ao mostrador do relógio.

Para uma implementação completa, consulte o Exemplo de exercício (link em inglês) baseado no Compose no GitHub. Este exemplo também demonstra o uso do elemento combinável AmbientAware da biblioteca Horologist para simplificar o processamento do modo ambiente no Compose.