Device Trust od Android Enterprise – przewodnik integracji

Ten dokument powinien być Twoim głównym przewodnikiem po korzystaniu z pakietu AMAPI SDK do odbierania sygnałów zaufania urządzenia.

Pakiet AMAPI SDK umożliwia aplikacji dostęp do sygnałów zaufania urządzenia z aplikacji Android Device Policy (ADP). Aplikacja może następnie używać tych sygnałów do obliczania stanu zaufania urządzenia i wdrażania wybranej logiki biznesowej.

Wymagania wstępne

  • Twoja aplikacja musi mieć uprawnienia do odczytu sygnałów zaufania urządzenia:
    • Aplikacje towarzyszące na urządzeniach zarządzanych przez AMAPI: aplikacje, którym w zasadach przypisano rolę COMPANION_APP, mogą uzyskiwać dostęp do sygnałów zaufania urządzenia bez konieczności umieszczania ich na liście dozwolonych na w pełni zarządzanych urządzeniach lub w profilu służbowym (z dostępem ograniczonym do sygnałów z profilu służbowego). Więcej informacji znajdziesz w artykułach Zarządzanie rolami aplikacji, Konfiguracja zasad i Uzyskiwanie dostępu do sygnałów z aplikacji towarzyszącej.
    • Wszystkie inne aplikacje i środowiska: dostęp do sygnałów zaufania urządzenia z profilu osobistego, na urządzeniu niezarządzanym, na urządzeniu zarządzanym przez niestandardowy kontroler zasad urządzeń lub bez roli COMPANION_APP wymaga, aby aplikacja znajdowała się na liście dozwolonych. Informacje o tym, jak się zgłosić, znajdziesz na stronie Dostęp do sygnałów zaufania urządzenia.
  • Android Enterprise zaleca zintegrowanie pakietu interfejsów Play Integrity API z aplikacją kliencką i odwoływanie się do wyniku przed odczytaniem sygnałów zaufania urządzenia i poleganiem na nich. Urządzenia, które nie przejdą testów interfejsu Play Integrity API, nie powinny być uznawane za zaufane. To samo dotyczy sygnałów pochodzących z urządzenia, które są używane do określania stanu zaufania. Więcej informacji znajdziesz w dokumentacji Play Integrity.

Integracja z pakietem AMAPI SDK w aplikacji

Aby uzyskać dostęp do sygnałów zaufania urządzenia, aplikacja musi zostać zintegrowana z pakietem SDK AMAPI. Więcej informacji o tej bibliotece i sposobie dodawania jej do aplikacji znajdziesz w przewodniku po integracji pakietu AMAPI SDK.

Dodawanie wymaganych uprawnień

Niektóre sygnały zwracane przez interfejs Device Trust od Android Enterprise API wymagają, aby aplikacja deklarowała te same uprawnienia, które byłyby wymagane do uzyskania dostępu do tych informacji, w szczególności:

Sygnał Wymagane uprawnienie
Stan sieci ACCESS_NETWORK_STATE
Złożoność blokady ekranu REQUEST_PASSWORD_COMPLEXITY

Jeśli te uprawnienia nie są uwzględnione w pliku AndroidManifest.xml aplikacji, interfejs Device Trust od Android Enterprise API zwróci wartość PERMISSION_ISSUE w metadanych powiązanego sygnału:

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

Więcej informacji znajdziesz na liście dostępnych sygnałów Device Trust.

Uzyskiwanie dostępu do sygnałów zaufania urządzenia

Aplikacje z listy dozwolonych (w tym aplikacje działające w profilu osobistym lub na urządzeniu niezarządzanym) muszą sprawdzić, czy środowisko klienta jest aktualne, i w razie potrzeby przygotować je przed wysłaniem żądania sygnałów.

Aby aplikacje z listy dozwolonych mogły uzyskać dostęp do sygnałów zaufania urządzenia, wykonaj te czynności:

Uzyskiwanie dostępu do sygnałów zaufania urządzenia

Weryfikacja środowiska klienta

Poniższy przykładowy kod pokazuje, jak używać getEnvironment do odczytywania bieżącego stanu aplikacji ADP. Twoja aplikacja może następnie używać zapisanej w pamięci podręcznej instancji deviceClient, aby uzyskać dostęp do sygnałów zaufania urządzenia, jeśli środowisko jest gotowe i aktualne (patrz Uzyskiwanie dostępu do sygnałów zaufania urządzenia).

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

Jeśli aplikacja ADP jest zainstalowana, ale nie jest aktualna, Twoja aplikacja powinna wywołać funkcję prepareEnvironment, aby dyskretnie zaktualizować aplikację ADP bez interwencji użytkownika.

Jeśli aplikacja ADP nie jest zainstalowana, Twoja aplikacja może wywołać prepareEnvironment, aby poprosić użytkownika o jej zainstalowanie. Więcej informacji znajdziesz w sekcji Przygotowywanie środowiska klienta.

Przygotowywanie środowiska klienta

  • Jeśli aplikacja ADP jest już zainstalowana, interfejs API zaktualizuje ją bez interwencji użytkownika.

  • Jeśli aplikacja ADP nie jest zainstalowana, interfejs API poprosi użytkownika o zaakceptowanie jej instalacji.

Zainstaluj Android Device Policy

Można zarejestrować wywołanie zwrotne, aby monitorować wybór użytkownika. Więcej informacji znajdziesz w artykule Śledzenie interakcji użytkownika podczas instalacji aplikacji ADP.

Zalecamy, aby wywołanie prepareEnvironment było wykonywane z procesu na pierwszym planie podczas procesu UX wprowadzania, aby nie zaskoczyć użytkownika oknem modalnym Zainstaluj zasady dotyczące urządzeń z Androidem. Jeśli wywołanie z procesu na pierwszym planie nie jest możliwe, ponieważ jest to proces internetowy, a komponent Androida nie ma interfejsu, wywołanie z tła jest dozwolone, pod warunkiem że następuje podczas procesu wprowadzania UX.

Po prawidłowym skonfigurowaniu środowiska można uzyskać dostęp do sygnałów zaufania urządzenia. Zobacz Uzyskiwanie dostępu do sygnałów zaufania urządzenia.

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

Dostęp do sygnałów zaufania urządzenia

Rekomendacja dotycząca korzystania z DeviceClient

Utworzenie instancji deviceClient (lub EnvironmentClient) powoduje ustanowienie kanału komunikacyjnego z aplikacją Android Device Policy (ADP), który automatycznie się rozłącza, gdy jest bezczynny. Instancje klienta nie udostępniają jawnej metody wyłączania, więc tworzenie i odrzucanie wielu instancji pozostawia osierocone kanały do usunięcia przez moduł odśmiecania pamięci.

Aby zoptymalizować wydajność i uniknąć osieroconych kanałów, utwórz pojedynczą instancję DeviceClient w zakresie aplikacji za pomocą DeviceClientFactory.create(context.applicationContext) i zapisz ją w pamięci podręcznej do ponownego użycia w aplikacji – na przykład jako singleton w zakresie aplikacji lub za pomocą wstrzykiwania zależności.

Aby uzyskać dostęp do interesujących Cię sygnałów zaufania do urządzenia, użyj instancji deviceClient z pamięci podręcznej i poproś o obiekt 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());

Sygnały dostępu z aplikacji towarzyszącej

Na w pełni zarządzanych urządzeniach lub w profilu służbowym zarządzanym za pomocą interfejsu Android Management API (AMAPI) aplikacja z przypisaną rolą COMPANION_APP może odczytywać sygnały Device Trust bez konieczności rejestrowania na liście dozwolonych.

Gdy aplikacja jest wywoływana z profilu służbowego (na urządzeniu należącym do firmy lub na urządzeniu osobistym), musi działać w profilu służbowym, a dostęp jest ograniczony do sygnałów z profilu służbowego (np. stan zarządzania, złożoność blokady ekranu i zainstalowane aplikacje odzwierciedlają tylko profil służbowy).

1. Przypisywanie roli COMPANION_APP w zasadzie

Skonfiguruj zasady EMM, aby przyznać uprawnienie roleType: "COMPANION_APP" aplikacji na liście zasad applications:

  • Aby dowiedzieć się, jak skonfigurować plik manifestu aplikacji i opcjonalnie nasłuchiwać przypisań ról, zapoznaj się z artykułem Zarządzanie rolami aplikacji.
  • Składnię konfiguracji zasad znajdziesz w artykule na temat konfiguracji zasad.

2. Odczytywanie sygnałów bezpośrednio za pomocą DeviceClient

Utwórz i zapisz w pamięci podręcznej pojedynczą instancję deviceClient w zakresie aplikacji, używając DeviceClientFactory.create (patrz zalecenia dotyczące korzystania z DeviceClient), a następnie poproś bezpośrednio o obiekt 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());
    }
}

Obsługuj błędy

Jeśli aplikacja wysyła żądanie zaufania urządzenia, a wywołanie się nie powiedzie, aplikacja otrzyma wyjątek. Te wyjątki mogą wystąpić z różnych powodów, takich jak:

Strategie ponawiania

W przypadku wyjątków spowodowanych stanem przejściowym wdróż strategię ponawiania prób ze wzrastającym czasem do ponowienia. Po pierwszym niepowodzeniu odczekaj 5 sekund przed ponowną próbą.

Wdróż strategię ponawiania z maksymalną liczbą prób jako warunkiem zakończenia, stosując za każdym razem wykładniczo zwiększane opóźnienie (10 s, 20 s itd.).

Śledzenie interakcji użytkownika podczas instalacji aplikacji ADP

Jeśli urządzenie musi zainstalować aplikację ADP podczas prepareEnvironment, Twoja aplikacja może śledzić interakcje użytkownika, implementując NotificationReceiverService, aby otrzymywać powiadomienia zastępujące 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");
        }
    }
}

Znane problemy

Obecnie nie ma żadnych znanych problemów.