Device Trust from Android Enterprise - 集成指南

本文档应作为您使用 AMAPI SDK 接收设备信任信号的主要指南。

AMAPI SDK 可让您的应用(我们有时也称之为“配套”应用)访问 ADP(Android 设备政策)应用中的设备信任信号。然后,您的应用可以使用这些信号计算设备的信任状态,并根据所选的业务逻辑执行操作。

前提条件

  • 设备搭载 Android 9 或更高版本。
  • 为防止未经授权的使用,我们对该 API 的访问权限进行了限制。如需详细了解受支持的用例以及如何申请,请参阅:API 访问权限。您的请求获得批准后,注册 API 访问权限部分会详细介绍相关技术要求。
  • Android Enterprise 建议您将 Play Integrity 套件 API 集成到客户端应用中,并在读取设备信任信号并依赖于这些信号之前,先参考结果。不应信任未通过 Play Integrity API 检查的设备,也不应信任从设备派生出来用于确定信任状态的信号。如需了解详情,请参阅 Play Integrity 文档

注册以获取 API 访问权限

  1. 使用您拥有的现有 Cloud 项目,然后从第 2 步开始操作;或者创建新的 Cloud Platform 项目:
    1. 在 Google Cloud 控制台中,前往“项目”页面
    2. 点击 NEW PROJECT
    3. 记下项目 ID,您在注册应用时需要用到此 ID。
  2. 为您的项目启用 Android Management API。
    1. 在 Google Cloud 控制台中,选择要用于 AMAPI SDK 集成的项目;可以是现有项目,也可以是第 1 步中创建的项目。
    2. 在 API 库中搜索并选择 Android Management API
    3. 前往“API 库”页面
    4. 点击 Enable
  3. 请使用反馈表单提供以下详细信息。如果您想使用不同的软件包名称 / 签名测试发布 build 和调试 build,也可以共享多个软件包名称。
    1. package_name:要集成 AMAPI SDK 的应用的软件包名称。
    2. certificate_hash_sha256:应用签名证书的 SHA256 哈希值
    3. project_id:在第 1 步中创建的 Cloud 项目的 ID。

示例数据

package_name = 'com.abc.xyz'
certificate_hash_sha256 = '03675ac53ff9cd1535ccc7dfcdfa2c458c5218371f418dc136f2d19ac1fbe8a5'
project_id =  "example-id"

在应用中与 AMAPI SDK 集成

如需访问设备信任信号,您的应用必须与 AMAPI SDK 集成。如需详细了解此库以及如何将其添加到应用中,请参阅 AMAPI SDK 集成指南

添加所需权限

从 Device Trust from 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 来访问设备信任信号(请参阅访问设备信任信号)。

KotlinJava
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 应用已安装但不是最新版本,您的应用应调用 prepareEnvironment 以在不干扰用户的情况下静默更新 ADP 应用。

如果未安装 ADP 应用,您的应用可以调用 prepareEnvironment 以提示用户安装 ADP 应用。请参阅准备客户端环境

准备客户端环境

  • 如果 ADP 应用已安装,该 API 将静默更新该应用,无需用户干预。

  • 如果未安装 ADP 应用,API 会提示用户接受安装 ADP 应用。

安装 Android Device Policy

您可以注册回调来监控用户的选择。如需了解详情,请参阅跟踪 ADP 应用安装期间的用户互动

我们建议在初始配置用户体验流程中通过前台进程执行 prepareEnvironment 调用,以免用户意外看到 Install Android Device Policy 模态对话框。如果由于这是 Web 流程且 Android 组件没有界面而无法从前台进程进行调用,则允许从后台进行调用,但要求是在初始配置用户体验流程中进行调用。

正确设置环境后,即可访问设备信任信号。请参阅访问设备信任信号

KotlinJava
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 对象。

KotlinJava
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 应用,您的应用可以跟踪用户注册服务以接收通知的互动:

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

已知问题

目前,如果不启用应用缩减功能,就无法使用 JaCoCo 进行覆盖率测试:

debug {
    // Set to true generates an exception when minify is false
    enableAndroidTestCoverage = false

    enableUnitTestCoverage = true
    // Enable to make the library work with JaCoCo
    isMinifyEnabled = false
}