Atividade em andamento

No Wear OS, o pareamento de uma atividade em andamento com uma notificação em andamento adiciona essa notificação a outras plataformas na interface do usuário do Wear OS. Assim, os usuários podem interagir melhor com atividades de longa duração.

Normalmente, as notificações em andamento são usadas para indicar que há uma tarefa em segundo plano com engajamento ativo do usuário ou que está pendente de alguma forma, mantendo o dispositivo ocupado.

Por exemplo, um usuário do Wear OS pode usar um app de treino para gravar uma corrida em uma atividade e, depois, sair desse app para iniciar outra tarefa. Quando o usuário sai do app de treino, o app faz a transição para uma notificação em andamento vinculada a algum trabalho em segundo plano para manter o usuário informado sobre a corrida. A notificação fornece atualizações ao usuário e uma maneira fácil de tocar para voltar ao app.

No entanto, para conferir a notificação, o usuário precisa deslizar para a bandeja de notificações abaixo do mostrador do relógio e encontrar a notificação correta. Isso não é tão prático quanto em outras plataformas.

Com a API Ongoing Activity, a notificação em andamento de um app pode expor informações a várias plataformas novas e convenientes no Wear OS para manter o usuário engajado.

Por exemplo, neste app de treino, as informações podem aparecer no mostrador do relógio do usuário como um ícone de corrida que pode ser tocado:

ícone de corrida

Figura 1. Indicador de atividade

A seção Recentes do Acesso rápido aos apps global também lista todas as atividades em andamento:

tela de início

Figura 2. Acesso rápido global

Confira abaixo boas situações para usar uma notificação em andamento vinculada a uma atividade em andamento:

cronômetro

Figura 3. Cronômetro: faz a contagem ativa de tempo decorrido e termina quando o cronômetro é pausado/interrompido.

mapa

Figura 4. Navegação guiada: mostra rotas para um destino. Termina quando o usuário chega ao destino ou para a navegação.

música

Figura 5. Mídia: reproduz música durante uma sessão. Termina imediatamente após o usuário pausar a sessão.

O Wear cria atividades em andamento automaticamente para apps de música.

Consulte o codelab sobre Atividades em andamento para um exemplo detalhado da criação dessas atividades para outros tipos de apps.

Configurar

Para começar a usar a API Ongoing Activity no app, adicione as dependências abaixo ao arquivo build.gradle do 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"
}

Iniciar uma atividade em andamento

Para começar, crie uma notificação e depois uma atividade em andamento.

Criar uma notificação em andamento

Uma atividade em andamento está intimamente relacionada a uma notificação em andamento. Elas trabalham em conjunto para informar sobre uma tarefa com que usuário está ativamente engajado ou que está pendente de alguma forma, mantendo o dispositivo ocupado.

Você precisa combinar uma atividade em andamento com uma notificação em andamento. Há muitos benefícios em vincular sua atividade em andamento a uma notificação, incluindo:

  • As notificações são os substitutos em dispositivos que não oferecem suporte a atividades em andamento. A notificação é a única plataforma que seu app mostra durante a execução em segundo plano.
  • No Android 11 e em versões mais recentes, o Wear OS oculta a notificação na bandeja quando o app está visível como uma atividade em andamento em outras plataformas.
  • A implementação atual usa a própria Notification como mecanismo de comunicação.

Crie uma notificação em andamento usando Notification.Builder.setOngoing.

Iniciar uma atividade em andamento

Com uma notificação em andamento, crie uma atividade em andamento, conforme mostrado no exemplo abaixo. Leia os comentários para entender o comportamento de cada propriedade.

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());

As etapas abaixo destacam a parte mais importante do exemplo anterior:

  1. Chame .setOngoing(true) no NotificationCompat.Builder e defina todos os campos opcionais.

  2. Crie um OngoingActivityStatus ou outra opção de status, conforme descrito na próxima seção, para representar o texto.

  3. Crie uma OngoingActivity e defina um ID de notificação.

  4. Chame apply() em OngoingActivity com o contexto.

  5. Chame notificationManager.notify() e transmita o mesmo ID de notificação definido na atividade em andamento para fazer a vinculação.

Status

O Status é usado para expor o status atual e ativo da OngoingActivity ao usuário em novas plataformas, como a seção Recentes na tela de início. Para usar o recurso, utilize a subclasse Status.Builder.

Na maioria dos casos, você só precisa adicionar um modelo que represente o texto que quer mostrar na seção Recentes da tela de início.

É possível personalizar a aparência do texto com Períodos usando o método addTemplate() e especificando qualquer parte dinâmica do texto como um Status.Part.

O exemplo abaixo mostra como a palavra "time" (tempo) aparece em vermelho. O exemplo usa uma Status.StopwatchPart para representar um cronômetro na seção Recentes do Acesso rápido aos apps.

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 fazer referência a uma parte do modelo, use o nome cercado por #. Para produzir # na saída, use ## no modelo.

O exemplo anterior usa HTMLCompat para gerar uma CharSequence para transmitir ao modelo, o que é mais fácil do que definir manualmente um objeto Spannable.

Outras personalizações

Além de Status, você pode personalizar as atividades ou notificações em andamento das formas mostradas abaixo. No entanto, essas personalizações podem não ser usadas, com base na implementação do OEM.

Notificação em andamento

  • A categoria definida determina a prioridade da atividade em andamento.
    • CATEGORY_CALL: uma ligação ou videochamada recebida ou uma solicitação de comunicação síncrona semelhante
    • CATEGORY_NAVIGATION: um mapa ou uma navegação guiada
    • CATEGORY_TRANSPORT: controle de transporte de mídia para reprodução
    • CATEGORY_ALARM: um alarme ou timer
    • CATEGORY_WORKOUT: um treino (nova categoria)
    • CATEGORY_LOCATION_SHARING: compartilhamento temporário de local (nova categoria)
    • CATEGORY_STOPWATCH: cronômetro (nova categoria)

Atividade em andamento

  • Ícone animado: um vetor em preto e branco, de preferência com um segundo plano transparente. Aparece no mostrador do relógio no modo ativo. Se o ícone animado não for fornecido, o ícone de notificação padrão vai ser usado. O ícone de notificação padrão é diferente para cada aplicativo.

  • Ícone estático: ícone vetorial com segundo plano transparente. Aparece no mostrador do relógio no modo ambiente. Se o ícone animado não estiver definido, o ícone estático vai ser usado no mostrador do relógio no modo ativo. Se esse valor não for fornecido, o ícone de notificação vai ser usado. Se nenhum deles for definido, uma exceção vai ser gerada. A tela de início ainda vai usar o ícone do app.

  • OngoingActivityStatus: texto simples ou um Chronometer. Aparece na seção Recentes da tela de início. Se não for informado, vai ser usada a notificação "texto de contexto".

  • Intent de toque: uma PendingIntent usada para voltar ao app se o usuário tocar no ícone de atividade em andamento. Aparece no mostrador do relógio ou no item da tela de início. Pode ser diferente da intent original usada para iniciar o app. Se não for fornecida, a intent de conteúdo da notificação será usada. Se nenhuma delas for definida, uma exceção vai ser gerada.

  • LocusId: um ID que atribui o atalho da tela de início a que a atividade em andamento corresponde. Aparece na tela de início na seção Recentes enquanto a atividade está em andamento. Se não for fornecido, a tela de início vai ocultar todos os itens do app na seção Recentes do mesmo pacote e mostrar apenas a atividade em andamento.

  • ID de atividade em andamento: um ID usado para diferenciar as chamadas para fromExistingOngoingActivity() quando um aplicativo tem mais de uma atividade em andamento.

Atualizar uma atividade em andamento

Na maioria dos casos, os desenvolvedores criam uma nova notificação e uma nova atividade em andamento quando precisam atualizar os dados na tela. No entanto, a API Ongoing Activity também oferece métodos auxiliares para atualizar uma OngoingActivity se você quiser reter, em vez de recriar uma instância.

Se o app estiver em execução em segundo plano, ele vai poder enviar atualizações para a API Ongoing Activity. No entanto, não faça isso com muita frequência, porque o método de atualização ignora chamadas muito próximas umas das outras. Algumas atualizações por minuto são razoáveis.

Para atualizar a atividade em andamento e a notificação postada, use o objeto criado anteriormente e chame update(), conforme mostrado no exemplo abaixo:

Kotlin

ongoingActivity.update(context, newStatus)

Java

ongoingActivity.update(context, newStatus);

Por conveniência, há um método estático para criar uma atividade em andamento.

Kotlin

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

Java

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

Interromper uma atividade em andamento

Quando o app termina de ser executado como uma atividade em andamento, ele só precisa cancelar a notificação em andamento.

Também é possível cancelar a notificação ou a atividade em andamento quando ela está em primeiro plano e, depois, recriá-la quando voltar ao segundo plano, mas isso não é necessário.

Pausar uma atividade em andamento

Se o app tiver uma ação de interrupção explícita, continue a atividade em andamento depois que ela for retomada. Para um app sem uma ação de parada explícita, encerre a atividade quando ela estiver pausada.

Práticas recomendadas

Não esqueça dos princípios abaixo ao trabalhar com a API Ongoing Activity:

  • Chame ongoingActivity.apply(context) antes de chamar notificationManager.notify(...).
  • Sempre defina um ícone estático para a atividade em andamento explicitamente ou como um substituto usando a notificação. Caso contrário, uma IllegalArgumentException vai ser gerada.

  • Use ícones vetoriais pretos e brancos com planos de fundo transparentes.

  • Sempre defina uma intent de toque para a atividade em andamento explicitamente ou como uma substituta usando a notificação. Caso contrário, uma IllegalArgumentException vai ser gerada.

  • Para NotificationCompat, use a biblioteca principal do AndroidX core:1.5.0-alpha05+, que inclui o LocusIdCompat e as novas categorias para treino, cronômetro e compartilhamento de local.

  • Se o app tiver mais de uma atividade MAIN LAUNCHER declarada no manifesto, publique um atalho dinâmico e o associe à atividade em andamento usando LocusId.

Publicar notificações ao abrir mídia em dispositivos Wear OS

Se estiver tocando conteúdo de mídia em um dispositivo Wear OS, publique uma notificação de mídia. Isso permite que o sistema crie uma atividade em andamento correspondente.

Se você estiver usando a Media3, a notificação será publicada automaticamente. Se você criar sua notificação manualmente, ela vai usar o MediaStyleNotificationHelper.MediaStyle, e a MediaSession correspondente terá a atividade da sessão preenchida.