Android Enterprise デバイス トラスト - 統合ガイド

このドキュメントは、デバイスの信頼シグナルを受信するために AMAPI SDK を使用する際の主なガイドとなります。

AMAPI SDK を使用すると、アプリケーションは Android Device Policy(ADP)アプリからデバイス トラスト シグナルにアクセスできます。アプリはこれらのシグナルを使用してデバイスの信頼状態を計算し、選択したビジネス ロジックを適用できます。

前提条件

  • アプリケーションは、デバイスの信頼シグナルを読み取る権限を付与されている必要があります。
    • AMAPI 管理対象デバイスのコンパニオン アプリ: ポリシーで COMPANION_APP ロールが割り当てられたアプリは、フルマネージド デバイスの許可リストに登録されていなくても、または仕事用プロファイル内からでも(仕事用プロファイルからのシグナルにアクセスが制限されている場合)、デバイスの信頼シグナルにアクセスできます。詳細については、アプリケーション ロールを管理する、ポリシー構成、コンパニオン アプリからシグナルにアクセスするをご覧ください。
    • その他のすべてのアプリケーションと環境: 個人用プロファイル内、管理対象外のデバイス、カスタム DPC で管理されているデバイス、または COMPANION_APP ロールなしでデバイスの信頼シグナルにアクセスするには、アプリケーションを許可リストに登録する必要があります。申請方法については、デバイス トラスト シグナルへのアクセスのページをご覧ください。
  • 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();

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

ADP アプリがインストールされているものの最新の状態でない場合、アプリは prepareEnvironment を呼び出して、ユーザーの介入なしで ADP アプリをサイレント アップデートする必要があります。

ADP アプリがインストールされていない場合、アプリは prepareEnvironment を呼び出して、ADP アプリをインストールするようユーザーに求めることができます。クライアント環境を準備するをご覧ください。

クライアント環境を準備する

  • ADP アプリがすでにインストールされている場合、API はユーザーの介入なしにアプリをサイレント更新します。

  • ADP アプリがインストールされていない場合、API は ADP アプリのインストールを承認するようユーザーに求めます。

Android Device Policy のインストール

コールバックを登録して、ユーザー チョイスをモニタリングできます。詳細については、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
    );

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

デバイス トラスト シグナルにアクセスする

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

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

コンパニオン アプリからシグナルにアクセスする

フルマネージド デバイス、または 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() {
        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());
    }
}

エラーを処理する

アプリがデバイス信頼リクエストを行い、呼び出しに失敗すると、アプリは例外を受け取ります。これらの例外は、次のようなさまざまな理由で発生する可能性があります。

再試行手段

一時的な条件による例外の場合は、指数バックオフを使用して再試行戦略を実装します。最初に失敗した後、最初に 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");
        }
    }
}

既知の問題

現時点では、既知の問題はありません。