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 執行的授權不同。請參閱「授權」一文,瞭解如何在應用程式中執行這項操作。

處理服務條款

使用者必須先接受最新版《服務條款》,才能呼叫 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 管理員會使用零接觸註冊入口網站管理貴機構的使用者,您無法透過客戶 API 提供這項服務。IT 管理員也可以透過入口網站管理裝置和設定。如需從主控台或說明文件中連結至入口網站,請使用下列網址:

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

您可以通知 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 傳回的值。

佈建中

請把握機會,為裝置佈建提供優質的使用者體驗。 只要提供使用者名稱和密碼,即可佈建裝置。 請注意,經銷商可能會直接將裝置寄給遠端使用者。將所有其他設定 (例如 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 屬性中的值,以意圖中的額外內容形式傳送至 DPC。您的 DPC 可以使用相同的金鑰,從 PersistableBundle 讀取佈建設定。

建議:使用下列意圖額外資訊設定 DPC:

不建議 - 請勿加入您可能在其他註冊方法中使用的下列額外項目:

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

開發和測試

如要開發及測試控制台的零接觸註冊功能,您需要下列項目:

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

使用支援零接觸註冊機制的裝置 (例如 Google Pixel) 進行開發及測試。您不必向經銷商合作夥伴購買開發裝置。

與我們聯絡,取得測試用客戶帳戶,並存取零接觸註冊入口網站。請使用與 Google 帳戶相關聯的公司電子郵件地址傳送電子郵件給我們。請提供一或兩部裝置的製造商和 IMEI 碼,我們會將這些裝置新增至您的開發人員帳戶。

請注意,零接觸註冊會自動下載及安裝 DPC,因此您必須先在 Google Play 上架 DPC,才能測試佈建作業。您無法使用 DPC 的開發版本進行測試。

IT 管理員專用支援服務

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

延伸閱讀

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