Applications d'extension et commandes locales

Le SDK de l'API Android Management (AMAPI) permet à une application d'extension spécifiée par l'EMM de communiquer directement avec Android Device Policy (ADP) et d'exécuter des Commands sur l'appareil.

La section Intégrer le SDK AMAPI fournit plus d'informations sur cette bibliothèque et sur la façon de l'ajouter à votre application.

Une fois le SDK intégré, votre application d'extension peut communiquer avec ADP pour :

Émettre une commande

Une application d'extension peut demander l'émission de commandes à l'aide d'ADP. IssueCommandRequest contient l'objet de requête qui contiendra des détails sur la commande à émettre et des paramètres spécifiques.

Effacer les données des applis

L'extrait de code suivant montre comment envoyer une requête pour effacer les données du package :

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

L'exemple précédent montre comment envoyer une requête d'effacement des données d'application pour des packages spécifiés et attendre que la commande ait bien été envoyée. Si l'émission aboutit, un objet Command est renvoyé avec l'état actuel de la commande et l'ID de la commande, qui peut être utilisé ultérieurement pour interroger l'état de toutes les commandes de longue durée.

Effacer les données de l'appareil ou du profil professionnel

Une application associée peut émettre une commande Wipe locale lorsque l'administrateur informatique attribue le rôle COMPANION_APP avec la fonctionnalité supplémentaire LOCAL_DEVICE_WIPE_COMMAND_ALLOWED. Sur les appareils appartenant à l'entreprise, cela rétablit la configuration d'usine de l'appareil. Sur les appareils personnels dotés d'un profil professionnel, cela supprime le profil professionnel. Si l'application ne dispose pas de ce rôle et de cette fonctionnalité supplémentaire, la requête échoue. Pour en savoir plus, consultez Configuration des règles.

Les deux paramètres Wipe sont facultatifs. Si vous ne souhaitez pas spécifier de valeur, omettez le setter du compilateur ou transmettez une collection vide ou une chaîne vide (ne transmettez pas null).

  • Les valeurs WipeDataFlag contrôlent les données qui sont effacées :
    • PRESERVE_RESET_PROTECTION_DATA : conserve les données de protection après rétablissement de la configuration d'usine, qui sont effacées par défaut. S'applique uniquement aux appareils détenus par l'entreprise.
    • WIPE_EXTERNAL_STORAGE : efface également l'espace de stockage externe, comme les cartes SD. S'applique uniquement aux appareils appartenant à l'entreprise.
    • WIPE_ESIMS : supprime toutes les eSIM des appareils appartenant à l'entreprise. Sur les appareils personnels, seules les eSIM gérées sont supprimées (sur Android 16 ou version ultérieure, les eSIM gérées sont toujours supprimées avec le profil professionnel, que cet indicateur soit défini ou non).
  • wipeReason : message court (jusqu'à 200 caractères) qui s'affiche pour l'utilisateur lorsque le profil professionnel est supprimé d'un appareil personnel. Il n'a aucun effet sur les appareils appartenant à l'entreprise.

L'extrait de code suivant montre comment envoyer une requête pour effacer l'appareil ou le profil professionnel :

Kotlin

import android.content.Context
import android.util.Log
...
import com.google.android.managementapi.commands.CommandException
import com.google.android.managementapi.commands.LocalCommandClientFactory
import com.google.android.managementapi.commands.model.IssueCommandRequest
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe.WipeDataFlag

...
  suspend fun issueWipeCommand(
      context: Context,
      wipeDataFlags: List<WipeDataFlag>,
      wipeReason: String,
  ) {
    try {
      val command =
          LocalCommandClientFactory.create(context)
              .issueCommandAwait(createWipeRequest(wipeDataFlags, wipeReason))
      Log.i(TAG, "Successfully issued wipe command: ${command.commandId}")
    } catch (e: CommandException) {
      Log.e(TAG, "Failed to issue wipe command", e)
    }
  }

  fun createWipeRequest(
      wipeDataFlags: List<WipeDataFlag>,
      wipeReason: String,
  ): IssueCommandRequest =
      IssueCommandRequest.builder()
          .setWipe(
              Wipe.builder()
                  .setWipeDataFlags(wipeDataFlags)
                  .setWipeReason(wipeReason)
                  .build()
          )
          .build()
...

Java

import android.content.Context;
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.IssueCommandRequest;
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe;
import com.google.android.managementapi.commands.model.IssueCommandRequest.Wipe.WipeDataFlag;
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 issueWipeCommand(
      Context context,
      ImmutableList<WipeDataFlag> wipeDataFlags,
      String wipeReason) {
    Futures.addCallback(
        LocalCommandClientFactory.create(context)
            .issueCommand(createWipeRequest(wipeDataFlags, wipeReason)),
        new FutureCallback<Command>() {
          @Override
          public void onSuccess(Command result) {
            Log.i(TAG, "Successfully issued wipe command: " + result.getCommandId());
          }

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

  IssueCommandRequest createWipeRequest(
      ImmutableList<WipeDataFlag> wipeDataFlags, String wipeReason) {
    return IssueCommandRequest.builder()
        .setWipe(
            Wipe.builder()
                .setWipeDataFlags(wipeDataFlags)
                .setWipeReason(wipeReason)
                .build())
        .build();
  }
...

Obtenir la commande

Une application d'extension peut interroger l'état des requêtes de commande précédemment émises. Pour récupérer l'état d'une commande, vous aurez besoin de son ID (disponible dans la requête d'émission de la commande). L'extrait de code suivant montre comment envoyer un GetCommandRequest à 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());
  }
  ...

Écouter les rappels de changement d'état de la commande

Une application d'extension peut éventuellement enregistrer un rappel pour recevoir des notifications de changement d'état des commandes de longue durée en procédant comme suit :

  1. Les changements d'état des commandes sont notifiés à CommandListener. Implémentez cette interface dans votre application et indiquez comment gérer les mises à jour d'état reçues.
  2. Étendez NotificationReceiverService et fournissez une instance CommandListener via la méthode getCommandListener.
  3. Configurez la règle d'application pour attribuer le rôle COMPANION_APP à votre application (voir Configuration de la règle).

    import com.google.android.managementapi.commands.CommandListener;
    import com.google.android.managementapi.notification.NotificationReceiverService;
    
    ...
    
    public class SampleCommandService extends NotificationReceiverService {
    
     @Override
     public CommandListener getCommandListener() {
       // return the concrete implementation from previous step
       return ...;
     }
    }
    

Configuration des règles

Pour permettre à l'application associée de communiquer directement avec ADP, l'EMM doit attribuer le rôle COMPANION_APP à l'application à l'aide du champ roles dans la règle d'application :

 "applications": [{
   "packageName": "com.amapi.extensibility.demo",
   "installType": "FORCE_INSTALLED",
   "roles": [
     { "roleType": "COMPANION_APP" }
   ]
 }]

Pour autoriser l'application associée à émettre une commande Wipe locale, définissez extraCapabilities.localDeviceWipeCommand sur LOCAL_DEVICE_WIPE_COMMAND_ALLOWED :

 "applications": [{
   "packageName": "com.amapi.extensibility.demo",
   "installType": "FORCE_INSTALLED",
   "roles": [
     {
       "roleType": "COMPANION_APP",
       "extraCapabilities": {
         "localDeviceWipeCommand": "LOCAL_DEVICE_WIPE_COMMAND_ALLOWED"
       }
     }
   ]
 }]

Pour connaître les autres options disponibles, consultez Provisionner l'appareil avec des règles de rôles d'application.

Le service qui écoute les changements d'état des commandes est automatiquement détecté par ADP à l'aide des métadonnées du fichier manifeste de l'application. Il n'est plus nécessaire de le spécifier dans la règle en tant que notificationReceiver. Pour savoir comment préparer votre application et votre fichier manifeste, consultez Gérer les rôles d'application.

Tests

Tests unitaires

LocalCommandClient est une interface qui permet de fournir une implémentation testable.

Tests d'intégration

Les informations suivantes sont nécessaires pour effectuer des tests avec ADP :

  1. Nom du package de l'application d'extension.
  2. Hachage SHA-256 encodé en base64 de la signature associée au package d'application.
  3. Facultatif, si vous testez le rappel : nom complet du service à partir du service récemment introduit pour prendre en charge le rappel. (nom complet de CommandService dans l'exemple).