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

As "Configurações rápidas" são blocos exibidos no painel "Configurações rápidas". que representam ações, que os usuários podem tocar para concluir rapidamente tarefas recorrentes. O app pode fornecer um Bloco personalizado aos usuários pela classe TileService e usar um objeto Tile para acompanhar o estado do Bloco. Por exemplo: você pode criar um bloco que permita aos usuários ativar uma VPN fornecida pelo seu app ou

Painel "Configurações rápidas" com o bloco "VPN" ativado
  ligado e desligado
Figura 1. Painel "Configurações rápidas" com o bloco "VPN" ativado sempre que necessário.

Decidir quando criar um bloco

Recomendamos a criação de blocos para funcionalidades específicas esperadas dos usuários 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 a ações realizadas com frequência.

Por exemplo, você pode criar um Bloco para um app de fitness que permita 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 analisar todo o histórico de treinos.

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

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

  • Evite usar blocos para iniciar um app. Use um atalho de app ou um iniciador 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 duas por app. Use um atalho do app.

  • 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, é necessário primeiro criar um ícone de bloco apropriado, depois criar e declarar o TileService no arquivo de manifesto do app.

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

Crie seu ícone personalizado

Você precisa fornecer um ícone personalizado, que aparece no bloco no painel "Configurações rápidas". Você vai adicionar esse ícone ao declarar o TileService, descritos na próxima seção. O ícone deve ser branco sólido com fundo transparente, medir 24 x 24 dp, na forma de VectorDrawable

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

Crie um ícone que indique visualmente o propósito do bloco. Isso ajuda os usuários a identificar facilmente se o bloco atende às necessidades deles. Por exemplo, é possível criar um ícone de um cronômetro em um bloco de um app fitness que permite 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.

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()
  }
}
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. Adicionar o nome e o rótulo do TileService, o ícone personalizado que você criou 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, você pode para gerenciar o estado.

TileService é um serviço vinculado. O TileService é vinculado quando solicitado pelo app ou se o sistema precisar se comunicar com ele. Um ciclo de vida de serviço vinculado 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 cada vez 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 da TileService. Esses métodos podem ser chamados fora de onCreate() e onDestroy() porque os métodos de ciclo de vida Service e TileService são chamados em dois encadeamentos assíncronos separados.

O ciclo de vida da TileService contém os seguintes métodos, que são invocados pelo sistema sempre que o TileService entrar 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 o usuário remover e adicionar o bloco novamente. Esse é o melhor momento para fazer qualquer inicialização única. No entanto, isso pode que não atendam 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 é chamado apenas se o usuário remover o bloco.

Selecionar um modo de escuta

Seu TileService detecta no modo ativo ou não ativo. Recomendamos usando 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 suponha que seu TileService ficará fora de onStartListening() e onStopListening() de métodos.

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

Recomendamos o modo ativo se o TileService for notificado quando o estado do bloco deve ser atualizada por um processo próprio. Os blocos ativos limitam a tensão no sistema porque não precisam ser vinculados sempre que o painel de 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 escuta e receber um callback para onStartListening().

Para declarar o modo ativo, adicione 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 não ativo

O modo não ativo é o modo padrão. Um TileService está no modo não ativo se for vinculado sempre que o bloco estiver visível para o usuário. Isso significa que seus TileService pode ser criado e vinculado novamente às vezes além do controle dele. Ele também pode ser desvinculado e destruído quando o usuário não está visualizando o bloco.

O app recebe uma chamada de retorno para onStartListening() depois que o usuário abre a Configurações rápidas. É possível atualizar o objeto Tile quantas vezes você 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 seu bloco, ele sempre existe em um dos seguintes estados.

  • STATE_ACTIVE: indica um estado ativado ou ativo. O usuário pode interagir com o Bloco nesse estado.

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

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

    Para usar o exemplo de bloco de app de condicionamento físico novamente, um bloco em STATE_INACTIVE significaria que o usuário não iniciou uma sessão de treino, mas poderia fazer isso se quisesse.

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

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

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

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 estão cada vez mais leves. A matiz exata é específica para o fabricante e a versão.

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

Atualizar o Bloco

É possível atualizar o Bloco assim que você 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, é possível atualizar seu bloco exatamente uma vez antes de receber para onStopListening(). No modo não ativo, você pode atualizar o bloco quantas vezes quiser entre onStartListening() e onStopListening().

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

Você precisa chamar updateTile() para atualizar o bloco ao terminar de definir o campos do objeto Tile nos valores corretos. Isso fará com que o sistema analisar os dados de bloco atualizados e atualizar a interface.

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()
}
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 bloco para iniciar uma ação se ele estiver STATE_ACTIVE ou STATE_INACTIVE. Em seguida, o sistema invoca Callback onClick().

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

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

Abrir uma caixa de diálogo

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

Iniciar uma atividade

startActivityAndCollapse() inicia uma atividade ao recolher o do painel de controle. As atividades são úteis quando há informações mais detalhadas para exibir do que em uma caixa de diálogo ou caso sua ação seja muito interativa.

Se seu aplicativo exigir uma interação significativa do usuário, ele deverá iniciar uma atividades apenas como último recurso. Em vez disso, use uma caixa de diálogo ou uma chave.

Tocar e segurar um bloco faz com que a tela Informações do app seja mostrada ao usuário. Para substituir esse comportamento e iniciar uma atividade para definir preferências, adicione um <intent-filter> a uma das suas atividades com ACTION_QS_TILE_PREFERENCES.

A partir do nível 28 da API do Android, a PendingIntent precisa têm o Intent.FLAG_ACTIVITY_NEW_TASK:

if (Build.VERSION.SDK_INT >= 28) {
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}

Como alternativa, adicione a flag no AndroidManifest.xml no Activity.

Marcar o bloco como alternável

Recomendamos marcar seu bloco como alternável se ele funcionar principalmente como um interruptor de dois estados (que é o comportamento mais comum dos blocos). Isso ajuda fornecem informações sobre o comportamento do bloco para o sistema operacional e e melhorar a acessibilidade geral.

Defina os metadados TOGGLEABLE_TILE como true para marcar o Bloco como alternável.

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

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

Seu bloco pode aparecer na parte de cima da tela de bloqueio em dispositivos bloqueados. Se o bloco contiver informações sensíveis, verifique o valor de isSecure() para determina se o dispositivo está em um estado seguro e se o TileService precisa mudar seu comportamento de acordo.

Se a ação do bloco puder ser realizada com segurança enquanto o dispositivo estiver bloqueado, use startActivity() para iniciar uma atividade na parte de cima da tela de bloqueio.

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

Pedir que o usuário adicione seu 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. Role por todos os blocos no dispositivo até encontrar o seu.
  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.

A partir do Android 13, é possível usar o método requestAddTileService() para facilitar a adição do bloco a um dispositivo. Esse método solicita que os usuários adicionem rapidamente seu bloco diretamente ao Quick Configurações. O comando inclui o nome do aplicativo, o rótulo fornecido e um ícone.

Prompt da API de posicionamento de Configurações rápidas
Figura 5. Solicitação da API Quick Settings Placement.
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 ou não, se ele já estava lá ou se ocorreu algum erro.

Use sua discrição ao decidir quando e com que frequência pedir algo aos usuários. Qa recomendamos chamar requestAddTileService() somente no contexto, como 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 um determinado ComponentName se tiver sido negada pelo usuário muitas vezes antes. O usuário é determinado pelo Context usado para recuperar esse serviço. Ele precisa corresponder ao usuário atual.