Criar blocos personalizados de Configurações rápidas para o app

As Configurações rápidas são blocos mostrados no painel Configurações rápidas que representam ações em que os usuários podem tocar para concluir tarefas recorrentes rapidamente. Seu app pode fornecer um bloco personalizado aos usuários com a classe TileService e usar um objeto Tile para monitorar o estado do bloco. Por exemplo, você pode criar um bloco que permite aos usuários ativar ou desativar uma VPN fornecida pelo app.

Painel de configurações rápidas com o bloco da VPN
  ativado e desativado
Figura 1. Painel "Configurações rápidas" com o bloco de VPN ativado e desativado.

Decida quando criar um bloco

Recomendamos a criação de blocos para funcionalidades específicas que você espera que os usuários acessem com frequência ou que precisem de acesso rápido (ou ambos). Os blocos mais eficazes são aqueles que correspondem a essas duas qualidades, oferecendo acesso rápido às ações realizadas com frequência.

Por exemplo, você pode criar um bloco para um app fitness que permite aos usuários iniciar rapidamente uma sessão de treino. No entanto, não recomendamos criar um bloco para o mesmo app que permita aos usuários revisar todo o histórico de treino.

Casos de uso de blocos do app fitness
Figura 2. Exemplos de blocos recomendados e não recomendados para um app fitness.

Para ajudar a melhorar a detecção e a facilidade de uso do Bloco, recomendamos evitar algumas práticas:

  • Evite usar blocos para iniciar um app. Em vez disso, use um atalho de app ou uma tela de início padrão.

  • Evite usar blocos para ações únicas do usuário. Use um atalho de app ou uma notificação.

  • Evite criar muitos blocos. Recomendamos no máximo dois por app. Use um atalho.

  • Evite usar blocos que mostram informações, mas não são interativos para os usuários. Use uma notificação ou um widget.

Criar seu bloco

Para criar um bloco, primeiro é necessário criar um ícone de bloco apropriado e, em seguida, criar e declarar o TileService no arquivo de manifesto do app.

O exemplo de Configurações rápidas mostra como criar e gerenciar um bloco.

Crie seu ícone personalizado

É necessário fornecer um ícone personalizado, que será exibido no bloco do painel "Configurações rápidas". Você vai adicionar esse ícone ao declarar o TileService, descrito na próxima seção. O ícone precisa ter um branco sólido com um plano de fundo transparente, medir 24 x 24 dp e estar na forma de um VectorDrawable.

Exemplo de um drawable vetorial
Figura 3. Exemplo de um drawable vetorial.

Crie um ícone que indique visualmente a finalidade do seu bloco. Isso ajuda os usuários a identificar facilmente se o bloco atende às necessidades deles. Por exemplo, você pode criar um ícone de cronômetro para um bloco de um app fitness que permita aos usuários iniciar uma sessão de treino.

Criar e declarar o TileService

Crie um serviço para o bloco que estenda a classe TileService.

Kotlin

class MyQSTileService: TileService() {

  // Called when the user adds your tile.
  override fun onTileAdded() {
    super.onTileAdded()
  }
  // Called when your app can update your tile.
  override fun onStartListening() {
    super.onStartListening()
  }

  // Called when your app can no longer update your tile.
  override fun onStopListening() {
    super.onStopListening()
  }

  // Called when the user taps on your tile in an active or inactive state.
  override fun onClick() {
    super.onClick()
  }
  // Called when the user removes your tile.
  override fun onTileRemoved() {
    super.onTileRemoved()
  }
}

Java

public class MyQSTileService extends TileService {

  // Called when the user adds your tile.
  @Override
  public void onTileAdded() {
    super.onTileAdded();
  }

  // Called when your app can update your tile.
  @Override
  public void onStartListening() {
    super.onStartListening();
  }

  // Called when your app can no longer update your tile.
  @Override
  public void onStopListening() {
    super.onStopListening();
  }

  // Called when the user taps on your tile in an active or inactive state.
  @Override
  public void onClick() {
    super.onClick();
  }

  // Called when the user removes your tile.
  @Override
  public void onTileRemoved() {
    super.onTileRemoved();
  }
}

Declare o TileService no arquivo de manifesto do app. Adicione o nome e a etiqueta do TileService, o ícone personalizado criado na seção anterior e a permissão adequada.

 <service
     android:name=".MyQSTileService"
     android:exported="true"
     android:label="@string/my_default_tile_label"  // 18-character limit.
     android:icon="@drawable/my_default_icon_label"
     android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
     <intent-filter>
         <action android:name="android.service.quicksettings.action.QS_TILE" />
     </intent-filter>
 </service>

Gerenciar o TileService

Depois de criar e declarar o TileService no manifesto do app, é necessário gerenciar o estado dele.

TileService é um serviço vinculado. O TileService é vinculado quando solicitado pelo app ou quando o sistema precisa se comunicar com ele. Um ciclo de vida de serviços vinculados típico contém os quatro métodos de callback a seguir: onCreate(), onBind(), onUnbind() e onDestroy(). Esses métodos são invocados pelo sistema sempre que o serviço entra em uma nova fase do ciclo de vida.

Visão geral do ciclo de vida do TileService

Além dos callbacks que controlam o ciclo de vida do serviço vinculado, é preciso implementar outros métodos específicos para o ciclo de vida do TileService. Esses métodos podem ser chamados fora de onCreate() e onDestroy(), porque os métodos de ciclo de vida de Service e TileService são chamados em duas linhas de execução assíncronas separadas.

O ciclo de vida da TileService contém os métodos abaixo, que são invocados pelo sistema sempre que a TileService entra em uma nova fase do ciclo de vida:

  • onTileAdded(): esse método é chamado apenas quando o usuário adiciona o bloco pela primeira vez e se ele remove e adiciona o bloco novamente. Esse é o melhor momento para fazer qualquer inicialização única. No entanto, isso pode não atender a toda a inicialização necessária.

  • onStartListening() e onStopListening(): esses métodos são chamados sempre que o app atualiza o bloco e são chamados com frequência. O TileService permanece vinculado entre onStartListening() e onStopListening(), permitindo que o app modifique o bloco e envie atualizações.

  • onTileRemoved(): esse método só será chamado se o usuário remover o bloco.

Selecione um modo de áudio

Seu TileService escuta no modo ativo ou não ativo. Recomendamos usar o modo ativo, que precisa ser declarado no manifesto do app. Caso contrário, TileService é o modo padrão e não precisa ser declarado.

Não presuma que TileService ficará fora do par de métodos onStartListening() e onStopListening().

Use o modo ativo para uma TileService que detecta e monitora o estado no próprio processo. Um TileService no modo ativo é vinculado a onTileAdded(), onTileRemoved(), eventos de toque e, quando solicitado pelo processo do app.

Recomendamos o modo ativo se a TileService for notificada quando o estado do bloco precisar ser atualizado pelo próprio processo. Os blocos ativos limitam a pressão no sistema porque não precisam ser vinculados toda vez que o painel "Configurações rápidas" fica visível para o usuário.

O método estático TileService.requestListeningState() pode ser chamado para solicitar o início do estado de detecção e receber um callback para onStartListening().

Você pode declarar o modo ativo adicionando o META_DATA_ACTIVE_TILE ao arquivo de manifesto do app.

<service ...>
    <meta-data android:name="android.service.quicksettings.ACTIVE_TILE"
         android:value="true" />
    ...
</service>

Modo inativo

O modo não ativo é o modo padrão. Um TileService fica no modo não ativo se está vinculado sempre que o bloco está visível para o usuário. Isso significa que seu TileService pode ser criado e vinculado novamente em momentos fora do controle dele. Ele também pode ser desvinculado e destruído quando o usuário não estiver visualizando o bloco.

Seu app recebe um callback para onStartListening() depois que o usuário abre o painel "Configurações rápidas". É possível atualizar seu objeto Tile quantas vezes quiser entre onStartListening() e onStopListening().

Não é necessário declarar o modo não ativo. Basta não adicionar o META_DATA_ACTIVE_TILE ao arquivo de manifesto do app.

Visão geral dos estados de bloco

Depois que um usuário adiciona um bloco, ele sempre aparece em um dos estados abaixo.

  • STATE_ACTIVE: indica o estado ativado ou ativado. O usuário pode interagir com o bloco enquanto está nesse estado.

    Por exemplo, para um bloco de um app fitness que permite que os usuários iniciem uma sessão de treino cronometrado, STATE_ACTIVE significa que o usuário iniciou uma sessão de treino e o timer está em execução.

  • STATE_INACTIVE: indica um estado desativado ou pausado. O usuário pode interagir com o bloco enquanto está nesse estado.

    Para usar o exemplo de bloco do app fitness novamente, um bloco em STATE_INACTIVE significa que o usuário não iniciou uma sessão de treino, mas pode fazer isso se quisesse.

  • STATE_UNAVAILABLE: indica um estado temporariamente indisponível. O usuário não pode interagir com o Bloco nesse estado.

    Por exemplo, um bloco em STATE_UNAVAILABLE significa que ele não está disponível para o usuário por algum motivo.

O sistema define apenas o estado inicial do objeto Tile. Você define o estado do objeto Tile durante o restante do ciclo de vida.

O sistema pode colorir o ícone e o plano de fundo do bloco para refletir o estado do objeto Tile. Os objetos Tile definidos como STATE_ACTIVE são os mais escuros, com STATE_INACTIVE e STATE_UNAVAILABLE cada vez mais claros. A tonalidade exata é específica para o fabricante e a versão.

Bloco da VPN colorido para refletir os estados dos objetos
Figura 4. Exemplos de um bloco colorido para refletir o estado do bloco (ativo, inativo e indisponível, respectivamente).

Atualizar seu bloco

Você pode atualizar seu bloco depois de receber um callback para onStartListening(). Dependendo do modo do bloco, ele pode ser atualizado pelo menos uma vez até receber um callback para onStopListening().

No modo ativo, você pode atualizar seu bloco exatamente uma vez antes de receber um callback para onStopListening(). No modo não ativo, você pode atualizar seu bloco quantas vezes quiser entre onStartListening() e onStopListening().

É possível extrair o objeto Tile chamando getQsTile(). Para atualizar campos específicos do objeto Tile, chame os seguintes métodos:

Chame updateTile() para atualizar o bloco quando terminar de definir os campos do objeto Tile com os valores corretos. Isso fará com que o sistema analise os dados de bloco atualizados e atualize a interface.

Kotlin

data class StateModel(val enabled: Boolean, val label: String, val icon: Icon)

override fun onStartListening() {
  super.onStartListening()
  val state = getStateFromService()
  qsTile.label = state.label
  qsTile.contentDescription = tile.label
  qsTile.state = if (state.enabled) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
  qsTile.icon = state.icon
  qsTile.updateTile()
}

Java

public class StateModel {
  final boolean enabled;
  final String label;
  final Icon icon;

  public StateModel(boolean e, String l, Icon i) {
    enabled = e;
    label = l;
    icon = i;
  }
}

@Override
public void onStartListening() {
  super.onStartListening();
  StateModel state = getStateFromService();
  Tile tile = getQsTile();
  tile.setLabel(state.label);
  tile.setContentDescription(state.label);
  tile.setState(state.enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
  tile.setIcon(state.icon);
  tile.updateTile();
}

Processar toques

Os usuários podem tocar no seu bloco para acionar uma ação caso ele esteja em STATE_ACTIVE ou STATE_INACTIVE. Em seguida, o sistema invoca o callback onClick() do app.

Depois que o app receber um callback para onClick(), ele poderá iniciar uma caixa de diálogo ou atividade, acionar o trabalho em segundo plano ou mudar o estado do bloco.

Kotlin

var clicks = 0
override fun onClick() {
  super.onClick()
  counter++
  qsTile.state = if (counter % 2 == 0) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
  qsTile.label = "Clicked $counter times"
  qsTile.contentDescription = qsTile.label
  qsTile.updateTile()
}

Java

int clicks = 0;

@Override
public void onClick() {
  super.onClick();
  counter++;
  Tile tile = getQsTile();
  tile.setState((counter % 2 == 0) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
  tile.setLabel("Clicked " + counter + " times");
  tile.setContentDescription(tile.getLabel());
  tile.updateTile();
}

Iniciar uma caixa de diálogo

showDialog() recolhe o painel "Configurações rápidas" e mostra uma caixa de diálogo. Use uma caixa de diálogo para adicionar contexto à sua ação se ela exigir mais entradas ou consentimento do usuário.

Iniciar uma atividade

startActivityAndCollapse() inicia uma atividade ao recolher o painel. As atividades são úteis quando há informações mais detalhadas para mostrar do que em uma caixa de diálogo ou se a ação é altamente interativa.

Caso seu app exija uma interação significativa do usuário, ele deve iniciar uma atividade somente como último recurso. Em vez disso, use uma caixa de diálogo ou um botão de alternância.

Ao tocar e manter pressionado um bloco, a tela Informações do app é exibida para o usuário. Para substituir esse comportamento e iniciar uma atividade para definir preferências, adicione um <intent-filter> a uma das atividades com ACTION_QS_TILE_PREFERENCES.

Marcar seu bloco como alternável

Recomendamos marcar o bloco como alternável se ele funcionar principalmente como uma chave de dois estados, que é o comportamento mais comum dos blocos. Isso ajuda a fornecer informações sobre o comportamento do bloco ao sistema operacional e melhorar a acessibilidade geral.

Defina os metadados de TOGGLEABLE_TILE como true para marcar seu bloco como alternável.

<service ...>
  <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
    android:value="true" />
</service>

Realizar ações seguras apenas em dispositivos bloqueados com segurança

Em dispositivos bloqueados, o bloco pode aparecer na parte de cima da tela de bloqueio. Se o bloco contiver informações sensíveis, confira o valor de isSecure() para determinar se o dispositivo está em um estado seguro. Nesse caso, o TileService vai mudar o comportamento de acordo com isso.

Se for seguro realizar a ação de blocos com o dispositivo bloqueado, use startActivity() para iniciar uma atividade na parte de cima da tela de bloqueio.

Se a ação de bloco não for segura, use unlockAndRun() para solicitar que o usuário desbloqueie o dispositivo. Se bem-sucedido, o sistema vai executar o objeto Runnable transmitido para esse método.

Solicitar que o usuário adicione o bloco

Para adicionar seu bloco manualmente, os usuários precisam seguir várias etapas:

  1. Deslize para baixo para abrir o painel "Configurações rápidas".
  2. Toque no botão de edição.
  3. Percorrer todos os blocos do dispositivo até que o bloco seja localizado.
  4. Mantenha seu bloco pressionado e arraste-o para a lista de blocos ativos.

O usuário também pode mover ou remover seu bloco a qualquer momento.

No Android 13 e versões mais recentes, você pode usar o método requestAddTileService() para facilitar muito a adição do bloco a um dispositivo. Esse método faz com que os usuários solicitem a adição rápida do seu bloco diretamente ao painel Configurações rápidas. O prompt inclui o nome do aplicativo, o rótulo fornecido e o ícone.

Solicitação da API Placement Configurações rápidas
Figura 5. Solicitação da API Placement Configurações rápidas.
public void requestAddTileService (
  ComponentName tileServiceComponentName,
  CharSequence tileLabel,
  Icon icon,
  Executor resultExecutor,
  Consumer<Integer> resultCallback
)

O callback contém informações sobre se o bloco foi adicionado, não adicionado, se ele já estava lá ou se ocorreu algum erro.

Tenha critério ao decidir quando e com que frequência enviar solicitações aos usuários. Recomendamos chamar requestAddTileService() apenas no contexto, por exemplo, quando o usuário interage pela primeira vez com um recurso facilitado pelo seu Bloco.

O sistema pode optar por interromper o processamento de solicitações para uma determinada ComponentName se ela tiver sido negada pelo usuário várias vezes antes. O usuário é determinado a partir do Context usado para extrair esse serviço. Ele precisa corresponder ao usuário atual.