> [!IMPORTANT]
> **Important:** Accessing device trust signals from inside a personal profile or on a non-managed device requires your application to be on the allowlist. Applications assigned the [`COMPANION_APP`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies.md.txt#RoleType.ENUM_VALUES.COMPANION_APP) role on AMAPI-managed devices can access signals without being on the allowlist on fully managed devices or from inside a work profile (limited to the work profile). For details, see [Device Trust from Android Enterprise - Signals access](https://developers.google.com/android/management/device-trust-register.md.txt).

This document should be your primary guide for usage of the AMAPI SDK for the
purposes of receiving device trust signals.

The AMAPI SDK enables your application to access device trust signals from the
Android Device Policy (ADP) app. Your app can then use these signals to compute
the device's trust state and enact business logic as chosen.

## Prerequisites

- Your application must be authorized to read device trust signals:
  - Companion apps on AMAPI-managed devices: Applications assigned the [`COMPANION_APP`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies.md.txt#RoleType.ENUM_VALUES.COMPANION_APP) role in policy can access device trust signals without being on the allowlist on fully managed devices or from inside a work profile (with access limited to signals from the work profile). For details, see [Manage application roles](https://developers.google.com/android/management/app-roles.md.txt), [Policy Configuration](https://developers.google.com/android/management/sdk-local-commands.md.txt#policy), and [Access signals from a companion app](https://developers.google.com/android/management/device-trust-api.md.txt#companion-app-access).
  - All other applications and environments: Accessing device trust signals from inside a personal profile, on a non-managed device, on a device managed by a custom DPC, or without the `COMPANION_APP` role requires your application to be on the allowlist. For information on how to apply, go to the [device trust signals access](https://developers.google.com/android/management/device-trust-register.md.txt) page.
- Android Enterprise recommends integrating the Play Integrity suite of APIs into your client application and referring to the result prior to reading and relying on the device trust signals. Devices that fail Play Integrity API checks shouldn't be trusted, nor any signals derived from the device used to make trust posture determinations. You can refer to [Play Integrity's documentation](https://developer.android.com/google/play/integrity/overview) for more details.

> [!NOTE]
> **Note:** Device Trust from Android Enterprise requires Android 10 or later.

## Integrate with the AMAPI SDK in your application

To access the device trust signals your application has to integrate with the
AMAPI SDK. You can find more information about this library and how to add it
to your application in the [AMAPI SDK integration guide](https://developers.google.com/android/management/extensibility-sdk-integration).

> [!NOTE]
> **Note:** This process requires version 1.3.0+ of the AMAPI SDK library. You can find the latest available release in the [AMAPI SDK library's release notes](https://developers.google.com/android/management/sdk-release-notes.md.txt).   
> **We strongly recommend always using the latest version of the library to
> benefit from the available bug fixes and improvements.**

## Add required permissions

A few of the signals returned from the Device Trust from Android Enterprise API
requires that the app declares the same permission that would be required to
access this information in the first place, in particular:

| Signal | Required permission |
|---|---|
| Network State | [`ACCESS_NETWORK_STATE`](https://developer.android.com/reference/android/Manifest.permission#ACCESS_NETWORK_STATE) |
| Screen lock complexity | [`REQUEST_PASSWORD_COMPLEXITY`](https://developer.android.com/reference/android/Manifest.permission#REQUEST_PASSWORD_COMPLEXITY) |

If these permissions are not included in the app's `AndroidManifest.xml`, the
Device Trust from Android Enterprise API will return `PERMISSION_ISSUE` in the
metadata of the related signal:

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

For additional details see the list of the [available device trust signals](https://developers.google.com/android/management/device-trust-signals.md.txt).

## Steps to access the device trust signals

Applications on the allowlist (including apps running inside a personal profile
or on a non-managed device) must verify that the client environment is up to
date and prepare it if necessary before requesting signals.

> [!NOTE]
> **Note:** If your application is assigned the [`COMPANION_APP`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies.md.txt#RoleType.ENUM_VALUES.COMPANION_APP) role on an AMAPI-managed device, skip `EnvironmentClient` and go directly to [Access signals from a companion app](https://developers.google.com/android/management/device-trust-api.md.txt#companion-app-access).

The steps for applications on the allowlist to access the device trust signals
are:

- [Verify the client environment](https://developers.google.com/android/management/device-trust-api.md.txt#verify-client-environment)
- [Prepare the client environment](https://developers.google.com/android/management/device-trust-api.md.txt#prepare-client-environment)
- [Access device trust signals](https://developers.google.com/android/management/device-trust-api.md.txt#access-device-trust-signals)

![Steps to access the device trust signals](https://developers.google.com/static/android/management/images/step-to-access-device-trust-signals.png)

### Verify the client environment

> [!NOTE]
> **Note:** The API requires an up to date version of GMSCore and the ADP app on the device to work correctly. To check the current device state, you can use [`EnvironmentClient`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt)'s [`getEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt#getEnvironment(com.google.android.managementapi.environment.model.GetEnvironmentRequest)) method to check if the ADP app is installed and up to date for the requested [`Role`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/model/Role.md.txt).

The following code example shows how to use [`getEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt#getEnvironment(com.google.android.managementapi.environment.model.GetEnvironmentRequest)) to read the
current state of the ADP app. Your application can then use a cached
[`deviceClient`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/device/DeviceClient.md.txt) instance to access the device trust signals if the environment
is ready and up to date (see [Access device trust signals](https://developers.google.com/android/management/device-trust-api.md.txt#access-device-trust-signals)).

> [!IMPORTANT]
> **Key Point:** Calling [`getEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt#getEnvironment(com.google.android.managementapi.environment.model.GetEnvironmentRequest)) or [`prepareEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt#prepareEnvironment(com.google.android.managementapi.environment.model.PrepareEnvironmentRequest,android.content.ComponentName)) without setting a [`Role`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/model/Role.md.txt) will result in an [`InvalidArgumentException`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/exceptions/InvalidArgumentException.md.txt).

### Kotlin

```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

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

If the ADP app is installed but not up to date, your application should call
[`prepareEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt#prepareEnvironment(com.google.android.managementapi.environment.model.PrepareEnvironmentRequest,android.content.ComponentName)) to silently update the ADP app without user intervention.

If the ADP app is not installed, your application can call `prepareEnvironment`
to prompt the user to install the ADP app. See
[Prepare the client environment](https://developers.google.com/android/management/device-trust-api.md.txt#prepare-client-environment).

### Prepare the client environment

> [!NOTE]
> **Note:** If the client environment on the device isn't ready, it is required to call `prepareEnvironment` to install or update the ADP app before accessing the device trust signals.

- If the ADP app is already installed, the API will silently update it with no
  user intervention.

- If the ADP app is not installed, the API will prompt the user to accept the
  installation of the ADP app.

![Install Android Device Policy](https://developers.google.com/static/android/management/images/device-trust-install-ADP.png)

It is possible to register a callback to monitor the user choice. See
[Track user interaction during the ADP app installation](https://developers.google.com/android/management/device-trust-api.md.txt#TrackUserInteraction)
for additional details.

We recommend that the `prepareEnvironment` call is done from a foreground
process, during the onboarding UX flow to avoid surprising the user with the
*Install Android Device Policy* modal dialog.
If calling from a foreground process is not possible, because this is a Web flow
and the Android component has no UI, calling from background is permitted with
the requirement that this happens during the onboarding UX flow.

> [!NOTE]
> **Note:** On managed devices it is recommended that the IT admin deploys the ADP app to the managed devices silently using the managed play. There are additional controls available with Managed Play that the IT admin may use:
>
> - [`policy.productPolicy[].autoInstallPolicy`](https://developers.google.com/android/work/play/emm-api/v1/devices.md.txt#policy.productPolicy.autoInstallPolicy) - controls the priority and situation to install the app
> - [`policy.productPolicy[].autoUpdateMode`](https://developers.google.com/android/work/play/emm-api/v1/devices.md.txt#policy.productPolicy.autoUpdateMode) - controls the app update priority. The IT admin may set this to `AUTO_UPDATE_HIGH_PRIORITY`

<br />


Once the environment is correctly set up, it is possible to
access the device trust Signals. See [Access device trust signals](https://developers.google.com/android/management/device-trust-api.md.txt#access-device-trust-signals).

> [!IMPORTANT]
> **Key Point:** Calling [`getEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt#getEnvironment(com.google.android.managementapi.environment.model.GetEnvironmentRequest)) or [`prepareEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt#prepareEnvironment(com.google.android.managementapi.environment.model.PrepareEnvironmentRequest,android.content.ComponentName)) without setting a [`Role`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/model/Role.md.txt) will result in an [`InvalidArgumentException`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/exceptions/InvalidArgumentException.md.txt).

### Kotlin

```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

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

### Access device trust signals

> [!IMPORTANT]
> **Key Point:** For applications on the allowlist, accessing the device trust signals requires an up to date environment. Always check the environment as explained in the [Verify the client environment](https://developers.google.com/android/management/device-trust-api.md.txt#verify-client-environment) section before trying to access the device posture information.

#### DeviceClient usage recommendation

Creating a [`deviceClient`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/device/DeviceClient.md.txt) instance (or an [`EnvironmentClient`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt) instance)
establishes a communication channel with the Android Device Policy (ADP) app
that automatically unbinds when idle. Because client instances don't expose an
explicit shutdown method, creating and discarding multiple instances leaves
orphaned channels to be garbage-collected.

To optimize performance and avoid orphaned channels, create a single instance of
`DeviceClient` at application scope using
`DeviceClientFactory.create(context.applicationContext)` and cache it for reuse
throughout your application---for example, as an application-scoped singleton or
using dependency injection.

To access the device trust signals you are interested in, use your cached
`deviceClient` instance to request the [`Device`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/device/model/Device.md.txt) object:

### Kotlin

```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

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

## Access signals from a companion app

On fully managed devices or from inside a work profile managed with the
Android Management API (AMAPI), an application assigned the
[`COMPANION_APP`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies.md.txt#RoleType.ENUM_VALUES.COMPANION_APP) role can read device trust signals without
being registered on the allowlist.

When called from inside a work profile (on either a company-owned or
personally-owned device), your app must run inside the work profile, and access
is limited to signals from the work profile (for example, management state,
screen lock complexity, and installed applications reflect only the work
profile).

### 1. Assign the COMPANION_APP role in policy

Configure your EMM policy to grant `roleType: "COMPANION_APP"` to your
application in the `applications` policy list:

- To configure your app's manifest and optionally listen for role assignments, see [Manage application roles](https://developers.google.com/android/management/app-roles.md.txt).
- For the policy configuration syntax, see [Policy Configuration](https://developers.google.com/android/management/sdk-local-commands.md.txt#policy).

### 2. Read signals directly with DeviceClient

> [!IMPORTANT]
> **Important:** Do **not** call [`EnvironmentClient`](https://developers.google.com/android/management/%5B%60getEnvironment%60%5D%20or%0A%5B%60prepareEnvironment%60%5D) from a companion app that isn't on the allowlist. On AMAPI-managed devices and work profiles, the ADP app is already installed, managing the device or profile, and updated automatically. `COMPANION_APP` callers that aren't on the allowlist aren't authorized to call `EnvironmentClient`: `getEnvironment` won't report `READY`, and `prepareEnvironment` throws a [`SecurityException`](https://developer.android.com/reference/java/lang/SecurityException).

Create and cache a single [`deviceClient`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/device/DeviceClient.md.txt) instance at application scope using
[`DeviceClientFactory.create`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/device/DeviceClientFactory.md.txt#create(android.content.Context)) (see
[DeviceClient usage recommendation](https://developers.google.com/android/management/device-trust-api.md.txt#deviceclient-usage-recommendation)), then
request the [`Device`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/device/model/Device.md.txt) object directly:

### Kotlin

```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

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

## Handle errors

If your app makes a device trust request and the call fails, your app receives
an exception. These exceptions can happen for various reasons, such as:

- [`SecurityException`](https://developer.android.com/reference/java/lang/SecurityException) - your app isn't authorized to make the request (for example, your app isn't registered on the allowlist when required, isn't assigned the [`COMPANION_APP`](https://developers.google.com/android/management/reference/rest/v1/enterprises.policies.md.txt#RoleType.ENUM_VALUES.COMPANION_APP) role on an AMAPI-managed device, or called [`prepareEnvironment`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/environment/EnvironmentClient.md.txt#prepareEnvironment(com.google.android.managementapi.environment.model.PrepareEnvironmentRequest,android.content.ComponentName)) as a companion app that isn't on the allowlist). See [device trust signals access](https://developers.google.com/android/management/device-trust-register.md.txt) and [Access signals from a companion app](https://developers.google.com/android/management/device-trust-api.md.txt#companion-app-access).
- A subclass of [`AmapiSdkException`](https://developers.google.com/android/management/reference/amapi/com/google/android/managementapi/common/exceptions/AmapiSdkException.md.txt) - ADP is not present or not ready. This can be caused by a transient condition and you should retry the call.

### Retry strategies

For exceptions due to a transient condition implement a retry strategy with an
exponential backoff. After the first failure, start with an initial delay of 5
seconds before retrying.

Implement a retry strategy with a maximum number of attempts as an exit
condition using an exponentially increased delay each time (10s, 20s, etc.).

> [!NOTE]
> **Note:** Logging `SecurityException` and `AmapiSdkException` messages on the device or remotely with tools like [*Firebase Crashlytics*](https://firebase.google.com/docs/crashlytics) allows a root cause analysis of errors during the initial roll-out phase of a solution adopting Device Trust from Android Enterprise.

## Track user interaction during ADP app installation

If the device needs to install the ADP app during the `prepareEnvironment` your
application can track the user interaction implementing a
[`NotificationReceiverService`](https://developers.google.com/android/management/sdk-integration.md.txt#notification_receiver_service) to receive notifications overriding
`getPrepareEnvironmentListener`:

### Kotlin

```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

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

## Known issues

There are no known issues at the moment.