Android Enterprise의 기기 신뢰 - 통합 가이드

이 문서는 기기 신뢰 신호를 수신하기 위한 목적으로 AMAPI SDK를 사용하는 데 있어 기본 가이드가 되어야 합니다.

AMAPI SDK를 사용하면 애플리케이션 (때로는 '동반' 앱이라고도 함)이 ADP (Android 기기 정책) 앱의 기기 신뢰 신호에 액세스할 수 있습니다. 그러면 앱이 이러한 신호를 사용하여 기기의 신뢰 상태를 계산하고 선택한 비즈니스 로직을 실행할 수 있습니다.

기본 요건

  • 무단 사용을 방지하기 위해 기기 신뢰 신호에 대한 액세스가 제한됩니다. 신청 방법에 관한 자세한 내용은 기기 신뢰 신호 액세스 페이지를 참고하세요.
  • Android Enterprise에서는 기기 신뢰 신호를 읽고 이에 의존하기 전에 Play Integrity API 모음을 클라이언트 애플리케이션에 통합하고 결과를 참조할 것을 권장합니다. Play Integrity API 검사에 실패한 기기나 신뢰 태도 결정을 내리는 데 사용된 기기에서 파생된 신호는 신뢰할 수 없습니다. 자세한 내용은 Play Integrity 문서를 참고하세요.

애플리케이션에서 AMAPI SDK와 통합

애플리케이션이 기기 신뢰 신호에 액세스하려면 AMAPI SDK와 통합해야 합니다. 이 라이브러리 및 애플리케이션에 추가하는 방법에 관한 자세한 내용은 AMAPI SDK 통합 가이드를 참고하세요.

필수 권한 추가

Android Enterprise API의 기기 신뢰에서 반환되는 일부 신호는 앱이 이 정보에 액세스하는 데 필요한 것과 동일한 권한을 선언해야 합니다. 특히 다음이 해당됩니다.

신호 필요한 권한
네트워크 상태 ACCESS_NETWORK_STATE
화면 잠금 복잡도 REQUEST_PASSWORD_COMPLEXITY

이러한 권한이 앱의 AndroidManifest.xml에 포함되지 않으면 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 = DeviceClientFactory.create(context))
        } 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)
}

자바

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..
                DeviceClient deviceClient = DeviceClientFactory.create(context);
                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 앱이 설치되었지만 최신 버전이 아닌 경우 애플리케이션은 사용자 개입 없이 ADP 앱을 자동으로 업데이트하기 위해 prepareEnvironment를 호출해야 합니다.

ADP 앱이 설치되어 있지 않으면 애플리케이션에서 prepareEnvironment를 호출하여 사용자에게 ADP 앱을 설치하라는 메시지를 표시할 수 있습니다. 클라이언트 환경 준비를 참고하세요.

클라이언트 환경 준비

  • ADP 앱이 이미 설치되어 있으면 API가 사용자 개입 없이 자동으로 업데이트합니다.

  • ADP 앱이 설치되어 있지 않으면 API에서 사용자에게 ADP 앱 설치를 수락하라는 메시지를 표시합니다.

Android Device Policy 설치

사용자 선택을 모니터링하는 콜백을 등록할 수 있습니다. 자세한 내용은 ADP 앱 설치 중 사용자 상호작용 추적을 참고하세요.

Android Device Policy 설치 모달 대화상자로 사용자를 놀라게 하지 않으려면 온보딩 UX 흐름 중에 포그라운드 프로세스에서 prepareEnvironment 호출을 실행하는 것이 좋습니다. 포그라운드 프로세스에서 호출할 수 없는 경우(웹 흐름이고 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 = DeviceClientFactory.create(context))
    } 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())
}

자바

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..
                DeviceClient deviceClient = DeviceClientFactory.create(context);
                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 인스턴스를 사용하여 Device 객체를 요청하면 됩니다.

Kotlin

try {
    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)
    }
} catch (e: java.lang.Exception) {
    Log.d(TAG, e.toString())
}

자바

try {
    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, Log.d(TAG, t.toString());
        }
    }, MoreExecutors.directExecutor());
} catch (Exception e) {
    Log.d(TAG, e.toString());
}

ADP 앱 설치 중 사용자 상호작용 추적

기기에서 prepareEnvironment 중에 ADP 앱을 설치해야 하는 경우 애플리케이션은 getPrepareEnvironmentListener를 재정의하는 알림을 수신하기 위해 NotificationReceiverService를 구현하여 사용자 상호작용을 추적할 수 있습니다.

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

자바

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

알려진 문제

현재 알려진 문제가 없습니다.