このドキュメントは、デバイスの信頼シグナルを受信するために AMAPI SDK を使用する際の主なガイドとなります。
AMAPI SDK を使用すると、アプリケーションは Android Device Policy(ADP)アプリからデバイス トラスト シグナルにアクセスできます。アプリはこれらのシグナルを使用してデバイスの信頼状態を計算し、選択したビジネス ロジックを適用できます。
前提条件
- アプリケーションは、デバイスの信頼シグナルを読み取る権限を付与されている必要があります。
- AMAPI 管理対象デバイスのコンパニオン アプリ: ポリシーで
COMPANION_APPロールが割り当てられたアプリは、フルマネージド デバイスの許可リストに登録されていなくても、または仕事用プロファイル内からでも(仕事用プロファイルからのシグナルにアクセスが制限されている場合)、デバイスの信頼シグナルにアクセスできます。詳細については、アプリケーション ロールを管理する、ポリシー構成、コンパニオン アプリからシグナルにアクセスするをご覧ください。 - その他のすべてのアプリケーションと環境: 個人用プロファイル内、管理対象外のデバイス、カスタム DPC で管理されているデバイス、または
COMPANION_APPロールなしでデバイスの信頼シグナルにアクセスするには、アプリケーションを許可リストに登録する必要があります。申請方法については、デバイス トラスト シグナルへのアクセスのページをご覧ください。
- AMAPI 管理対象デバイスのコンパニオン アプリ: ポリシーで
- Android Enterprise では、Play Integrity API スイートをクライアント アプリケーションに統合し、デバイスの信頼シグナルを読み取って信頼する前に、その結果を参照することをおすすめします。Play Integrity API のチェックに合格しなかったデバイスは信頼すべきではありません。また、信頼状況の判定に使用されるデバイスから得られたシグナルも信頼すべきではありません。詳しくは、Play Integrity のドキュメントをご覧ください。
アプリケーションに AMAPI SDK を統合する
デバイスの信頼シグナルにアクセスするには、アプリケーションを AMAPI SDK と統合する必要があります。このライブラリの詳細と、アプリに追加する方法については、AMAPI SDK 統合ガイドをご覧ください。
必要な権限を追加する
Device Trust from Android Enterprise API から返されるシグナルの一部では、アプリがこの情報にアクセスするために必要な権限を宣言する必要があります。特に、次の権限が必要です。
| シグナル | 必要な権限 |
|---|---|
| ネットワーク ステータス | ACCESS_NETWORK_STATE |
| 画面ロックの複雑さ | REQUEST_PASSWORD_COMPLEXITY |
これらの権限がアプリの AndroidManifest.xml に含まれていない場合、Device Trust from Android Enterprise API は、関連するシグナルのメタデータで PERMISSION_ISSUE を返します。
internalDeviceSettings=DeviceSettings{
screenLockComplexity=COMPLEXITY_UNSPECIFIED,
internalScreenLockComplexityMetadata=Metadata{
dataIssues=[
DataIssue{
issueType=PERMISSION_ISSUE,
issueLevel=WARNING,
issueDetails=IssueDetailsCase{none}
}
]
},
詳しくは、利用可能なデバイス信頼シグナルのリストをご覧ください。
デバイス トラスト シグナルにアクセスする手順
許可リストに登録されているアプリ(個人用プロファイル内または管理対象外のデバイスで実行されているアプリを含む)は、シグナルをリクエストする前に、クライアント環境が最新であることを確認し、必要に応じて準備する必要があります。
許可リストに登録されているアプリがデバイス トラスト シグナルにアクセスする手順は次のとおりです。

クライアント環境を確認する
次のコード例は、getEnvironment を使用して ADP アプリの現在の状態を読み取る方法を示しています。環境が準備され、最新の状態であれば、アプリケーションはキャッシュに保存された deviceClient インスタンスを使用してデバイス トラスト シグナルにアクセスできます(デバイス トラスト シグナルにアクセスするをご覧ください)。
Kotlin
import com.google.android.managementapi.common.model.Role import com.google.android.managementapi.device.DeviceClient import com.google.android.managementapi.device.DeviceClientFactory import com.google.android.managementapi.device.model.GetDeviceRequest import com.google.android.managementapi.environment.EnvironmentClient import com.google.android.managementapi.environment.EnvironmentClientFactory import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.INSTALLED import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.NOT_INSTALLED import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE import com.google.android.managementapi.environment.model.GetEnvironmentRequest import com.google.android.managementapi.environment.model.PrepareEnvironmentRequest try { val context = applicationContext val roles = listOf(Role.builder().setRoleType(Role.RoleType.IDENTITY_PROVIDER).build()) val request = GetEnvironmentRequest.builder().setRoles(roles).build() val environmentClient = EnvironmentClientFactory.create(context) val environmentResponse = environmentClient.getEnvironment(request) if (environmentResponse.hasAndroidDevicePolicyEnvironment()) { val adpEnvironment = environmentResponse.androidDevicePolicyEnvironment if (adpEnvironment.state == READY && adpEnvironment.version == UP_TO_DATE) { // AMAPI Environment State OK, Version OK. Requesting Device signals.. checkDevice(deviceClient) } else if (adpEnvironment.state == INSTALLED) { // prepareEnvironment should be called, calling // prepareEnvironment won't show the UI prepareEnvironment(context, environmentClient) } else if (adpEnvironment.state == NOT_INSTALLED) { // prepareEnvironment should be called, calling // prepareEnvironment will show the UI prepareEnvironment(context, environmentClient) } } } catch (e: Exception) { Log.e(TAG, "Exception", e) }
Java
import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.INSTALLED; import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.NOT_INSTALLED; import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.State.READY; import static com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment.Version.UP_TO_DATE; import com.google.android.managementapi.common.model.Role; import com.google.android.managementapi.device.DeviceClient; import com.google.android.managementapi.device.DeviceClientFactory; import com.google.android.managementapi.device.model.Device; import com.google.android.managementapi.device.model.GetDeviceRequest; import com.google.android.managementapi.environment.EnvironmentClient; import com.google.android.managementapi.environment.EnvironmentClientFactory; import com.google.android.managementapi.environment.model.Environment; import com.google.android.managementapi.environment.model.Environment.AndroidDevicePolicyEnvironment; import com.google.android.managementapi.environment.model.GetEnvironmentRequest; import com.google.android.managementapi.environment.model.PrepareEnvironmentRequest; import com.google.android.managementapi.environment.model.PrepareEnvironmentResponse; try { Context context = getApplicationContext(); ImmutableListroles = new ImmutableList.Builder () .add(Role.builder() .setRoleType(Role.RoleType.IDENTITY_PROVIDER) .build()) .build(); EnvironmentClient environmentClient = EnvironmentClientFactory.create(context); GetEnvironmentRequest request = GetEnvironmentRequest.builder() .setRoles(roles) .build(); ListenableFuture getEnvironmentFuture = environmentClient.getEnvironmentAsync(request); Futures.addCallback(getEnvironmentFuture, new FutureCallback<>() { @Override public void onSuccess(Environment environment) { AndroidDevicePolicyEnvironment adpEnvironment = environment.getAndroidDevicePolicyEnvironment(); AndroidDevicePolicyEnvironment.State state = adpEnvironment.getState(); AndroidDevicePolicyEnvironment.Version version = adpEnvironment.getVersion(); if (state == READY && version == UP_TO_DATE) { // AMAPI Environment State OK, Version OK. Requesting Device signals.. checkDevice(deviceClient); } else if (state == INSTALLED) { // prepareEnvironment should be called, calling // prepareEnvironment won't show the UI prepareEnvironment(context, environmentClient); } else if (state == NOT_INSTALLED) { // prepareEnvironment should be called, calling // prepareEnvironment will show the UI prepareEnvironment(context, environmentClient); } } @Override public void onFailure(Throwable t) { Log.d(TAG, t.toString()); } }, MoreExecutors.directExecutor()); } catch (Exception e) { Log.d(TAG, e.toString()); }
ADP アプリがインストールされているものの最新の状態でない場合、アプリは prepareEnvironment を呼び出して、ユーザーの介入なしで ADP アプリをサイレント アップデートする必要があります。
ADP アプリがインストールされていない場合、アプリは prepareEnvironment を呼び出して、ADP アプリをインストールするようユーザーに求めることができます。クライアント環境を準備するをご覧ください。
クライアント環境を準備する
ADP アプリがすでにインストールされている場合、API はユーザーの介入なしにアプリをサイレント更新します。
ADP アプリがインストールされていない場合、API は ADP アプリのインストールを承認するようユーザーに求めます。
コールバックを登録して、ユーザー チョイスをモニタリングできます。詳細については、ADP アプリのインストール中にユーザー操作を追跡するをご覧ください。
prepareEnvironment 呼び出しは、オンボーディング UX フロー中にフォアグラウンド プロセスから行うことをおすすめします。これにより、[Android Device Policy をインストール] モーダル ダイアログが表示されてユーザーが驚くのを防ぐことができます。これはウェブフローであり、Android コンポーネントに UI がないため、フォアグラウンド プロセスからの呼び出しが不可能な場合は、オンボーディング UX フロー中に呼び出しが行われるという要件を満たしていれば、バックグラウンドからの呼び出しが許可されます。
環境が正しく設定されると、デバイス トラスト シグナルにアクセスできるようになります。デバイス トラスト シグナルにアクセスするをご覧ください。
Kotlin
try { val myNotificationReceiverService = ComponentName( context, MyNotificationReceiverService::class.java ) val roles = listOf(Role.builder().setRoleType(Role.RoleType.IDENTITY_PROVIDER).build()) val request = PrepareEnvironmentRequest.builder().setRoles(roles).build() val response = environmentClient.prepareEnvironment(request, myNotificationReceiverService) val environment = response.environment val adpEnvironment = environment.androidDevicePolicyEnvironment val state = adpEnvironment.state val version = adpEnvironment.version if (state == READY && version == UP_TO_DATE) { // Environment is prepared, access device posture signals using // DeviceClient. checkDevice(deviceClient) } else { // The prepareEnvironment call failed to prepare Log.w( TAG, "AMAPI environment was not ready: " + state + " - " + version ) } } catch (e: java.lang.Exception) { Log.d(TAG, e.toString()) }
Java
try { ComponentName myNotificationReceiverService = new ComponentName( context, MyNotificationReceiverService.class ); ImmutableListroles = new ImmutableList.Builder () .add(Role.builder() .setRoleType(Role.RoleType.IDENTITY_PROVIDER) .build()) .build(); PrepareEnvironmentRequest request = PrepareEnvironmentRequest.builder() .setRoles(roles) .build(); ListenableFuture environmentFuture = environmentClient.prepareEnvironmentAsync( request, myNotificationReceiverService ); Futures.addCallback(environmentFuture, new FutureCallback<>() { @Override public void onSuccess(PrepareEnvironmentResponse response) { Environment environment = response.getEnvironment(); AndroidDevicePolicyEnvironment adpEnvironment = environment.getAndroidDevicePolicyEnvironment(); AndroidDevicePolicyEnvironment.State state = adpEnvironment.getState(); AndroidDevicePolicyEnvironment.Version version = adpEnvironment.getVersion(); if (state == READY && version == UP_TO_DATE) { // AMAPI Environment State OK, Version OK. Requesting Device signals.. checkDevice(deviceClient); } else { // The prepareEnvironment call failed to prepare Log.w( TAG, "AMAPI environment was not ready: " + adpEnvironment.getState() + " - " + adpEnvironment.getVersion() ); } } @Override public void onFailure(@NonNull Throwable t) { // Handle the error Log.d(TAG, "AMAPI response did not contain an ADP environment"); } }, MoreExecutors.directExecutor()); } catch (Exception e) { Log.d(TAG, e.toString()); }
デバイス トラスト シグナルにアクセスする
DeviceClient の使用に関する推奨事項
deviceClient インスタンス(または EnvironmentClient インスタンス)を作成すると、Android デバイス ポリシー(ADP)アプリとの通信チャネルが確立され、アイドル状態になると自動的にバインドが解除されます。クライアント インスタンスは明示的なシャットダウン メソッドを公開しないため、複数のインスタンスを作成して破棄すると、孤立したチャネルがガベージ コレクションの対象になります。
パフォーマンスを最適化し、孤立したチャネルを回避するには、DeviceClientFactory.create(context.applicationContext) を使用してアプリケーション スコープで DeviceClient の単一インスタンスを作成し、アプリケーション全体で再利用できるようにキャッシュに保存します。たとえば、アプリケーション スコープのシングルトンとして、または依存関係の注入を使用して保存します。
目的のデバイス トラスト シグナルにアクセスするには、キャッシュに保存された deviceClient インスタンスを使用して Device オブジェクトをリクエストします。
Kotlin
runCatching { deviceClient.getDeviceAwait(GetDeviceRequest.getDefaultInstance()) }.onFailure { t -> Log.d(TAG, t.toString()) }.onSuccess { device -> // Access device posture signals available in device val deviceString = device.toString() Log.d(TAG, deviceString) }
Java
ListenableFuturedeviceFuture = deviceClient.getDevice(GetDeviceRequest.getDefaultInstance()); Futures.addCallback(deviceFuture, new FutureCallback () { @Override public void onSuccess(Device device) { // Access device posture signals available in device String deviceString = device.toString(); Log.d(TAG, deviceString); } @Override public void onFailure(Throwable t) { Log.d(TAG, t.toString()); } }, MoreExecutors.directExecutor());
コンパニオン アプリからシグナルにアクセスする
フルマネージド デバイス、または Android Management API(AMAPI)で管理されている仕事用プロファイル内では、COMPANION_APP ロールが割り当てられたアプリは、許可リストに登録されていなくてもデバイス トラスト シグナルを読み取ることができます。
仕事用プロファイル内(会社所有デバイスまたは個人所有デバイス)から呼び出された場合、アプリは仕事用プロファイル内で実行される必要があり、アクセスは仕事用プロファイルからのシグナルに制限されます(たとえば、管理状態、画面ロックの複雑さ、インストールされているアプリは仕事用プロファイルのみを反映します)。
1. ポリシーで COMPANION_APP ロールを割り当てる
applications ポリシーリストで、アプリケーションに roleType: "COMPANION_APP" を付与するように EMM ポリシーを構成します。
- アプリのマニフェストを構成し、必要に応じてロールの割り当てをリッスンするには、アプリケーション ロールを管理するをご覧ください。
- ポリシー構成の構文については、ポリシー構成をご覧ください。
2. DeviceClient でシグナルを直接読み取る
DeviceClientFactory.create を使用して、アプリケーション スコープで単一の deviceClient インスタンスを作成してキャッシュに保存し(DeviceClient の使用に関する推奨事項を参照)、Device オブジェクトを直接リクエストします。
Kotlin
import android.content.Context import android.util.Log import com.google.android.managementapi.device.DeviceClient import com.google.android.managementapi.device.DeviceClientFactory import com.google.android.managementapi.device.model.GetDeviceRequest // Instantiate once at application scope (for example, using dependency injection) // to reuse a single DeviceClient instance across your app. class DeviceTrustRepository(context: Context) { private val deviceClient: DeviceClient = DeviceClientFactory.create(context.applicationContext) suspend fun readSignalsFromCompanionApp() { runCatching { deviceClient.getDeviceAwait(GetDeviceRequest.getDefaultInstance()) }.onSuccess { device -> Log.d(TAG, "Device signals: $device") }.onFailure { t -> Log.e(TAG, "Failed to read device signals", t) } } companion object { private const val TAG = "DeviceTrustRepository" } }
Java
import android.content.Context; import android.util.Log; import com.google.android.managementapi.device.DeviceClient; import com.google.android.managementapi.device.DeviceClientFactory; import com.google.android.managementapi.device.model.Device; import com.google.android.managementapi.device.model.GetDeviceRequest; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; // Instantiate once at application scope (for example, using dependency injection) // to reuse a single DeviceClient instance across your app. public class DeviceTrustRepository { private static final String TAG = "DeviceTrustRepository"; private final DeviceClient deviceClient; public DeviceTrustRepository(Context context) { this.deviceClient = DeviceClientFactory.create(context.getApplicationContext()); } public void readSignalsFromCompanionApp() { ListenableFuturedeviceFuture = deviceClient.getDevice(GetDeviceRequest.getDefaultInstance()); Futures.addCallback(deviceFuture, new FutureCallback () { @Override public void onSuccess(Device device) { Log.d(TAG, "Device signals: " + device); } @Override public void onFailure(Throwable t) { Log.e(TAG, "Failed to read device signals", t); } }, MoreExecutors.directExecutor()); } }
エラーを処理する
アプリがデバイス信頼リクエストを行い、呼び出しに失敗すると、アプリは例外を受け取ります。これらの例外は、次のようなさまざまな理由で発生する可能性があります。
SecurityException- アプリにリクエストを行う権限がありません(たとえば、アプリが必要なときに許可リストに登録されていない、AMAPI 管理対象デバイスでCOMPANION_APPロールが割り当てられていない、許可リストにないコンパニオン アプリとしてprepareEnvironmentを呼び出したなど)。デバイス トラスト シグナルへのアクセスとコンパニオン アプリからシグナルにアクセスするをご覧ください。AmapiSdkExceptionのサブクラス - ADP が存在しないか、準備ができていません。これは一時的な事象が原因である可能性があるため、呼び出しを再試行する必要があります。
再試行手段
一時的な条件による例外の場合は、指数バックオフを使用して再試行戦略を実装します。最初に失敗した後、最初に 5 秒遅延してから再試行します。
試行のたびに指数関数的に増加する遅延(10 秒、20 秒など)を使用して、終了条件として最大試行回数を設定した再試行手段をおすすめします。
ADP アプリのインストール中のユーザー操作を追跡する
prepareEnvironment の間にデバイスに ADP アプリをインストールする必要がある場合、アプリは NotificationReceiverService を実装してユーザー操作を追跡し、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"); } } }
既知の問題
現時点では、既知の問題はありません。