Integración con el SDK de AMAPI

El SDK de AMAPI permite que una app de extensión especificada por EMM se comunique directamente con Android Device Policy. Actualmente, incluye compatibilidad con la ejecución local de Commands y solo el comando ClearAppData. Se deben seguir estos pasos para integrarlo al SDK:

  1. Agrega la biblioteca a la extensión de app.
  2. Usa las APIs proporcionadas para emitir comandos según sea necesario.
  3. Agrega el elemento de consultas si el SDK de destino es >= 30.
  4. De manera opcional, puedes proporcionar la implementación del servicio para escuchar las devoluciones de llamada de cambio de estado del comando.
  5. Aprovisiona el dispositivo con la política de extensibilidad.

Requisitos previos

  • Asegúrate de que el minSdkVersion de la app de la extensión esté configurado en al menos el nivel de API 21.

Agrega la biblioteca a la app de extensiones

En el archivo build.gradle de nivel superior, agrega el repositorio Maven de Google que contiene la biblioteca del SDK a los módulos relevantes y agrega la dependencia a la biblioteca:

repositories {
  ...
  google()
}

Luego, agrega la biblioteca al bloque de dependencias de tu módulo:

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

Envía solicitudes a Android Device Policy

Ahora debería ser posible enviar solicitudes a ADP. Se admiten las siguientes solicitudes.

Ejecutar comando

La app de extensión puede solicitar que se emitan los comandos con ADP. IssueCommandRequest contiene el objeto de la solicitud que contendrá detalles sobre el comando que se emitirá y los parámetros específicos. Puedes encontrar más información al respecto en el Javadoc.

En el siguiente fragmento, se muestra cómo emitir una solicitud para borrar los datos del paquete:

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

En el ejemplo anterior, se muestra cómo se emite una solicitud para borrar los datos de una app para paquetes específicos y cómo se espera hasta que el comando se emita de forma correcta. Si se emite correctamente, se mostrará un objeto Command con el estado actual del comando y el ID del comando, que se puede usar más adelante para consultar el estado de cualquier comando de larga duración.

Obtener comando

La app de extensión también puede consultar el estado de las solicitudes de comandos emitidas con anterioridad. Para recuperar el estado de un comando, necesitarás el ID del comando (disponible en la solicitud del comando de emisión). En el siguiente fragmento, se muestra cómo enviar una solicitud GetCommand a 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());
  }
  ...

Agrega el elemento de consultas

Si tu app se orienta al SDK 30 y versiones posteriores, se necesita el elemento de consultas en el manifiesto para especificar que interactuará con ADP.

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

Consulta Filtrado de visibilidad de paquetes en Android para obtener más información.

Escucha las devoluciones de llamada de cambio de estado del comando

  1. Los cambios en el estado del comando se notifican a CommandListener. Implementa esta interfaz en tu app y proporciona una implementación para controlar las actualizaciones de estado recibidas.
  2. Extiende NotificationReceiverService y proporciona una instancia de CommandListener.
  3. Especifica el nombre de clase de la extensión NotificationReceiverService en la política de la API de Android Management (consulta la configuración de la 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. Agrega el servicio a tu archivo AndroidManifest.xml y asegúrate de que se haya exportado.

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

Configuración de la 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"
   }
 }]

Prueba

Pruebas de unidades

LocalCommandClient es una interfaz y, por lo tanto, las pruebas permiten proporcionar fácilmente una implementación que se pueda probar.

Pruebas de integración

La siguiente información será necesaria para realizar pruebas con Android Device Policy:

  1. Es el nombre del paquete de la app de la extensión.
  2. El hash SHA-256 con codificación hexadecimal de la firma asociada con el paquete de la app.
  3. De manera opcional, si se prueba la devolución de llamada, es el nombre completamente calificado del servicio desde el servicio recién presentado para admitir la devolución de llamada. (Nombre completo de CommandService en el ejemplo).