Como integrar com o SDK da AMAPI

O SDK da AMAPI permite que um app de extensão especificado pelo EMM se comunique diretamente com o Android Device Policy. Atualmente, ele inclui suporte para a execução local de Commands e apenas o comando ClearAppData. Siga estas etapas para fazer a integração com o SDK:

  1. Adicionar biblioteca ao app de extensões
  2. Use as APIs fornecidas para emitir comandos conforme necessário.
  3. Adicione o elemento de consultas se o SDK de destino for maior ou igual a 30.
  4. Opcionalmente, forneça implementação do serviço para detectar callbacks de mudança do status do comando (em inglês).
  5. Provisione o dispositivo com a política de extensibilidade.

Pré-requisitos

  • Confira se a minSdkVersion do app da extensão está definida como pelo menos o nível 21 da API.

Adicionar uma biblioteca ao aplicativo de extensão

No arquivo build.gradle de nível superior, adicione o repositório Maven do Google, que contém a biblioteca do SDK aos módulos relevantes e adicione a dependência à biblioteca:

repositories {
  ...
  google()
}

Em seguida, adicione a biblioteca ao bloco de dependências do módulo:

dependencies {
  implementation 'com.google.android.libraries.enterprise.amapi:amapi:1.0.0'
}

Enviar solicitações para o Android Device Policy

Agora deve ser possível enviar solicitações ao ADP. As solicitações a seguir são aceitas.

Emitir comando

O app da extensão pode solicitar que comandos sejam emitidos usando o ADP. IssueCommandRequest contém o objeto de solicitação com detalhes sobre o comando a ser emitido e parâmetros específicos. Confira mais informações sobre ele no Javadoc.

O snippet a seguir mostra como emitir uma solicitação para limpar os dados do pacote:

import android.util.Log;
...
import com.google.android.managementapi.commands.LocalCommandClientFactory
import com.google.android.managementapi.commands.model.Command
import com.google.android.managementapi.commands.model.GetCommandRequest
import com.google.android.managementapi.commands.model.IssueCommandRequest
import com.google.android.managementapi.commands.model.IssueCommandRequest.ClearAppsData
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;

...
  void issueClearAppDataCommand(ImmutableList<String> packageNames) {
    Futures.addCallback(
        LocalCommandClientFactory.create(getContext())
            .issueCommand(createClearAppRequest(packageNames)),
        new FutureCallback<Command>() {
          @Override
          public void onSuccess(Command result) {
            // Process the returned command result here
            Log.i(TAG, "Successfully issued command");
          }

          @Override
          public void onFailure(Throwable t) {
            Log.e(TAG, "Failed to issue command", t);
          }
        },
        MoreExecutors.directExecutor());
  }

  IssueCommandRequest createClearAppRequest(ImmutableList<String> packageNames) {
    return IssueCommandRequest.builder()
        .setClearAppsData(
            ClearAppsData.builder()
                .setPackageNames(packageNames)
                .build()
        )
        .build();
  }
...

O exemplo acima mostra a emissão de uma solicitação de dados do app claros para pacotes especificados e a espera até que o comando seja emitido. Se emitido com sucesso, um objeto Command será retornado com o status atual do comando e o ID do comando, que pode ser usado posteriormente para consultar o status de qualquer comando de longa duração.

Receber comando

O app de extensão também pode consultar o status de solicitações de comando emitidas anteriormente. Para recuperar o status de um comando, você vai precisar do ID do comando (disponível na solicitação de problema). O snippet a seguir mostra o envio de uma solicitação GetCommand para o ADP.

import android.util.Log;
...
import com.google.android.managementapi.commands.LocalCommandClientFactory;
...
import com.google.android.managementapi.commands.model.GetCommandRequest;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;

...
  void getCommand(String commandId) {
    Futures.addCallback(
        LocalCommandClientFactory.create(getApplication())
            .getCommand(GetCommandRequest.builder().setCommandId(commandId).build()),
        new FutureCallback<Command>() {
          @Override
          public void onSuccess(Command result) {
            // Process the returned command result here
            Log.i(Constants.TAG, "Successfully issued command");
          }

          @Override
          public void onFailure(Throwable t) {
            Log.e(Constants.TAG, "Failed to issue command", t);
          }
        },
        MoreExecutors.directExecutor());
  }
  ...

Adicionando elemento de consultas

Se o app for direcionado ao SDK 30 e versões mais recentes, o elemento de consulta é necessário no manifesto para especificar que ele vai interagir com o ADP.

<queries>
    <package android:name="com.google.android.apps.work.clouddpc" />
</queries>

Consulte Filtragem da visibilidade de pacotes no Android para mais informações.

Detectar callbacks de mudança de status do comando

  1. As mudanças de status do comando são notificadas ao CommandListener, implementa essa interface no app e fornece a implementação sobre como lidar com as atualizações de status recebidas.
  2. Estender NotificationReceiverService e fornecer a instância CommandListener.
  3. Especifica o nome da classe do NotificationReceiverService estendido na política da API Android Management. Consulte "Configuração da política".

    import com.google.android.managementapi.commands.CommandListener;
    import com.google.android.managementapi.notification.NotificationReceiverService;
    
    ...
    
    public class SampleCommandService extends NotificationReceiverService {
    
      @Override
      protected void setupInjection() {
        // (Optional) If using DI and needs initialisation then use this method.
      }
    
      @Override
      public CommandListener getCommandListener() {
        // return the concrete implementation from previous step
        return ...;
      }
    }
    
  4. Adicione o serviço ao AndroidManifest.xml e verifique se ele foi exportado.

    <service
     android:name = ".notification.SampleCommandService"
     android:exported = "true" />
    

Configuração de política

 "applications": [{
   "packageName": "com.amapi.extensibility.demo",
   ...
   "extensionConfig": {
     "signingKeyFingerprintsSha256": [
       // Include signing key of extension app
     ],
     // Optional if callback is implemented
     "notificationReceiver": "com.amapi.extensibility.demo.notification.SampleCommandService"
   }
 }]

Teste

Teste de unidade

O LocalCommandClient é uma interface e, portanto, o teste permite facilmente uma implementação testável.

Teste de integração

As informações a seguir serão necessárias para fazer testes com o Android Device Policy:

  1. Nome do pacote do app de extensão.
  2. O hash SHA-256 da assinatura associada ao pacote do app, codificado em hexadecimal.
  3. Opcionalmente, se for testar o callback: nome totalmente qualificado do serviço do serviço recém-introduzido para oferecer suporte ao callback. (Nome totalmente qualificado de CommandService no exemplo).