Build a device policy controller

This guide describes how to develop a device policy controller (DPC) for devices in an Android enterprise deployment. A DPC app, previously known as a work policy controller, controls local device policies and system applications on devices.

About DPCs

In an Android enterprise deployment, an enterprise maintains control over various aspects of user devices, such as isolating work-related information from users' personal data, pre-configuring approved apps for the environment, or disabling device capabilities (for example, the camera).

As an EMM, you develop a DPC app that can be used by your customers in conjunction with your EMM console and server. Your customer deploys the DPC to the user devices that they manage. The DPC acts as the bridge between your EMM console (and server) and the device. An admin uses the EMM console to perform a range of tasks, including configuring device settings and apps.

The DPC creates and manages the work profile on the device on which it is installed. The work profile encrypts work-related information and keeps it separate from users' personal apps and data. Before creating the work profile, the DPC can also provision a managed Google Play Account for use on the device.

This guide shows you how to develop a DPC that can create and manage work profiles.

DPC Support Library for EMMs

The DPC Support Library for EMMs comprises utility and helper classes that facilitate provisioning and management of Android devices in an enterprise environment. The library lets you take advantage of important features in your DPC apps:

  • Managed Google Play Accounts provisioning support: Provisioning managed Google Play Accounts from the DPC app requires that Google Play and Google Play services apps meet minimum version requirements. However, updating these apps can be complex. The DPC support library takes care of updating these apps, and also ensures compatibility with future updates to the managed Google Play Accounts provisioning process. See managed Google Play Accounts provisioning support for details.
  • Managed Configurations support: Using Play EMM API to handle managed configurations for approved apps is the easiest way to implement managed configurations on your DPC. The DPC Support Library lets you delegate to Google Play the task of applying managed configurations (formerly, app restrictions) as set by the admin using your EMM console. Using the Play EMM API to handle managed configurations allows the app configuration to be applied atomically during the installation. See Apply managed configurations to work apps for more information about how to enable this capability in your DPC.

Follow the steps below to download the library. The tasks detailed in this guide assume the use of the DPC Support Library.

Download the DPC Support Library

To use the DPC Support Library, download the library from the Android Enterprise EMM Provider community. You must add the library to your build.gradle file and take care of other dependencies when you build your DPC app. For example, the library requires 11.4.0 Google Play Services auth client library.

  1. Add the library to the build.gradle file:

    Groovy

    implementation(name:'dpcsupport-yyyymmdd', ext:'aar')
    

    Kotlin

    implementation(name = "dpcsupport-yyyymmdd", ext = "aar")
    
  2. Add 11.4.0 Google Play Services auth client library to the build.gradle file:

    Groovy

    implementation 'com.google.android.gms:play-services-auth:11.4.0'
    

    Kotlin

    implementation("com.google.android.gms:play-services-auth:11.4.0")
    

The library requires certain permissions to run, so you must add these to your DPC app’s manifest when you upload to Google Play:

  <uses-permission android:name=
      "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"/>
  <uses-permission android:name=
      "android.permission.GET_ACCOUNTS"/>
  <uses-permission android:name=
      "android.permission.MANAGE_ACCOUNTS"/>
  <uses-permission android:name=
      "android.permission.WRITE_SYNC_SETTINGS"/>
  <uses-permission android:name=
      "com.google.android.providers.gsf.permission.READ_GSERVICES"/>

In addition to these preliminary setup and deployment steps, you must also initialize the specific library functionality in your DPC code, depending on the capability you want to implement. The details are included in the relevant sections below.

Create a DPC

Build your DPC on the existing model used for device administration applications. Specifically, your app must subclass DeviceAdminReceiver (a class from the android.app.admin package) as described in Device Administration.

Create a work profile

For a sample that demonstrates how to create a basic work profile, see BasicManagedProfile on GitHub.

To create a work profile on a device that already has a personal profile, first find out if the device can support a work profile, by checking for the existence of the FEATURE_MANAGED_USERS system feature:

Kotlin

if (!packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
    // This device does not support work profiles!
}

Java

PackageManager pm = getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
    // This device does not support work profiles!
}

If the device supports work profiles, create a work profile by sending an intent with an ACTION_PROVISION_MANAGED_PROFILE action. (In some documentation, managed profile is a general term that means the same thing as work profile in the context of Android in the enterprise.) Include the device admin package name as an extra:

Kotlin

val provisioningActivity = getActivity()

// You'll need the package name for the DPC app.
val myDPCPackageName = "com.example.myDPCApp"

// Set up the provisioning intent
val adminComponent = ComponentName(provisioningActivity.applicationContext, MyAdminReceiver::class.java)
provisioningIntent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, adminComponent.flattenToString())
if (provisioningIntent.resolveActivity(provisioningActivity.packageManager) == null) {
    // No handler for intent! Can't provision this device.
    // Show an error message and cancel.
} else {
    // REQUEST_PROVISION_MANAGED_PROFILE is defined
    // to be a suitable request code
    startActivityForResult(provisioningIntent,
            REQUEST_PROVISION_MANAGED_PROFILE)
    provisioningActivity.finish()
}

Java

Activity provisioningActivity = getActivity();
// You'll need the package name for the DPC app.
String myDPCPackageName = "com.example.myDPCApp";
// Set up the provisioning intent
Intent provisioningIntent =
        new Intent("android.app.action.PROVISION_MANAGED_PROFILE");
ComponentName adminComponent = new ComponentName(provisioningActivity.getApplicationContext(), MyAdminReceiver.class);
provisioningIntent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, adminComponent.flattenToString());
if (provisioningIntent.resolveActivity(provisioningActivity.getPackageManager())
         == null) {
    // No handler for intent! Can't provision this device.
    // Show an error message and cancel.
} else {
    // REQUEST_PROVISION_MANAGED_PROFILE is defined
    // to be a suitable request code
    startActivityForResult(provisioningIntent,
            REQUEST_PROVISION_MANAGED_PROFILE);
    provisioningActivity.finish();
}

The system responds to this intent by doing the following:

  • Verifies that the device is encrypted. If it isn't, the system prompts the user to encrypt the device before proceeding.
  • Creates a work profile.
  • Removes non-required applications from the work profile.
  • Copies the DPC app into the work profile and sets the DPC itself as the profile owner.

Override onActivityResult() to see if provisioning was successful:

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    // Check if this is the result of the provisioning activity
    if (requestCode == REQUEST_PROVISION_MANAGED_PROFILE) {
        // If provisioning was successful, the result code is
        // Activity.RESULT_OK
        if (resultCode == Activity.RESULT_OK) {
            // Work profile created and provisioned.
        } else {
            // Provisioning failed.
        }
        return
    } else {
        // This is the result of some other activity. Call the superclass.
        super.onActivityResult(requestCode, resultCode, data)
    }
}

Java

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Check if this is the result of the provisioning activity
    if (requestCode == REQUEST_PROVISION_MANAGED_PROFILE) {
        // If provisioning was successful, the result code is
        // Activity.RESULT_OK
        if (resultCode == Activity.RESULT_OK) {
            // Work profile created and provisioned.
        } else {
            // Provisioning failed.
        }
        return;
    } else {
        // This is the result of some other activity. Call the superclass.
        super.onActivityResult(requestCode, resultCode, data);
    }
}

Finish enabling the work profile

When the profile has been provisioned, the system calls the DPC app's DeviceAdminReceiver.onProfileProvisioningComplete() method. Override this callback method to finish enabling the work profile.

A typical DeviceAdminReceiver.onProfileProvisioningComplete() callback implementation does the following:

Activate the work profile

Once you have completed these tasks, call the device policy manager's setProfileEnabled() method to activate the work profile:

Kotlin

// Get the device policy manager
val myDevicePolicyMgr = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
val componentName = myDeviceAdminReceiver.getComponentName(this)
// Set the name for the newly created work profile.
myDevicePolicyMgr.setProfileName(componentName, "My New Work Profile")
// ...and enable the profile
myDevicePolicyMgr.setProfileEnabled(componentName)

Java

// Get the device policy manager
DevicePolicyManager myDevicePolicyMgr =
        (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName componentName = myDeviceAdminReceiver.getComponentName(this);
// Set the name for the newly created work profile.
myDevicePolicyMgr.setProfileName(componentName, "My New Work Profile");
// ...and enable the profile
myDevicePolicyMgr.setProfileEnabled(componentName);

Set up device policies

The DPC app applies the device policies as set by an admin to meet an organization's requirements and constraints. For example, security policy might require that devices lock after a certain number of failed password attempts. The DPC queries the EMM console for current policies then applies the policies using the Device Administration API.

For information on how to apply device policies, see Policies.

Apply managed configurations to work apps

Managed configurations let you provide your customers with the ability to pre-configure the apps that they’ve approved for deployment, and update those apps easily when the configuration needs to change. Configuring an app prior to deployment ensures that the organization’s security and other policies are met upon installation of the app on the target device.

The app capabilities are defined by the app developer in an XML schema (the managed configurations schema) that accompanies the app upon upload to Google Play (app developers, see Set Up Managed Configurations for details).

You retrieve this schema from the app to display for your customer admins in your EMM console, provide a UI in which the various options defined in the schema display, and enable admins to pre-configure the app’s settings. The resulting managed configuration set by the admin is typically stored on the EMM server which then uses the Play EMM API to set Managedconfigurationsfordevice or Managedconfigurationsforuser. See Managed Configurations through Play for details.

Managed configurations can be applied to the app by using the Play EMM API (recommended approach) or directly from the DPC (described in Apply managed configurations directly from the DPC). Using the Play EMM API has several advantages, including easy implementation because you can use the DPC Support Library to simplify DPC tasks. In addition, the Play EMM API:

  • Sets the configuration atomically when a new app is installed, thus ensuring the app is ready the first time the user launches the app.
  • Lets you manage configurations on a per-user basis, so you can avoid monitoring provisioning on a per-device basis.

Apply managed configurations using the Play EMM API

To use the Play EMM API for managed configurations, the DPC must allow the Google Play to set configurations. The DPC Support Library takes care of this task for you by proxying the configuration sent by Google Play.

To use the Play EMM API, download the DPC Support Library and then enable managed configurations support in your DPC.

Enable Managed Configurations support in your DPC

Import this class in your DPC:

com.google.android.apps.work.dpcsupport.ManagedConfigurationsSupport

Initialize the managed configurations library. In this example, "admin" is the ComponentName of the DeviceAdminReceiver.

Kotlin

var managedConfigurationsSupport = ManagedConfigurationsSupport(context, admin)

Java

ManagedConfigurationsSupport managedConfigurationsSupport =
    new ManagedConfigurationsSupport(context, admin);

Enable managed configurations:

Kotlin

managedConfigurationsSupport.enableManagedConfigurations()

Java

managedConfigurationsSupport.enableManagedConfigurations();

With this library initialized in your DPC, you can use the Google Play EMM API in your EMM console and server to apply managed configurations to approved apps, instead of coding these tasks directly in the DPC. See Managed Configurations through Play for details.

Apply managed configurations directly from the DPC

To change an app's configuration settings directly from the DPC, call the DevicePolicyManager.setApplicationRestrictions() method and pass parameters for the DPC app's DeviceAdminReceiver, the package name of the target app, and the Bundle comprising the app’s managed configuration as set by the admin. See How your DPC and EMM console interact and Set up Managed Configurations for details. However, note that this alternative approach to applying managed configurations is not recommended in managed Google Play Accounts deployments.

Managed Google Play Account provisioning support

The DPC Support Library includes support for provisioning managed Google Play Accounts. To use this support, you must first initialize the library, and then you can Ensure the working environment and Add a managed Google Play Account.

Initialize managed Google Play Accounts support in your DPC

Import this class in your DPC:

com.google.android.apps.work.dpcsupport.AndroidForWorkAccountSupport

Initialize the provisioning compatibility library. In this example, “admin” is the ComponentName of the DeviceAdminReceiver.

Kotlin

var androidForWorkAccountSupport = AndroidForWorkAccountSupport(context, admin)

Java

AndroidForWorkAccountSupport androidForWorkAccountSupport =
    new AndroidForWorkAccountSupport(context, admin);

Ensure the working environment for managed Google Play Accounts

After the DPC provisions a device in profile owner mode (ACTION_PROVISION_MANAGED_PROFILE) or device owner mode (ACTION_PROVISION_MANAGED_DEVICE), make sure that the device can support managed Google Play Accounts by calling:

Kotlin

androidForWorkAccountSupport.ensureWorkingEnvironment(callback)

Java

androidForWorkAccountSupport.ensureWorkingEnvironment(callback);

The callback reports the success or failure of this process. When the callback returns successfully, a managed Google Play Account can be added. If the callback reports an error, prompt the user to make sure the device has a network connection (for example, if the download fails). In other cases, report the failure to Google.

Kotlin

object : WorkingEnvironmentCallback() {
    override fun onSuccess() {
        // Can now provision the managed Google Play Account
    }
    override fun onFailure(error: Error) {
        // Notify user, handle error (check network connection)
    }
}

Java

new WorkingEnvironmentCallback() {
    @Override
    public void onSuccess() {
        // Can now provision the managed Google Play Account
    }

    @Override
    public void onFailure(Error error) {
        // Notify user, handle error (check network connection)
    }
}

Add a managed Google Play Account

The Android framework's AccountManager can add a managed Google Play Account to a device. To simplify interaction with AccountManager, use the helper function (shown in the example below) from the DPC Support Library. The function handles the token returned by Google Play server and facilitates provisioning the managed Google Play Account. The function returns when the managed Google Play Account is in a valid state:

Kotlin

androidForWorkAccountSupport.addAndroidForWorkAccount(token, accountAddedCallback)

Java

androidForWorkAccountSupport.addAndroidForWorkAccount(token, accountAddedCallback);
  • token—The user authentication token generated by the Google Play EMM API Users.generateAuthenticationToken() call.
  • accountAddedCallback—Returns the managed Google Play Account that was successfully added to the device. This callback should include onAccountReady() and onFailure() methods.

Kotlin

val workAccountAddedCallback = object : WorkAccountAddedCallback() {
    override fun onAccountReady(account: Account, deviceHint: String) {
        // Device account was successfully added to the device
        // and is ready to be used.
    }

    override fun onFailure(error: Error) {
        // The account was not successfully added. Check that the token
        // provided was valid (it expires after a certain period of time).
    }
}

Java

WorkAccountAddedCallback workAccountAddedCallback =
    new WorkAccountAddedCallback() {
        @Override
        public void onAccountReady(Account account, String deviceHint) {
            // Device account was successfully added to the device
            // and is ready to be used.
        }

        @Override
        public void onFailure(Error error) {
            // The account was not successfully added. Check that the token
            // provided was valid (it expires after a certain period of time).
        }
};
  • To learn more about the Device Administration API, see Device Administration.
  • To learn about Android Enterprise provisioning methods, see Provision devices in the Android Enterprise developer's guide.
  • For a GitHub sample that demonstrates how to create a basic work profile, see BasicManagedProfile.
  • For a GitHub sample that demonstrates how to set configurations on other apps as a profile owner, see AppRestrictionEnforcer.