Device Trust from Android Enterprise – Integrationsanleitung

Dieses Dokument ist Ihr primärer Leitfaden für die Verwendung des AMAPI SDK zum Empfangen von Signalen zur Gerätevertrauenswürdigkeit.

Mit dem AMAPI SDK kann Ihre Anwendung auf Device-Trust-Signale der Android Device Policy-App (ADP) zugreifen. Ihre App kann diese Signale dann verwenden, um den Vertrauensstatus des Geräts zu berechnen und die gewünschte Geschäftslogik anzuwenden.

Vorbereitung

  • Ihre Anwendung muss autorisiert sein, Device-Trust-Signale zu lesen:
    • Companion-Apps auf AMAPI-verwalteten Geräten: Anwendungen, denen in der Richtlinie die Rolle COMPANION_APP zugewiesen ist, können auf Vertrauenssignale des Geräts zugreifen, ohne auf vollständig verwalteten Geräten oder in einem Arbeitsprofil auf der Zulassungsliste zu stehen. Der Zugriff ist dabei auf Signale aus dem Arbeitsprofil beschränkt. Weitere Informationen finden Sie unter Anwendungsrollen verwalten, Richtlinienkonfiguration und Auf Signale von einer Companion-App zugreifen.
    • Alle anderen Anwendungen und Umgebungen: Für den Zugriff auf Vertrauenssignale von Geräten über ein privates Profil, auf einem nicht verwalteten Gerät, auf einem von einem benutzerdefinierten DPC verwalteten Gerät oder ohne die Rolle COMPANION_APP muss Ihre Anwendung auf der Zulassungsliste stehen. Informationen zur Beantragung finden Sie auf der Seite Zugriff auf Device-Trust-Signale.
  • Android Enterprise empfiehlt, die Play Integrity-APIs in Ihre Clientanwendung zu integrieren und das Ergebnis zu prüfen, bevor Sie die Vertrauenssignale des Geräts lesen und sich darauf verlassen. Geräten, die die Play Integrity API-Prüfungen nicht bestehen, sollte nicht vertraut werden. Das gilt auch für Signale, die vom Gerät stammen und zur Bestimmung des Vertrauensstatus verwendet werden. Weitere Informationen finden Sie in der Dokumentation zu Play Integrity.

AMAPI SDK in Ihre Anwendung einbinden

Damit Ihre Anwendung auf die Signale zur Gerätevertrauenswürdigkeit zugreifen kann, muss sie in das AMAPI SDK eingebunden werden. Weitere Informationen zu dieser Bibliothek und dazu, wie Sie sie Ihrer Anwendung hinzufügen, finden Sie im Integrationsleitfaden für das AMAPI SDK.

Erforderliche Berechtigungen hinzufügen

Für einige der von der Device Trust from Android Enterprise API zurückgegebenen Signale muss die App dieselbe Berechtigung deklarieren, die für den Zugriff auf diese Informationen erforderlich wäre, insbesondere:

Signal Erforderliche Berechtigung
Netzwerkstatus ACCESS_NETWORK_STATE
Komplexität der Displaysperre REQUEST_PASSWORD_COMPLEXITY

Wenn diese Berechtigungen nicht in der AndroidManifest.xml der App enthalten sind, gibt die Device Trust from Android Enterprise API in den Metadaten des zugehörigen Signals PERMISSION_ISSUE zurück:

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

Weitere Informationen finden Sie in der Liste der verfügbaren Device Trust-Signale.

Schritte zum Zugriff auf die Device-Trust-Signale

Anwendungen auf der Zulassungsliste (einschließlich Apps, die in einem privaten Profil oder auf einem nicht verwalteten Gerät ausgeführt werden) müssen prüfen, ob die Clientumgebung auf dem neuesten Stand ist, und sie gegebenenfalls vorbereiten, bevor Signale angefordert werden.

So können Anwendungen auf der Zulassungsliste auf die Device-Trust-Signale zugreifen:

Schritte zum Zugriff auf die Device-Trust-Signale

Clientumgebung prüfen

Im folgenden Codebeispiel wird gezeigt, wie Sie mit getEnvironment den aktuellen Status der ADP-App lesen. Ihre Anwendung kann dann eine im Cache gespeicherte deviceClient-Instanz verwenden, um auf die Device-Trust-Signale zuzugreifen, wenn die Umgebung bereit und auf dem neuesten Stand ist (siehe Auf Device-Trust-Signale zugreifen).

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

Wenn die ADP-App installiert, aber nicht auf dem neuesten Stand ist, sollte deine Anwendung prepareEnvironment aufrufen, um die ADP-App ohne Nutzerinteraktion im Hintergrund zu aktualisieren.

Wenn die ADP-App nicht installiert ist, kann Ihre Anwendung prepareEnvironment aufrufen, um den Nutzer zur Installation der ADP-App aufzufordern. Weitere Informationen finden Sie unter Clientumgebung vorbereiten.

Clientumgebung vorbereiten

  • Wenn die ADP-App bereits installiert ist, wird sie von der API ohne Eingreifen des Nutzers im Hintergrund aktualisiert.

  • Wenn die ADP-App nicht installiert ist, wird der Nutzer von der API aufgefordert, die Installation der ADP-App zu akzeptieren.

Android Device Policy installieren

Sie können einen Callback registrieren, um die Nutzerauswahl zu überwachen. Weitere Informationen finden Sie unter Nutzerinteraktion während der Installation der ADP-App erfassen.

Wir empfehlen, den Aufruf prepareEnvironment während des Onboarding-UX-Prozesses über einen Vordergrundprozess auszuführen, damit der Nutzer nicht von dem modalen Dialogfeld Android Device Policy installieren überrascht wird. Wenn ein Aufruf aus einem Vordergrundprozess nicht möglich ist, weil es sich um einen Web-Flow handelt und die Android-Komponente keine Benutzeroberfläche hat, ist ein Aufruf aus dem Hintergrund zulässig, sofern dies während des Onboarding-UX-Flows geschieht.

Sobald die Umgebung korrekt eingerichtet ist, können Sie auf die Device-Trust-Signale zugreifen. Weitere Informationen finden Sie unter Auf Device-Trust-Signale zugreifen.

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

Auf Device-Trust-Signale zugreifen

Empfehlung zur DeviceClient-Nutzung

Wenn Sie eine deviceClient-Instanz (oder eine EnvironmentClient-Instanz) erstellen, wird ein Kommunikationskanal mit der Android-Geräterichtlinien-App (ADP) eingerichtet, der automatisch getrennt wird, wenn er inaktiv ist. Da Clientinstanzen keine explizite Methode zum Herunterfahren bereitstellen, werden beim Erstellen und Verwerfen mehrerer Instanzen verwaiste Kanäle hinterlassen, die von der automatischen Speicherbereinigung entfernt werden müssen.

Um die Leistung zu optimieren und verwaiste Kanäle zu vermeiden, erstellen Sie eine einzelne Instanz von DeviceClient im Anwendungsbereich mit DeviceClientFactory.create(context.applicationContext) und speichern Sie sie im Cache, um sie in der gesamten Anwendung wiederzuverwenden, z. B. als Singleton im Anwendungsbereich oder mithilfe von Dependency Injection.

Wenn Sie auf die gewünschten Device-Trust-Signale zugreifen möchten, fordern Sie mit Ihrer im Cache gespeicherten deviceClient-Instanz das Device-Objekt an:

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

Zugriffssignale von einer Companion-App

Auf vollständig verwalteten Geräten oder in einem Arbeitsprofil, das mit der Android Management API (AMAPI) verwaltet wird, kann eine Anwendung, der die Rolle COMPANION_APP zugewiesen ist, Device-Trust-Signale lesen, ohne auf der Zulassungsliste registriert zu sein.

Wenn Ihre App innerhalb eines Arbeitsprofils (entweder auf einem unternehmenseigenen oder einem privaten Gerät) aufgerufen wird, muss sie innerhalb des Arbeitsprofils ausgeführt werden. Der Zugriff ist auf Signale aus dem Arbeitsprofil beschränkt (z. B. spiegeln der Verwaltungsstatus, die Komplexität der Displaysperre und die installierten Anwendungen nur das Arbeitsprofil wider).

1. Weisen Sie die Rolle COMPANION_APP in der Richtlinie zu.

Konfigurieren Sie Ihre EMM-Richtlinie so, dass Ihrer Anwendung in der Richtlinienliste applications die Berechtigung roleType: "COMPANION_APP" gewährt wird:

  • Informationen zum Konfigurieren des Manifests Ihrer Anwendung und optional zum Überwachen von Rollenzuweisungen finden Sie unter Anwendungsrollen verwalten.
  • Informationen zur Syntax der Richtlinienkonfiguration finden Sie unter Richtlinienkonfiguration.

2. Signale direkt mit DeviceClient lesen

Erstellen Sie eine einzelne deviceClient-Instanz im Anwendungsbereich mit DeviceClientFactory.create (siehe Empfehlung zur Verwendung von DeviceClient) und speichern Sie sie im Cache. Fordern Sie dann das Device-Objekt direkt an:

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

Fehler verarbeiten

Wenn Ihre App eine Anfrage zur Gerätevertrauenswürdigkeit stellt und der Aufruf fehlschlägt, erhält Ihre App eine Ausnahme. Diese Ausnahmen können verschiedene Ursachen haben, z. B.:

Wiederholungsstrategien

Implementieren Sie für Ausnahmen, die auf einen vorübergehenden Zustand zurückzuführen sind, eine Wiederholungsstrategie mit exponentiellem Backoff. Nach dem ersten Fehler wird mit einer anfänglichen Verzögerung von 5 Sekunden begonnen, bevor der Vorgang wiederholt wird.

Implementieren Sie eine Wiederholungsstrategie mit einer maximalen Anzahl von Versuchen als Abbruchbedingung und einer exponentiell zunehmenden Verzögerung (10 s, 20 s usw.).

Nutzerinteraktion während der Installation der ADP-App erfassen

Wenn die ADP-App während des prepareEnvironment auf dem Gerät installiert werden muss, kann Ihre Anwendung die Nutzerinteraktion verfolgen, indem sie einen NotificationReceiverService implementiert, um Benachrichtigungen zu erhalten, die getPrepareEnvironmentListener überschreiben:

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

Bekannte Probleme

Derzeit sind keine Probleme bekannt.