EMM 集成指南

本指南可帮助企业移动管理 (EMM) 提供商将零触摸注册集成到其控制台中。请继续阅读,详细了解注册,并查看最佳实践建议,以帮助您的 DPC(设备政策控制器)配置设备。如果您有 DPC,您将学习配置设备时的最佳实践,并获取有助于开发和测试的建议。

面向 IT 管理员的功能

使用客户 API 帮助 IT 管理员直接从控制台设置零触摸注册。以下是 IT 管理员可能会在控制台中完成的一些任务:

  • 根据您的移动设备政策创建、修改和删除零触摸注册配置。
  • 设置默认配置,以便您的 DPC 预配组织未来购买的设备。
  • 将个别配置应用于设备,或从零触摸注册中移除设备。

如需详细了解零触摸注册,请参阅概览

前提条件

在将零触摸注册添加到 EMM 控制台之前,请确认您的解决方案支持以下操作:

  • 您的 EMM 解决方案需要在全代管式模式下配置公司自有的 Android 8.0 及更高版本(Pixel 7.1 及更高版本)设备。公司自有的 Android 10 及更高版本的设备可以配置为全托管式或带有工作资料
  • 由于零触摸注册会自动下载并安装 DPC,因此您的 DPC 必须可从 Google Play 获取。我们维护了一个兼容的 DPC 列表,IT 管理员可以使用客户 API 或门户进行配置。您可以通过 EMM 提供商社区提交产品修改请求,将您的 DPC 添加到列表中。
  • 您的客户需要使用零触摸注册帐号来调用客户 API。合作伙伴转销商在 IT 管理员组织购买其设备时为其设置帐号。
  • 设备必须与 Google 移动服务 (GMS) 兼容,并且必须始终启用 Google Play 服务,零触摸注册才能正常运行。

调用该 API

控制台用户(使用其 Google 帐号)授权您向客户 API 发出的 API 请求。此流程不同于您为其他 EMM API 执行的授权。请参阅授权,了解如何在您的应用中执行此操作。

处理服务条款

您的用户在调用该 API 之前需要接受最新的服务条款 (ToS)。如果 API 调用返回 HTTP 403 Forbidden 状态代码,并且响应正文包含 TosError,请提示用户登录零触摸注册门户以接受服务条款。以下示例展示了其中一种方法:

Java

// Authorize this method call as a user that hasn't yet accepted the ToS.
final String googleApiFormatHttpHeader = "X-GOOG-API-FORMAT-VERSION";
final String googleApiFormatVersion = "2";
final String tosErrorType =
      "type.googleapis.com/google.android.device.provisioning.v1.TosError";

try {
  // Send an API request to list all the DPCs available including the HTTP header
  // X-GOOG-API-FORMAT-VERSION with the value 2. Import the  exception:
  // from googleapiclient.errors import HttpError
  AndroidProvisioningPartner.Customers.Dpcs.List request =
        service.customers().dpcs().list(customerAccount);
  request.getRequestHeaders().put(googleApiFormatHttpHeader, googleApiFormatVersion);
  CustomerListDpcsResponse response = request.execute();
  return response.getDpcs();

} catch (GoogleJsonResponseException e) {
  // Get the error details. In your app, check details exists first.
  ArrayList<Map> details = (ArrayList<Map>) e.getDetails().get("details");
  for (Map detail : details) {
    if (detail.get("@type").equals(tosErrorType)
          && (boolean) detail.get("latestTosAccepted") != true) {
      // Ask the user to accept the ToS. If they agree, open the portal in a browser.
      // ...
    }
  }
  return null;
}

.NET

// Authorize this method call as a user that hasn't yet accepted the ToS.
try
{
    var request = service.Customers.Dpcs.List(customerAccount);
    CustomerListDpcsResponse response = request.Execute();
    return response.Dpcs;
}
catch (GoogleApiException e)
{
    foreach (SingleError error in e.Error?.Errors)
    {
        if (error.Message.StartsWith("The user must agree the terms of service"))
        {
            // Ask the user to accept the ToS. If they agree, open the portal in a browser.
            // ...
        }
    }
}

Python

# Authorize this method call as a user that hasn't yet accepted the ToS.
tos_error_type = ('type.googleapis.com/'
                  'google.android.device.provisioning.v1.TosError')
portal_url = 'https://partner.android.com/zerotouch'

# Send an API request to list all the DPCs available including the HTTP
# header X-GOOG-API-FORMAT-VERSION with the value 2. Import the exception:
# from googleapiclient.errors import HttpError
try:
  request = service.customers().dpcs().list(parent=customer_account)
  request.headers['X-GOOG-API-FORMAT-VERSION'] = '2'
  response = request.execute()
  return response['dpcs']

except HttpError as err:
  # Parse the JSON content of the error. In your app, check ToS exists first.
  error = json.loads(err.content)
  tos_error = error['error']['details'][0]

  # Ask the user to accept the ToS (not shown here). If they agree, then open
  # the portal in a browser.
  if (tos_error['@type'] == tos_error_type
      and tos_error['latestTosAccepted'] is not True):
    if raw_input('Accept the ToS in the zero-touch portal? y|n ') == 'y':
      webbrowser.open(portal_url)

如果您的 Google API 客户端支持详细错误(Java、Python 或 HTTP 请求),请在请求中包含值为 2 的 HTTP 标头 X-GOOG-API-FORMAT-VERSION。如果您的客户端不支持详细错误(.NET 等),请与错误消息保持一致。

我们日后更新服务条款时,如果您采用此方法,您的应用会引导用户重新接受新的服务条款。

IT 管理员使用零触摸注册门户来管理其组织的用户 - 您无法通过客户 API 提供此功能。IT 管理员还可以使用该门户管理设备和配置。如果您需要从控制台或文档中链接到该门户,请使用以下网址:

https://partner.android.com/zerotouch

您可能需要通知 IT 管理员,系统提示他们使用其 Google 帐号登录。

设备注册

零触摸注册是一种设备注册机制,与 NFC 注册或二维码注册类似。您的控制台需要支持受管设备,并且您的 DPC 必须能够在完全受管设备模式下运行。

零触摸注册适用于搭载 Android 8.0 或更高版本的设备。IT 管理员必须从合作伙伴转销商处购买受支持的设备。您的控制台可以通过调用 customers.devices.list 来跟踪 IT 管理员的哪些设备可用于零触摸注册。

下文简要介绍了注册流程:

  1. 设备在首次启动(或恢复出厂设置后)签入 Google 服务器以进行零触摸注册。
  2. 如果 IT 管理员已将配置应用于设备,零触摸注册会运行全代管式设备 Android 设置向导,并使用配置中的元数据对屏幕进行个性化设置。
  3. 零触摸注册会从 Google Play 下载并安装 DPC。
  4. 您的 DPC 会收到 ACTION_PROVISION_MANAGED_DEVICE intent 并配置设备。

如果没有互联网连接,则会在有可用互联网连接时执行检查。如需详细了解如何通过零触摸注册进行设备配置,请参阅下面的配置

默认配置

零触摸注册可以在 IT 管理员设置默认配置以应用于其组织购买的任何新设备时,从而最大程度地帮助 IT 管理员。建议通过控制台设置默认配置(如果尚未设置)。您可以查看 customers.configurations.isDefault 的值,确定组织是否已设置默认配置。

以下示例展示了如何将现有配置设为默认配置:

Java

// Send minimal data with the request. Just the 2 required fields.
// targetConfiguration is an existing configuration that we want to make the default.
Configuration configuration = new Configuration();
configuration.setIsDefault(true);
configuration.setConfigurationId(targetConfiguration.getConfigurationId());

// Call the API, including the FieldMask to avoid setting other fields to null.
AndroidProvisioningPartner.Customers.Configurations.Patch request = service
      .customers()
      .configurations()
      .patch(targetConfiguration.getName(), configuration);
request.setUpdateMask("isDefault");
Configuration results = request.execute();

.NET

// Send minimal data with the request. Just the 2 required fields.
// targetConfiguration is an existing configuration that we want to make the default.
Configuration configuration = new Configuration
{
    IsDefault = true,
    ConfigurationId = targetConfiguration.ConfigurationId,
};

// Call the API, including the FieldMask to avoid setting other fields to null.
var request = service.Customers.Configurations.Patch(configuration,
                                                     targetConfiguration.Name);
request.UpdateMask = "IsDefault";
Configuration results = request.Execute();

Python

# Send minimal data with the request. Just the 2 required fields.
# target_configuration is an existing configuration we'll make the default.
configuration = {
    'isDefault': True,
    'configurationId': target_configuration['configurationId']}

# Call the API, including the FieldMask to avoid setting other fields to null.
response = service.customers().configurations().patch(
    name=target_configuration['name'],
    body=configuration, updateMask='isDefault').execute()

参阅您的设备政策控制器 (DPC)

我们建议您使用 API 资源名称 customers.dpcs.name 来标识您的 DPC,并在配置中使用它。资源名称包含 DPC 的唯一且不变的标识符。调用 customers.dpcs.list 以获取所有受支持 DPC 的列表。由于资源名称还包含客户 ID,因此请使用最后一个路径组成部分过滤列表,以查找匹配的 Dpc 实例。以下示例展示了如何匹配您的 DPC 并保留下来以备在配置中使用:

Java

// Return a customer Dpc instance for the specified DPC ID.
String myDpcIdentifier = "AH6Gbe4aiS459wlz58L30cqbbXbUa_JR9...xMSWCiYiuHRWeBbu86Yjq";
final int dpcIdIndex = 3;
final String dpcComponentSeparator = "/";
// ...
for (Dpc dpcApp : dpcs) {
    // Because the DPC name is in the format customers/{CUST_ID}/dpcs/{DPC_ID}, check the
    // fourth component matches the DPC ID.
    String dpcId = dpcApp.getName().split(dpcComponentSeparator)[dpcIdIndex];
    if (dpcId.equals(myDpcIdentifier)) {
        System.out.format("My DPC is: %s\n", dpcApp.getDpcName());
        return dpcApp;
    }
}
// Handle the case when the DPC isn't found...

.NET

// Return a customer Dpc instance for the specified DPC ID.
var myDpcIdentifer = "AH6Gbe4aiS459wlz58L30cqbbXbUa_JR9...fE9WdHcxMSWCiYiuHRWeBbu86Yjq";
const int dpcIdIndex = 3;
const String dpcComponentSeparator = "/";
// ...
foreach (Dpc dpcApp in dpcs)
{
    // Because the DPC name is in the format customers/{CUST_ID}/dpcs/{DPC_ID}, check the
    // fourth component matches the DPC ID.
    String dpcId = dpcApp.Name.Split(dpcComponentSeparator)[dpcIdIndex];
    if (dpcId.Equals(myDpcIdentifer))
    {
        Console.WriteLine("Matched DPC is: {0}", dpcApp.DpcName);
        return dpcApp;
    }
}
// Handle the case when the DPC isn't found...

Python

# Return a customer Dpc instance for the specified DPC ID.
my_dpc_id = 'AH6Gbe4aiS459wlz58L30cqb...fE9WdHcxMSWCiYiuHRWeBbu86Yjq'
# ...
for dpc_app in dpcs:
  # Because the DPC name is in the format customers/{CUST_ID}/dpcs/{DPC_ID},
  # check the fourth component matches the DPC ID.
  dpc_id = dpc_app['name'].split('/')[3]
  if dpc_id == my_dpc_id:
    return dpc_app

# Handle the case when the DPC isn't found...

如果您需要在控制台界面中显示 DPC 的名称,会显示从 customers.dpcs.dpcName 返回的值。

正在预配

借此机会为设备配置提供出色的用户体验。配置设备只需设置用户名和密码。请注意,转销商可能会直接向远程用户发货。在 customers.configuration.dpcExtras 中包含所有其他设置,例如 EMM 服务器或组织部门。

以下 JSON 代码段显示了示例配置的一部分:

{
  "android.app.extra.PROVISIONING_LOCALE": "en_GB",
  "android.app.extra.PROVISIONING_TIME_ZONE": "Europe/London",
  "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED": true,
  "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE": {
    "workflow_type": 3,
    "default_password_quality": 327680,
    "default_min_password_length": 6,
    "company_name": "XYZ Corp",
    "organizational_unit": "sales-uk",
    "management_server": "emm.example.com",
    "detail_tos_url": "https://www.example.com/policies/terms/",
    "allowed_user_domains": "[\"example.com\", \"example.org\", \"example.net\"]"
    }
}

零触摸注册会使用 Android intent 安装和启动您的 DPC。系统会将 android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE JSON 属性中的值作为 intent 中的 extra 发送到您的 DPC。您的 DPC 可以使用相同的密钥从 PersistableBundle 读取配置设置。

推荐 - 使用以下 intent extra 设置 DPC:

不建议 - 请勿添加可能在其他注册方法中使用的以下 extra:

如需了解如何在 DPC 中提取和使用这些设置,请参阅配置客户设备

开发和测试

如需开发和测试控制台的零触摸注册功能,您需要具备以下各项:

  • 受支持的设备
  • 客户零触摸注册帐号

使用支持零触摸注册的设备(例如 Google Pixel)进行开发和测试。您无需从转销商合作伙伴处购买开发设备。

与我们联系,获取测试客户帐号并访问零触摸注册门户。使用与 Google 账号关联的公司电子邮件地址向我们发送电子邮件。告诉我们一两台设备的制造商和 IMEI 识别码,我们会将其添加到您的开发账号中。

请注意,由于零触摸注册会自动下载并安装 DPC,因此必须先从 Google Play 获得您的 DPC,然后才能测试配置。无法使用开发版 DPC 进行测试。

面向 IT 管理员的支持服务

如果您需要通过控制台界面或文档帮助 IT 管理员,请参阅面向 IT 管理员的零触摸注册以获取相关指导。您还可以引导控制台用户查看相应的帮助中心文章。

深入阅读

请阅读以下文档,以便在控制台中集成零触摸注册: