Device Trust di Android Enterprise - Guida all'integrazione

Questo documento deve essere la tua guida principale per l'utilizzo dell'SDK AMAPI ai fini della ricezione degli indicatori di attendibilità del dispositivo.

L'SDK AMAPI consente alla tua applicazione di accedere agli indicatori di attendibilità del dispositivo dall'app Android Device Policy (ADP). La tua app può quindi utilizzare questi indicatori per calcolare lo stato di attendibilità del dispositivo e applicare la logica di business scelta.

Prerequisiti

  • La tua applicazione deve essere autorizzata a leggere gli indicatori di attendibilità del dispositivo:
    • App complementari sui dispositivi gestiti da AMAPI: le applicazioni a cui è assegnato il ruolo COMPANION_APP nella policy possono accedere agli indicatori di attendibilità del dispositivo senza essere incluse nella lista consentita sui dispositivi completamente gestiti o da un profilo di lavoro (con accesso limitato agli indicatori del profilo di lavoro). Per maggiori dettagli, consulta Gestisci i ruoli delle applicazioni, Configurazione delle policy e Accedi agli indicatori da un'app complementare.
    • Tutte le altre applicazioni e ambienti: l'accesso agli indicatori di attendibilità del dispositivo da un profilo personale, su un dispositivo non gestito, su un dispositivo gestito da un DPC personalizzato o senza il ruolo COMPANION_APP richiede che l'applicazione sia inclusa nella lista consentita. Per informazioni su come fare domanda, vai alla pagina Accesso agli indicatori di attendibilità dei dispositivi.
  • Android Enterprise consiglia di integrare la suite di API Play Integrity nella tua applicazione client e di fare riferimento al risultato prima di leggere e fare affidamento sugli indicatori di attendibilità del dispositivo. I dispositivi che non superano i controlli dell'API Play Integrity non devono essere considerati attendibili, né devono esserlo gli indicatori derivati dal dispositivo utilizzati per determinare la postura di attendibilità. Per maggiori dettagli, puoi consultare la documentazione di Play Integrity.

Integra l'SDK AMAPI nella tua applicazione

Per accedere agli indicatori di attendibilità del dispositivo, l'applicazione deve essere integrata con l'SDK AMAPI. Puoi trovare ulteriori informazioni su questa libreria e su come aggiungerla alla tua applicazione nella guida all'integrazione dell'SDK AMAPI.

Aggiungi le autorizzazioni richieste

Alcuni degli indicatori restituiti dall'API Device Trust di Android Enterprise richiedono che l'app dichiari la stessa autorizzazione che sarebbe necessaria per accedere a queste informazioni, in particolare:

Indicatore Autorizzazione obbligatoria
Stato della rete ACCESS_NETWORK_STATE
Complessità del blocco schermo REQUEST_PASSWORD_COMPLEXITY

Se queste autorizzazioni non sono incluse nel file AndroidManifest.xml dell'app, l'API Device Trust di Android Enterprise restituirà PERMISSION_ISSUE nei metadati dell'indicatore correlato:

internalDeviceSettings=DeviceSettings{
  screenLockComplexity=COMPLEXITY_UNSPECIFIED,
  internalScreenLockComplexityMetadata=Metadata{
    dataIssues=[
      DataIssue{
        issueType=PERMISSION_ISSUE,
        issueLevel=WARNING,
        issueDetails=IssueDetailsCase{none}
      }
    ]
  },

Per maggiori dettagli, consulta l'elenco degli indicatori di attendibilità dei dispositivi disponibili.

Passaggi per accedere agli indicatori di attendibilità del dispositivo

Le applicazioni incluse nella lista consentita (incluse le app in esecuzione all'interno di un profilo personale o su un dispositivo non gestito) devono verificare che l'ambiente client sia aggiornato e prepararlo, se necessario, prima di richiedere gli indicatori.

I passaggi per consentire alle applicazioni nella lista consentita di accedere agli indicatori di attendibilità del dispositivo sono:

Passaggi per accedere agli indicatori di attendibilità del dispositivo

Verifica l'ambiente client

Il seguente esempio di codice mostra come utilizzare getEnvironment per leggere lo stato attuale dell'app ADP. L'applicazione può quindi utilizzare un'istanza deviceClient memorizzata nella cache per accedere agli indicatori di attendibilità del dispositivo se l'ambiente è pronto e aggiornato (consulta Accedi agli indicatori di attendibilità del dispositivo).

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

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

Se l'app ADP è installata, ma non è aggiornata, l'applicazione deve chiamare prepareEnvironment per aggiornare l'app ADP in modalità invisibile senza l'intervento dell'utente.

Se l'app ADP non è installata, la tua applicazione può chiamare prepareEnvironment per chiedere all'utente di installare l'app ADP. Consulta Prepara l'ambiente client.

Prepara l'ambiente client

  • Se l'app ADP è già installata, l'API la aggiornerà automaticamente senza l'intervento dell'utente.

  • Se l'app ADP non è installata, l'API chiederà all'utente di accettare l'installazione dell'app ADP.

Installa Android Device Policy

È possibile registrare un callback per monitorare la scelta dell'utente. Per ulteriori dettagli, consulta Monitora l'interazione dell'utente durante l'installazione dell'app ADP.

Ti consigliamo di effettuare la chiamata prepareEnvironment da un processo in primo piano, durante il flusso UX di onboarding, per evitare di sorprendere l'utente con la finestra di dialogo modale Installa Android Device Policy. Se non è possibile chiamare da un processo in primo piano, perché si tratta di un flusso web e il componente Android non ha un'UI, è consentito chiamare dallo sfondo, a condizione che ciò avvenga durante il flusso UX di onboarding.

Una volta configurato correttamente l'ambiente, è possibile accedere agli indicatori di attendibilità del dispositivo. Consulta la sezione Accedi agli indicatori di attendibilità del dispositivo.

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

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

Accedi agli indicatori di attendibilità del dispositivo

Suggerimento sull'utilizzo di DeviceClient

La creazione di un'istanza deviceClient (o un'istanza EnvironmentClient) stabilisce un canale di comunicazione con l'app Android Device Policy (ADP) che si scollega automaticamente quando è inattiva. Poiché le istanze client non espongono un metodo di arresto esplicito, la creazione e l'eliminazione di più istanze lasciano canali orfani che devono essere sottoposti a garbage collection.

Per ottimizzare le prestazioni ed evitare canali orfani, crea una singola istanza di DeviceClient nell'ambito dell'applicazione utilizzando DeviceClientFactory.create(context.applicationContext) e memorizzala nella cache per riutilizzarla in tutta l'applicazione, ad esempio come singleton con ambito dell'applicazione o utilizzando l'inserimento delle dipendenze.

Per accedere agli indicatori di attendibilità del dispositivo che ti interessano, utilizza l'istanza deviceClient memorizzata nella cache per richiedere l'oggetto 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

ListenableFuture deviceFuture =
        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());

Accedi agli indicatori da un'app complementare

Sui dispositivi completamente gestiti o all'interno di un profilo di lavoro gestito con l'API Android Management (AMAPI), un'applicazione a cui è assegnato il ruolo COMPANION_APP può leggere gli indicatori di attendibilità del dispositivo senza essere registrata nella lista consentita.

Se viene chiamata dall'interno di un profilo di lavoro (su un dispositivo di proprietà dell'azienda o personale), l'app deve essere eseguita all'interno del profilo di lavoro e l'accesso è limitato agli indicatori del profilo di lavoro (ad esempio, lo stato di gestione, la complessità del blocco schermo e le applicazioni installate riflettono solo il profilo di lavoro).

1. Assegna il ruolo COMPANION_APP nella policy

Configura la policy EMM per concedere roleType: "COMPANION_APP" alla tua applicazione nell'elenco delle policy applications:

2. Leggi gli indicatori direttamente con DeviceClient

Crea e memorizza nella cache una singola istanza deviceClient nell'ambito dell'applicazione utilizzando DeviceClientFactory.create (consulta Suggerimento per l'utilizzo di DeviceClient), quindi richiedi direttamente l'oggetto 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() {
        ListenableFuture deviceFuture =
                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());
    }
}

Gestisci gli errori

Se la tua app effettua una richiesta di attendibilità del dispositivo e la chiamata non va a buon fine, l'app riceve un'eccezione. Queste eccezioni possono verificarsi per vari motivi, ad esempio:

Strategie di ripetizione dei tentativi

Per le eccezioni dovute a una condizione temporanea, implementa una strategia di nuovo tentativo con un backoff esponenziale. Dopo il primo errore, inizia con un ritardo iniziale di 5 secondi prima di riprovare.

Implementa una strategia di nuovi tentativi con un numero massimo di tentativi come condizione di uscita utilizzando un ritardo aumentato in modo esponenziale ogni volta (10 secondi, 20 secondi e così via).

Monitora l'interazione dell'utente durante l'installazione dell'app ADP

Se il dispositivo deve installare l'app ADP durante la prepareEnvironment, la tua applicazione può monitorare l'interazione dell'utente implementando un NotificationReceiverService per ricevere notifiche che eseguono l'override di 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");
        }
    }
}

Problemi noti

Al momento non sono presenti problemi noti.