Atividades contínuas

Os dispositivos Wear OS são usados com frequência para experiências de longa duração, como o rastreamento de um treino. Isso apresenta um desafio para a experiência do usuário: se um usuário iniciar uma tarefa e depois navegar até o mostrador do relógio, como ele volta? Voltar ao app usando o iniciador pode ser difícil, especialmente quando você está em movimento, criando atrito desnecessário.

A solução é parear uma notificação em andamento com um OngoingActivity. Isso permite que o dispositivo mostre informações sobre a atividade de longa duração na interface do usuário, ativando recursos como o ícone de toque na parte de baixo do mostrador do relógio. Isso mantém os usuários cientes da tarefa em segundo plano e oferece uma maneira de voltar ao app com um toque.

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

Uma notificação em andamento também mostra informações na seção Recentes do Acesso rápido aos apps global. Assim, os usuários têm outro lugar conveniente para conferir o status da tarefa e voltar a interagir com o app:

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.

Configuração

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.1.0"
  implementation "androidx.core:core:1.17.0"
}

Criar uma atividade em andamento

O processo envolve três etapas:

  1. Crie um NotificationCompat.Builder padrão e configure-o como contínuo.
  2. Crie e configure um objeto OngoingActivity, transmitindo o criador de notificações para ele.
  3. Aplique a atividade em andamento ao builder de notificações e poste a notificação resultante.

Criar e configurar a notificação

Comece criando um NotificationCompat.Builder. A etapa principal é chamar setOngoing(true) para marcá-la como uma notificação em andamento. Você também pode definir outras propriedades de notificação nessa etapa, como o ícone pequeno e a categoria.

// Create a PendingIntent to pass to the notification builder
val pendingIntent =
    PendingIntent.getActivity(
        this,
        0,
        Intent(this, AlwaysOnActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
        },
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
    )

val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
    .setContentTitle("Always On Service")
    .setContentText("Service is running in background")
    .setSmallIcon(R.drawable.animated_walk)
    // Category helps the system prioritize the ongoing activity
    .setCategory(NotificationCompat.CATEGORY_WORKOUT)
    .setContentIntent(pendingIntent)
    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
    .setOngoing(true) // Important!

Criar o OngoingActivity

Em seguida, crie uma instância de OngoingActivity usando o builder correspondente. O OngoingActivity.Builder exige um Context, um ID de notificação e o NotificationCompat.Builder criado na etapa anterior.

Configure as propriedades principais que serão mostradas nas novas plataformas de interface:

  • Ícones animados e estáticos: forneça ícones que são exibidos no mostrador do relógio nos modos ativo e ambiente.
  • Intent de toque: uma PendingIntent que traz o usuário de volta ao seu app quando ele toca no ícone de atividade em andamento. Você pode reutilizar o pendingIndent criado na etapa anterior.

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // Sets the icon that appears on the watch face in active mode.
        .setAnimatedIcon(R.drawable.animated_walk)
        // Sets the icon that appears on the watch face in ambient mode.
        .setStaticIcon(R.drawable.ic_walk)
        // Sets the tap target to bring the user back to the app.
        .setTouchIntent(pendingIntent)
        .build()

Aplicar à notificação e postar

A etapa final é vincular o OngoingActivity à notificação e postar. O método ongoingActivity.apply() modifica o criador de notificações original, adicionando os dados necessários para que o sistema possa mostrar a notificação nas outras plataformas. Depois de aplicar, crie e publique a notificação como de costume.

// This call modifies notificationBuilder to include the ongoing activity data.
ongoingActivity.apply(applicationContext)

// Post the notification.
startForeground(NOTIFICATION_ID, notificationBuilder.build())

Adicionar texto de status dinâmico à tela de início

O código anterior adiciona o ícone tocável ao mostrador do relógio. Para oferecer atualizações ainda mais completas e em tempo real na seção Recentes da tela de início, crie um objeto Status e anexe-o ao seu OngoingActivity . Se você não fornecer um Status personalizado, o sistema usará o texto do conteúdo da notificação (definido com setContentText()).

Para mostrar texto dinâmico, use um Status.Builder. É possível definir uma string de modelo com marcadores de posição e fornecer objetos Status.Part para preencher esses marcadores. O Status.Part pode ser dinâmico, como um cronômetro ou timer .

O exemplo a seguir mostra como criar um status que exibe "Executar por [um cronômetro]":

// Define a template with placeholders for the activity type and the timer.
val statusTemplate = "#type# for #time#"

// Set the start time for a stopwatch.
// Use SystemClock.elapsedRealtime() for time-based parts.
val runStartTime = SystemClock.elapsedRealtime()

val ongoingActivityStatus = Status.Builder()
    // Sets the template string.
    .addTemplate(statusTemplate)
    // Fills the #type# placeholder with a static text part.
    .addPart("type", Status.TextPart("Run"))
    // Fills the #time# placeholder with a stopwatch part.
    .addPart("time", Status.StopwatchPart(runStartTime))
    .build()

Por fim, vincule essa Status ao OngoingActivity chamando setStatus() no OngoingActivity.Builder.

val ongoingActivity =
    OngoingActivity.Builder(applicationContext, NOTIFICATION_ID, notificationBuilder)
        // ...
        // Add the status to the OngoingActivity.
        .setStatus(ongoingActivityStatus)
        .build()

Outras personalizações

Além de Status, você pode personalizar as atividades ou notificações em andamento das seguintes formas. 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
    • CATEGORY_LOCATION_SHARING:compartilhamento temporário de local categoria)
    • CATEGORY_STOPWATCH:cronômetro

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 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 nenhuma delas for definida, 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 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 um OngoingActivity se você quiser reter uma instância em vez de recriá-la.

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:

ongoingActivity.update(context, newStatus)

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

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:

  • 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.

  • 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.

  • Se o app tiver mais de uma atividade MAIN LAUNCHER declarada no manifesto, publique um atalho dinâmico e associe-o à 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.