EMM 集成指南

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

面向 IT 管理员的功能

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

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

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

前提条件

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

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

调用该 API

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

使用名服务条款

您的用户需要先接受最新版《服务条款》 (ToS),然后才能调用该 API。如果 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://enterprise.google.com/android/zero-touch/customers'

# 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 请求),请在请求中添加 HTTP 标头 X-GOOG-API-FORMAT-VERSION,并将值设置为 2。如果您的客户端不支持详细错误(.NET 和其他),请匹配错误消息。

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

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

https://enterprise.google.com/android/zero-touch/customers

您可能需要告知 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 管理员设置适用于组织购买的所有新设备的默认配置时,零触摸注册功能可为他们提供最大帮助。如果未设置默认配置,则在控制台中提示设置默认配置。您可以检查 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 返回的值。

正在预配

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

以下 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:

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

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

开发和测试

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

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

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

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

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

面向 IT 管理员的支持服务

如果您需要在控制台界面或文档中帮助 IT 管理员,请参阅适用于 IT 管理员的零触摸注册以获取指导。您还可以引导控制台的用户参阅该帮助中心文章。

深入阅读

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