EMM 整合指南

本指南可協助企業行動管理服務 (EMM) 供應商將零接觸註冊機制整合至控制台。請繼續閱讀下文瞭解註冊詳情,並查看最佳做法建議,協助 DPC (裝置政策控制器) 佈建裝置。如果您有 DPC,您將瞭解佈建裝置的最佳做法,並取得有助於開發和測試的建議。

IT 管理員專屬功能

您可以使用客戶 API,協助 IT 管理員直接透過控制台設定零接觸註冊機制。以下是 IT 管理員可能會在控制台中完成的部分工作:

  • 根據行動政策建立、編輯及刪除零接觸註冊機制設定。
  • 進行預設設定,以便 DPC 佈建機構日後購買的裝置。
  • 對裝置套用個別設定,或移除裝置不再是零接觸註冊機制。

如要進一步瞭解零接觸註冊機制,請參閱總覽

必要條件

在 EMM 控制台中新增零接觸註冊機制前,請先確認您的解決方案支援下列項目:

  • EMM 解決方案需要以全代管模式佈建公司擁有的 Android 8.0 以上版本 (Pixel 7.1 以上版本) 裝置。公司擁有的 Android 10 以上版本裝置可以佈建為全代管裝置或使用工作資料夾
  • 零接觸註冊機制會自動下載並安裝 DPC,因此 DPC 必須可從 Google Play 取得。我們會提供一份清單,列出 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://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 要求),請在要求中加入 HTTP 標頭 X-GOOG-API-FORMAT-VERSION2 值。如果您的用戶端不支援詳細錯誤 (.NET 及其他),請比對錯誤訊息。

日後當我們更新《服務條款》時,如果您採用此方法,應用程式會引導使用者重新接受新版《服務條款》。

IT 管理員可使用零接觸註冊機制入口網站管理所屬機構的使用者,但您無法透過客戶 API 提供這項功能。IT 管理員也可以透過入口網站管理裝置和設定。如果您需要從主控台或說明文件連結至入口網站,請使用這個網址:

https://partner.android.com/zerotouch

建議您通知 IT 管理員,系統會提示他們使用 Google 帳戶登入。

裝置註冊

零接觸註冊機制是一種註冊裝置的機制,與 NFC 註冊或 QR code 註冊類似。控制台必須支援受管理的裝置, DPC 必須能在全代管裝置模式下執行。

零接觸註冊機制適用於搭載 Android 8.0 以上版本的支援裝置。IT 管理員必須向合作夥伴經銷商購買支援的裝置。控制台可呼叫 customers.devices.list,追蹤哪些 IT 管理員的裝置可使用零接觸註冊機制。

以下概略說明註冊程序:

  1. 裝置在首次啟動時 (或恢復原廠設定後) 向 Google 伺服器進行零接觸註冊機制。
  2. 如果 IT 管理員為裝置套用設定,零接觸註冊機制就會執行全代管的裝置 Android 設定精靈,並使用設定中的中繼資料來個人化畫面。
  3. 零接觸註冊機制會從 Google Play 下載並安裝 DPC。
  4. 您的 DPC 會收到 ACTION_PROVISION_MANAGED_DEVICE 意圖,並佈建裝置。

如未連上網際網路,系統會在有可用檢查時進行檢查。如要進一步瞭解如何透過零接觸註冊機制佈建裝置,請參閱下方的佈建一節。

預設設定

只要設定套用至機構購買新裝置的預設設定,零接觸註冊機制就能協助 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 的專屬且未變更 ID。呼叫 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 屬性中的值,以意圖中的額外項目的形式傳送至 DPC。DPC 可以使用相同金鑰從 PersistableBundle 讀取佈建設定。

建議做法:使用下列意圖額外項目來設定 DPC:

不建議的做法:請勿加入可能用於其他註冊方法的以下額外項目:

如要瞭解如何在 DPC 中擷取及使用這些設定,請參閱「佈建客戶裝置」。

開發和測試

如要開發及測試主控台的零接觸註冊機制功能,您必須符合以下條件:

  • 支援的裝置
  • 客戶零接觸註冊機制帳戶

開發及測試支援零接觸註冊機制的裝置,例如 Google Pixel。您無須向經銷商合作夥伴購買開發裝置。

如要取得測試客戶帳戶並存取零接觸註冊機制入口網站,請與我們聯絡。請透過已與 Google 帳戶連結的公司電子郵件地址與我們聯絡,請提供一或兩部裝置的製造商和 IMEI 編號,我們會將這些裝置加入您的開發帳戶。

提醒您,零接觸註冊機制會自動下載並安裝 DPC,因此您的 DPC 必須可從 Google Play 取得,然後才能測試佈建作業。您無法使用 DPC 開發版本進行測試。

專為 IT 管理員提供的支援

如需在控制台介面或說明文件中協助 IT 管理員,請參閱「適用於 IT 管理員的零接觸註冊機制」一文中的指引。您也可以引導控制台使用者參閱該說明中心文章。

其他資訊

請參閱下列文件,瞭解如何在控制台中整合零接觸註冊機制: