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 para que os usuários possam interagir melhor com atividades de longa duração.

Normalmente, as notificações em andamento são usadas para indicar que uma notificação tem uma tarefa em segundo plano com que o usuário está ativamente envolvido 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 geralmente faz a transição para uma notificação em andamento vinculada a algum trabalho em segundo plano (por exemplo, serviços ou gerenciadores de alarme) 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. Tela de início 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 Atividade em andamento no GitHub para um exemplo detalhado sobre a criação de atividades em andamento 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

Comece uma atividade em andamento.

Notificação em andamento

Como já mencionamos, as atividades em andamento estão diretamente relacionadas a uma notificação em andamento.

Ambas trabalham em conjunto para informar sobre uma tarefa com que usuário está ativamente engajado ou 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 vai mostrar 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.

Atividade em andamento

É fácil iniciar uma atividade em andamento quando você tem uma notificação em andamento.

O exemplo de código abaixo contém comentários para ajudar você a entender o que cada propriedade significa:

Kotlin

var builder = 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)
        // In our case, 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 builder = 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)
        // In our case, 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 para representar o texto. Confira outras opções de status na próxima seção.

  3. Crie uma OngoingActivity e defina um ID de notificação (obrigatório).

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

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

Status

O Status permite que os desenvolvedores exponham o status atual e ativo da OngoingActivity ao usuário em novas plataformas, como a seção "Recentes" da tela de início. Para usar o recurso, utilize o Status.Builder.

Na maioria dos casos, os desenvolvedores só precisam adicionar um modelo que represente o texto que eles querem que apareça na seção Recentes do Acesso rápido aos apps.

Os desenvolvedores podem personalizar a aparência do texto com Períodos usando o método addTemplate() e especificando qualquer parte dinâmica do texto como uma Status.Part.

O exemplo abaixo mostra como a palavra "time" (tempo) aparece em vermelho. O exemplo usa uma Status.TimerPart, que permite representar um timer ou 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, os desenvolvedores podem personalizar as atividades ou notificações em andamento das formas mostradas abaixo. Essas personalizações podem ou 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 recebida (voz ou vídeo) ou solicitação similar de comunicação síncrona.
    • 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 (novo).
    • CATEGORY_LOCATION_SHARING: compartilhamento temporário de local (novo).
    • CATEGORY_STOPWATCH: cronômetro (novo).

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 durante o modo ativo. Se esse valor não for fornecido, o ícone de notificação padrão vai ser usado.

  • Í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 para o 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. O ícone usado no Acesso rápido aos apps ainda vai usar o ícone do app.

  • OngoingActivityStatus: texto simples ou um cronômetro. Aparece na seção "Recentes" do Acesso rápido aos apps. 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 vai ser usada. Se nenhuma delas for definida, uma exceção será lançada. \

  • 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 fromExistingOngoingActivityfromExistingOngoingActivity() 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 uma instância, em vez de a recriar.

Se o app estiver sendo executado em segundo plano, ele vai poder enviar atualizações para a API Ongoing Activity, mas isso não será frequente. O método de atualização pode ignorar chamadas que estão muito próximas uma da outra. 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.

Cabe ao app cancelar a notificação ou a atividade em andamento quando ela está em primeiro plano e, depois, recriá-la quando voltar ao segundo plano.

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. No entanto, apps sem uma ação de interrupção explícita precisam encerrar a atividade quando pausadas.

Práticas recomendadas

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

  • Sempre 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, você vai receber uma IllegalArgumentException.

  • Os ícones precisam ser vetores em preto e branco com segundo plano transparente.

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

  • Para NotificationCompat, use a biblioteca AndroidX principal core:1.5.0-alpha05+, que inclui o LocusIdCompat e as novas categorias (treino, cronômetro ou 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.