Ce document doit être votre guide principal pour l'utilisation du SDK AMAPI afin de recevoir des signaux de confiance de l'appareil.
Le SDK AMAPI permet à votre application d'accéder aux signaux de confiance des appareils à partir de l'application Règles relatives aux appareils Android (ADP). Votre application peut ensuite utiliser ces signaux pour calculer l'état de confiance de l'appareil et appliquer la logique métier de votre choix.
Prérequis
- Votre application doit être autorisée à lire les signaux de confiance de l'appareil :
- Applications associées sur les appareils gérés par l'AMAPI : les applications auxquelles le rôle
COMPANION_APPest attribué dans la règle peuvent accéder aux signaux de confiance de l'appareil sans figurer sur la liste d'autorisation sur les appareils entièrement gérés ou depuis un profil professionnel (avec un accès limité aux signaux du profil professionnel). Pour en savoir plus, consultez Gérer les rôles d'application, Configuration des règles et Accéder aux signaux depuis une application associée. - Toutes les autres applications et tous les autres environnements : pour accéder aux signaux de confiance de l'appareil depuis un profil personnel, sur un appareil non géré, sur un appareil géré par un DPC personnalisé ou sans le rôle
COMPANION_APP, votre application doit figurer sur la liste d'autorisation. Pour savoir comment envoyer une demande, consultez la page Accès aux signaux de confiance des appareils.
- Applications associées sur les appareils gérés par l'AMAPI : les applications auxquelles le rôle
- Android Enterprise recommande d'intégrer la suite d'API Play Integrity à votre application cliente et de vous référer au résultat avant de lire les signaux de confiance de l'appareil et de vous y fier. Les appareils qui échouent aux vérifications de l'API Play Integrity ne doivent pas être considérés comme fiables, pas plus que les signaux provenant de l'appareil utilisé pour déterminer la stratégie de confiance. Pour en savoir plus, consultez la documentation sur Play Integrity.
Intégrer le SDK AMAPI à votre application
Pour accéder aux signaux de confiance de l'appareil, votre application doit s'intégrer au SDK AMAPI. Pour en savoir plus sur cette bibliothèque et sur la façon de l'ajouter à votre application, consultez le guide d'intégration du SDK AMAPI.
Ajouter les autorisations requises
Certains des signaux renvoyés par l'API Device Trust from Android Enterprise nécessitent que l'application déclare la même autorisation que celle requise pour accéder à ces informations, en particulier :
| Signal | Autorisation requise |
|---|---|
| État du réseau | ACCESS_NETWORK_STATE |
| Complexité du verrouillage de l'écran | REQUEST_PASSWORD_COMPLEXITY |
Si ces autorisations ne sont pas incluses dans le fichier AndroidManifest.xml de l'application, l'API Device Trust d'Android Enterprise renvoie PERMISSION_ISSUE dans les métadonnées du signal associé :
internalDeviceSettings=DeviceSettings{
screenLockComplexity=COMPLEXITY_UNSPECIFIED,
internalScreenLockComplexityMetadata=Metadata{
dataIssues=[
DataIssue{
issueType=PERMISSION_ISSUE,
issueLevel=WARNING,
issueDetails=IssueDetailsCase{none}
}
]
},
Pour en savoir plus, consultez la liste des signaux de confiance des appareils disponibles.
Procédure d'accès aux signaux de confiance des appareils
Les applications de la liste d'autorisation (y compris celles exécutées dans un profil personnel ou sur un appareil non géré) doivent vérifier que l'environnement client est à jour et le préparer si nécessaire avant de demander des signaux.
Voici la procédure à suivre pour que les applications de la liste d'autorisation puissent accéder aux signaux de confiance des appareils :
- Vérifier l'environnement client
- Préparer l'environnement client
- Accéder aux signaux de confiance des appareils

Vérifier l'environnement client
L'exemple de code suivant montre comment utiliser getEnvironment pour lire l'état actuel de l'application ADP. Votre application peut ensuite utiliser une instance deviceClient mise en cache pour accéder aux signaux de confiance de l'appareil si l'environnement est prêt et à jour (voir Accéder aux signaux de confiance de l'appareil).
Kotlin
import com.google.android.managementapi.common.model.Role import com.google.android.managementapi.device.DeviceClient import com.google.android.managementapi.device.DeviceClientFactory import com.google.android.managementapi.device.model.GetDeviceRequest import com.google.android.managementapi.environment.EnvironmentClient import com.google.android.managementapi.environment.EnvironmentClientFactory import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.INSTALLED import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.NOT_INSTALLED import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE import com.google.android.managementapi.environment.model.GetEnvironmentRequest import com.google.android.managementapi.environment.model.PrepareEnvironmentRequest try { val context = applicationContext val roles = listOf(Role.builder().setRoleType(Role.RoleType.IDENTITY_PROVIDER).build()) val request = GetEnvironmentRequest.builder().setRoles(roles).build() val environmentClient = EnvironmentClientFactory.create(context) val environmentResponse = environmentClient.getEnvironment(request) if (environmentResponse.hasAndroidDevicePolicyEnvironment()) { val adpEnvironment = environmentResponse.androidDevicePolicyEnvironment if (adpEnvironment.state == READY && adpEnvironment.version == UP_TO_DATE) { // AMAPI Environment State OK, Version OK. Requesting Device signals.. checkDevice(deviceClient) } else if (adpEnvironment.state == INSTALLED) { // prepareEnvironment should be called, calling // prepareEnvironment won't show the UI prepareEnvironment(context, environmentClient) } else if (adpEnvironment.state == NOT_INSTALLED) { // prepareEnvironment should be called, calling // prepareEnvironment will show the UI prepareEnvironment(context, environmentClient) } } } catch (e: Exception) { Log.e(TAG, "Exception", e) }
Java
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.INSTALLED; import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.NOT_INSTALLED; import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY; import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE; import com.google.android.managementapi.common.model.Role; import com.google.android.managementapi.device.DeviceClient; import com.google.android.managementapi.device.DeviceClientFactory; import com.google.android.managementapi.device.model.Device; import com.google.android.managementapi.device.model.GetDeviceRequest; import com.google.android.managementapi.environment.EnvironmentClient; import com.google.android.managementapi.environment.EnvironmentClientFactory; import com.google.android.managementapi.environment.model.Environment; import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment; import com.google.android.managementapi.environment.model.GetEnvironmentRequest; import com.google.android.managementapi.environment.model.PrepareEnvironmentRequest; import com.google.android.managementapi.environment.model.PrepareEnvironmentResponse; try { Context context = getApplicationContext(); ImmutableListroles = new ImmutableList.Builder () .add(Role.builder() .setRoleType(Role.RoleType.IDENTITY_PROVIDER) .build()) .build(); EnvironmentClient environmentClient = EnvironmentClientFactory.create(context); GetEnvironmentRequest request = GetEnvironmentRequest.builder() .setRoles(roles) .build(); ListenableFuture getEnvironmentFuture = environmentClient.getEnvironmentAsync(request); Futures.addCallback(getEnvironmentFuture, new FutureCallback<>() { @Override public void onSuccess(Environment environment) { AndroidDevicePolicyEnvironment adpEnvironment = environment.getAndroidDevicePolicyEnvironment(); AndroidDevicePolicyEnvironment.State state = adpEnvironment.getState(); AndroidDevicePolicyEnvironment.Version version = adpEnvironment.getVersion(); if (state == READY && version == UP_TO_DATE) { // AMAPI Environment State OK, Version OK. Requesting Device signals.. checkDevice(deviceClient); } else if (state == INSTALLED) { // prepareEnvironment should be called, calling // prepareEnvironment won't show the UI prepareEnvironment(context, environmentClient); } else if (state == NOT_INSTALLED) { // prepareEnvironment should be called, calling // prepareEnvironment will show the UI prepareEnvironment(context, environmentClient); } } @Override public void onFailure(Throwable t) { Log.d(TAG, t.toString()); } }, MoreExecutors.directExecutor()); } catch (Exception e) { Log.d(TAG, e.toString()); }
Si l'application ADP est installée, mais n'est pas à jour, votre application doit appeler prepareEnvironment pour mettre à jour l'application ADP de manière silencieuse, sans intervention de l'utilisateur.
Si l'application ADP n'est pas installée, votre application peut appeler prepareEnvironment pour inviter l'utilisateur à l'installer. Consultez Préparer l'environnement client.
Préparer l'environnement client
Si l'application ADP est déjà installée, l'API la mettra à jour de manière silencieuse, sans intervention de l'utilisateur.
Si l'application ADP n'est pas installée, l'API invite l'utilisateur à accepter l'installation de l'application ADP.
Il est possible d'enregistrer un rappel pour surveiller le choix utilisateur. Pour en savoir plus, consultez Suivre l'interaction des utilisateurs lors de l'installation de l'application ADP.
Nous vous recommandons d'effectuer l'appel prepareEnvironment à partir d'un processus de premier plan, pendant le flux d'intégration de l'expérience utilisateur, afin d'éviter de surprendre l'utilisateur avec la boîte de dialogue modale Installer Android Device Policy.
S'il n'est pas possible d'appeler depuis un processus de premier plan, car il s'agit d'un flux Web et que le composant Android n'a pas d'UI, l'appel depuis l'arrière-plan est autorisé, à condition qu'il se produise pendant le flux d'expérience utilisateur d'intégration.
Une fois l'environnement correctement configuré, vous pouvez accéder aux signaux de confiance de l'appareil. Consultez Accéder aux signaux de confiance des appareils.
Kotlin
try { val myNotificationReceiverService = ComponentName( context, MyNotificationReceiverService::class.java ) val roles = listOf(Role.builder().setRoleType(Role.RoleType.IDENTITY_PROVIDER).build()) val request = PrepareEnvironmentRequest.builder().setRoles(roles).build() val response = environmentClient.prepareEnvironment(request, myNotificationReceiverService) val environment = response.environment val adpEnvironment = environment.androidDevicePolicyEnvironment val state = adpEnvironment.state val version = adpEnvironment.version if (state == READY && version == UP_TO_DATE) { // Environment is prepared, access device posture signals using // DeviceClient. checkDevice(deviceClient) } else { // The prepareEnvironment call failed to prepare Log.w( TAG, "AMAPI environment was not ready: " + state + " - " + version ) } } catch (e: java.lang.Exception) { Log.d(TAG, e.toString()) }
Java
try { ComponentName myNotificationReceiverService = new ComponentName( context, MyNotificationReceiverService.class ); ImmutableListroles = new ImmutableList.Builder () .add(Role.builder() .setRoleType(Role.RoleType.IDENTITY_PROVIDER) .build()) .build(); PrepareEnvironmentRequest request = PrepareEnvironmentRequest.builder() .setRoles(roles) .build(); ListenableFuture environmentFuture = environmentClient.prepareEnvironmentAsync( request, myNotificationReceiverService ); Futures.addCallback(environmentFuture, new FutureCallback<>() { @Override public void onSuccess(PrepareEnvironmentResponse response) { Environment environment = response.getEnvironment(); AndroidDevicePolicyEnvironment adpEnvironment = environment.getAndroidDevicePolicyEnvironment(); AndroidDevicePolicyEnvironment.State state = adpEnvironment.getState(); AndroidDevicePolicyEnvironment.Version version = adpEnvironment.getVersion(); if (state == READY && version == UP_TO_DATE) { // AMAPI Environment State OK, Version OK. Requesting Device signals.. checkDevice(deviceClient); } else { // The prepareEnvironment call failed to prepare Log.w( TAG, "AMAPI environment was not ready: " + adpEnvironment.getState() + " - " + adpEnvironment.getVersion() ); } } @Override public void onFailure(@NonNull Throwable t) { // Handle the error Log.d(TAG, "AMAPI response did not contain an ADP environment"); } }, MoreExecutors.directExecutor()); } catch (Exception e) { Log.d(TAG, e.toString()); }
Accéder aux signaux de confiance des appareils
Recommandation d'utilisation de DeviceClient
La création d'une instance deviceClient (ou d'une instance EnvironmentClient) établit un canal de communication avec l'application Règles relatives aux appareils Android (ADP) qui se dissocie automatiquement lorsqu'elle est inactive. Étant donné que les instances clientes n'exposent pas de méthode d'arrêt explicite, la création et l'abandon de plusieurs instances laissent des canaux orphelins à récupérer.
Pour optimiser les performances et éviter les canaux orphelins, créez une seule instance de DeviceClient au niveau de l'application à l'aide de DeviceClientFactory.create(context.applicationContext) et mettez-la en cache pour la réutiliser dans l'ensemble de votre application, par exemple en tant que singleton au niveau de l'application ou en utilisant l'injection de dépendances.
Pour accéder aux signaux de confiance des appareils qui vous intéressent, utilisez votre instance deviceClient mise en cache pour demander l'objet Device :
Kotlin
runCatching { deviceClient.getDeviceAwait(GetDeviceRequest.getDefaultInstance()) }.onFailure { t -> Log.d(TAG, t.toString()) }.onSuccess { device -> // Access device posture signals available in device val deviceString = device.toString() Log.d(TAG, deviceString) }
Java
ListenableFuturedeviceFuture = deviceClient.getDevice(GetDeviceRequest.getDefaultInstance()); Futures.addCallback(deviceFuture, new FutureCallback () { @Override public void onSuccess(Device device) { // Access device posture signals available in device String deviceString = device.toString(); Log.d(TAG, deviceString); } @Override public void onFailure(Throwable t) { Log.d(TAG, t.toString()); } }, MoreExecutors.directExecutor());
Accéder aux signaux depuis une application associée
Sur les appareils entièrement gérés ou depuis un profil professionnel géré avec l'API Android Management (AMAPI), une application à laquelle le rôle COMPANION_APP est attribué peut lire les signaux de confiance de l'appareil sans être enregistrée dans la liste d'autorisation.
Lorsqu'elle est appelée depuis un profil professionnel (sur un appareil appartenant à l'entreprise ou à un particulier), votre application doit s'exécuter dans le profil professionnel, et l'accès est limité aux signaux du profil professionnel (par exemple, l'état de gestion, la complexité du verrouillage de l'écran et les applications installées ne reflètent que le profil professionnel).
1. Attribuer le rôle COMPANION_APP dans la règle
Configurez votre règle EMM pour accorder roleType: "COMPANION_APP" à votre application dans la liste de règles applications :
- Pour configurer le fichier manifeste de votre application et éventuellement écouter les attributions de rôles, consultez Gérer les rôles d'application.
- Pour connaître la syntaxe de configuration des règles, consultez Configuration des règles.
2. Lire les signaux directement avec DeviceClient
Créez et mettez en cache une seule instance deviceClient au niveau de l'application à l'aide de DeviceClientFactory.create (voir Recommandation d'utilisation de DeviceClient), puis demandez directement l'objet Device :
Kotlin
import android.content.Context import android.util.Log import com.google.android.managementapi.device.DeviceClient import com.google.android.managementapi.device.DeviceClientFactory import com.google.android.managementapi.device.model.GetDeviceRequest // Instantiate once at application scope (for example, using dependency injection) // to reuse a single DeviceClient instance across your app. class DeviceTrustRepository(context: Context) { private val deviceClient: DeviceClient = DeviceClientFactory.create(context.applicationContext) suspend fun readSignalsFromCompanionApp() { runCatching { deviceClient.getDeviceAwait(GetDeviceRequest.getDefaultInstance()) }.onSuccess { device -> Log.d(TAG, "Device signals: $device") }.onFailure { t -> Log.e(TAG, "Failed to read device signals", t) } } companion object { private const val TAG = "DeviceTrustRepository" } }
Java
import android.content.Context; import android.util.Log; import com.google.android.managementapi.device.DeviceClient; import com.google.android.managementapi.device.DeviceClientFactory; import com.google.android.managementapi.device.model.Device; import com.google.android.managementapi.device.model.GetDeviceRequest; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; // Instantiate once at application scope (for example, using dependency injection) // to reuse a single DeviceClient instance across your app. public class DeviceTrustRepository { private static final String TAG = "DeviceTrustRepository"; private final DeviceClient deviceClient; public DeviceTrustRepository(Context context) { this.deviceClient = DeviceClientFactory.create(context.getApplicationContext()); } public void readSignalsFromCompanionApp() { ListenableFuturedeviceFuture = deviceClient.getDevice(GetDeviceRequest.getDefaultInstance()); Futures.addCallback(deviceFuture, new FutureCallback () { @Override public void onSuccess(Device device) { Log.d(TAG, "Device signals: " + device); } @Override public void onFailure(Throwable t) { Log.e(TAG, "Failed to read device signals", t); } }, MoreExecutors.directExecutor()); } }
Gérer les erreurs
Si votre application effectue une requête de confiance de l'appareil et que l'appel échoue, elle reçoit une exception. Ces exceptions peuvent se produire pour diverses raisons, par exemple :
SecurityException: votre application n'est pas autorisée à effectuer la requête (par exemple, elle n'est pas enregistrée dans la liste d'autorisation alors que cela est requis, le rôleCOMPANION_APPne lui est pas attribué sur un appareil géré par l'AMAPI ou elle a appeléprepareEnvironmenten tant qu'application associée qui ne figure pas dans la liste d'autorisation). Consultez Accéder aux signaux de confiance des appareils et Accéder aux signaux depuis une application associée.- Une sous-classe de
AmapiSdkException(ADP) n'est pas présente ou n'est pas prête. Cela peut être dû à une condition temporaire. Vous devez relancer l'appel.
Stratégies de nouvelle tentative
Pour les exceptions dues à une condition temporaire, implémentez une stratégie de nouvelle tentative avec un intervalle exponentiel entre les tentatives. Après le premier échec, commencez avec un délai initial de cinq secondes avant de réessayer.
Implémentez une stratégie de nouvelle tentative avec un nombre maximal de tentatives comme condition de sortie en augmentant le délai de manière exponentielle à chaque fois (10 s, 20 s, etc.).
Suivre l'interaction des utilisateurs lors de l'installation de l'application ADP
Si l'appareil doit installer l'application ADP pendant l'prepareEnvironment, votre application peut suivre l'interaction de l'utilisateur en implémentant un NotificationReceiverService pour recevoir des notifications remplaçant getPrepareEnvironmentListener :
Kotlin
import android.util.Log import com.google.android.managementapi.environment.EnvironmentListener import com.google.android.managementapi.environment.model.EnvironmentEvent.EventCase.Kind.ANDROID_DEVICE_POLICY_INSTALL_CONSENT_ACCEPTED import com.google.android.managementapi.environment.model.EnvironmentEvent import com.google.android.managementapi.notification.NotificationReceiverService class MyNotificationReceiverService : NotificationReceiverService() { override fun getPrepareEnvironmentListener(): EnvironmentListener { return MyEnvironmentListener() } } class MyEnvironmentListener : EnvironmentListener { override fun onEnvironmentEvent( event: EnvironmentEvent ) { if (event.event.kind == ANDROID_DEVICE_POLICY_INSTALL_CONSENT_ACCEPTED) { Log.d(TAG, "User provided install consent") } else { Log.d(TAG, "User rejected install consent") } } companion object { private val TAG: String = MyEnvironmentListener::class.java.simpleName } }
Java
import static com.google.android.managementapi.environment.model.EnvironmentEvent.EventCase.Kind.ANDROID_DEVICE_POLICY_INSTALL_CONSENT_ACCEPTED; import android.util.Log; import androidx.annotation.NonNull; import com.google.android.managementapi.environment.EnvironmentListener; import com.google.android.managementapi.environment.model.EnvironmentEvent; import com.google.android.managementapi.notification.NotificationReceiverService; class MyNotificationReceiverService extends NotificationReceiverService { @NonNull @Override protected EnvironmentListener getPrepareEnvironmentListener() { return new MyEnvironmentListener(); } } class MyEnvironmentListener implements EnvironmentListener { final private String TAG = MyEnvironmentListener.class.getSimpleName(); @Override public void onEnvironmentEvent(EnvironmentEvent event) { if (event.getEvent().getKind() == ANDROID_DEVICE_POLICY_INSTALL_CONSENT_ACCEPTED) { Log.d(TAG, "User provided install consent"); } else { Log.d(TAG, "User rejected install consent"); } } }
Problèmes connus
Il n'y a aucun problème connu pour le moment.