EMM integration guide

This guide helps enterprise mobility management (EMM) providers integrate zero-touch enrollment into their console. Continue reading to learn more about enrollment and see best-practice advice to help your DPC (device policy controller) provision devices. If you have a DPC, you'll learn best practices when provisioning devices and get advice to help with development and testing.

Features for IT admins

Use the customer API to help IT admins setup zero-touch enrollment direct from your console. Here are some tasks an IT admin might complete in your console:

  • Create, edit, and delete zero-touch enrollment configurations based on your mobile policies.
  • Set a default configuration so your DPC provisions future devices the organization purchases.
  • Apply individual configurations to devices or remove devices from zero-touch enrollment.

To learn more about zero-touch enrollment, read the overview.

Prerequisites

Before you add zero-touch enrollment to your EMM console, confirm that your solution supports the following:

  • Your EMM solution needs to provision a company-owned Android 8.0+ (Pixel 7.1+) device in fully managed mode. Company-owned Android 10+ devices can be provisioned as fully managed or with a work profile.
  • Because zero-touch enrollment automatically downloads and installs a DPC, your DPC must be available from Google Play. We maintain a list of compatible DPCs that IT admins can configure using the customer API or the portal. Submit a product modification request via the EMM Provider community to add your DPC to the list.
  • Your customers need a zero-touch enrollment account to call the customer API. A partner reseller sets up the account for an IT admin's organization when the organization purchases their devices.
  • The device must be compatible with Google Mobile Services (GMS) and Google Play services must be enabled at all times for zero-touch enrollment to function correctly.

Call the API

Your console's users (using their Google Account) authorize your API requests to the customer API. This flow is different to the authorization you perform for other EMM APIs. Read Authorization to learn how to do this in your app.

Handle Terms of Service

Your users need to accept the latest Terms of Service (ToS) before calling the API. If the API call returns an HTTP 403 Forbidden status code and the response body contains a TosError, prompt the user to accept the ToS by signing in to the zero-touch enrollment portal. The example below shows one of the ways you could do this:

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)

If your Google API client supports detailed errors (Java, Python, or HTTP requests), include the HTTP header X-GOOG-API-FORMAT-VERSION with the value 2 in your requests. If your client doesn't support detailed errors (.NET and others), match the error message.

When we update the ToS in the future, if you follow this approach, your app directs the user to re-accept the new ToS.

IT admins use the zero-touch enrollment portal to manage the users for their organization—you can't offer this through the customer API. IT admins can also manage devices and configurations using the portal. If you need to link to the portal from your console or in your documentation, use this URL:

https://partner.android.com/zerotouch

You might want to inform IT admins that they're prompted to sign in with their Google Account.

Device enrollment

Zero-touch enrollment is a mechanism to enroll devices and is like NFC enrollment or QR-code enrollment. Your console needs to support managed devices and your DPC must be able to run in fully managed device mode.

Zero-touch enrollment is available on supported devices running Android 8.0 or later. IT admins must purchase supported devices from a partner reseller. Your console can track which of the IT admin's devices are available for zero-touch enrollment by calling customers.devices.list.

Here's an outline of how enrollment works:

  1. A device checks in with a Google server on first startup (or after a factory reset) for zero-touch enrollment.
  2. If the IT admin has applied a configuration to the device, zero-touch enrollment runs the fully managed device Android setup wizard and personalizes the screens with metadata from the configuration.
  3. Zero-touch enrollment downloads and installs your DPC from Google Play.
  4. Your DPC receives the ACTION_PROVISION_MANAGED_DEVICE intent and provisions the device.

If there isn't an internet connection, the check happens when one becomes available. To learn more about device provisioning with zero-touch enrollment, see Provisioning below.

Default configurations

Zero-touch enrollment helps IT admins most when they set a default configuration that's applied to any new devices their organization purchases. Promote setting a default configuration from your console if one isn't set. You can check the value of customers.configurations.isDefault to find out if an organization has set a default configuration.

The example below shows how you might make an existing configuration the default:

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()

Referencing your DPC

We recommend using the API resource name customers.dpcs.name to identify your DPC and use it in configurations. The resource name contains a unique and unchanging identifier for the DPC. Call customers.dpcs.list to get the list of all supported DPCs. Because the resource name also includes the customer ID, filter the list using the last path component to find a matching Dpc instance. The example below shows how to match your DPC and persist for it later use in a configuration:

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...

If you need to show the name of a DPC in your console's user interface, display the value returned from customers.dpcs.dpcName.

Provisioning

Take the opportunity to provide a great user experience for device provisioning. A username and password should be all that's needed to provision the device. Remember, that resellers might ship devices directly to remote users. Include all other settings, such as EMM server or organizational unit, in customers.configuration.dpcExtras.

The JSON snippet below shows part of an example configuration:

{
  "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\"]"
    }
}

Zero-touch enrollment installs and launches your DPC using an Android Intent. The system sends the values in the android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE JSON property to your DPC as extras in the intent. Your DPC can read the provisioning settings from the PersistableBundle using the same keys.

Recommended—use the following intent extras to set up your DPC:

Not recommended—don't include the following extras that you might use in other enrollment methods:

To learn how to extract and use these settings in your DPC, read Provision customer devices.

Development and testing

To develop and test your console's zero-touch enrollment features, you'll need the following:

  • a supported device
  • a customer zero-touch enrollment account

Develop and test with devices that support zero-touch enrollment, such as the Google Pixel. You don't have to purchase your development devices from a reseller partner.

Contact us to get a test customer account and access to the zero-touch enrollment portal. Email us from your corporate email address that's associated with a Google Account. Tell us the manufacturer and IMEI number of one or two devices and we'll add them to your development account.

Remember, because zero-touch enrollment automatically downloads and installs a DPC, your DPC must be available from Google Play before you can test provisioning. You can't test with a development version of your DPC.

Support for IT admins

If you need to help IT admins in your console's interface or your documentation, look at Zero-touch enrollment for IT admins for guidance. You can also direct your console's users to that help center article.

Further reading

Read these documents to help you integrate zero-touch enrollment in your console: