The features in this guide describe networking and telephony management capabilities you can implement in your device policy controller (DPC) app. This document contains code samples and you can also use the Test DPC app as a source of sample code for Android's enterprise features.
A DPC app can run in profile owner mode on personal devices or in device owner mode on fully managed devices. This table indicates which features are available when the DPC runs in profile owner mode or device owner mode:
Feature | Profile owner | Device owner |
Access work contacts across profiles | ✓ | |
Ensure a secure network connection for work traffic | ✓ | ✓ |
Set up a single wireless network ID across regions | ✓ | ✓ |
Specify a separate dialer for the work profile | ✓ |
Access work contacts across profiles
EMMs can allow a user’s personal profile to access their work contacts so that a user’s personal and work contacts are accessible through local search and remote directory lookup. On personal devices, a single dialer in the personal profile can make and receive personal calls as well as work calls. In addition, work contacts are well integrated into the system UI. If the work profile is encrypted, its data isn’t available to the personal profile.
Integrated with system UI
The system UI indicates incoming work calls using a briefcase icon. The
callLog
also shows the
icon to designate incoming and outgoing work calls. The personal dialer and
contact apps can display a work contact’s caller ID information using a remote
directory lookup, so it isn’t required that the contact is already synced on the
local device. The messaging app can do local caller ID and search.
The Android Compatibility Definition Document (CDD) includes requirements for work contacts to display in the default dialer, and requirements that contacts and messaging apps are badged to indicate they’re from the work profile.
Work contacts are accessible and searchable
The user can access and call work contacts from their personal profile, which display in the search screen of the dialer app. The user can search for work contacts—using autocomplete—that are synced locally to the device, and listed through a remote directory lookup.
Control work contacts in the primary profile
The DPC controls the permission to search work contacts. Running in profile owner mode, the DPC manages the visibility of work contacts in the personal profile. For more information, see Build a device policy controller.
Searching work contacts by the personal profile is enabled by default.
To see how the policy is set, use
DevicePolicyManager.getCrossProfileContactsSearchDisabled()
.To enable or disable searching work contacts by the personal profile, use
DevicePolicyManager.setCrossProfileContactsSearchDisabled()
.
Ensure a secure network connection for work traffic
Running in either a device owner mode or profile owner mode, a device policy controller can use an always-on Virtual Private Network (VPN) connection to force applications to pass traffic through a specified VPN app that can’t be bypassed. Using an always-on VPN connection, the DPC can ensure that network traffic from a work profile or managed device passes through a VPN service, and without user intervention. This process creates a secure network connection for continual traffic within a work profile.
About always-on VPN connections
As part of the system framework, VPN routing is automatically managed so the
user can’t bypass the VPN service. If the VPN service is disconnected while in
lockdown mode, traffic can’t leak to the open Internet. For applications
implementing
VpnService
,
always-on VPN provides a framework for managing a secure VPN connection through
a trusted server and keeping it up. The VPN service automatically restarts the
connection across app updates, regardless if the connection is over Wi-Fi or
cellular. And if the device reboots, the framework restarts the VPN connection.
The connection to the VPN service is transparent to the user. For a company-owned device, the user isn’t required to confirm a consent dialog for a VPN in always-on mode. The user’s VPN network settings allow for enabling an always-on connection manually.
If DISALLOW_CONFIG_VPN
is true
, the user is prevented from configuring the VPN. Enable
DISALLOW_DEBUGGING_FEATURES
to restrict users from overriding the always-on VPN using the adb debug command.
To prevent a user from uninstalling the VPN, call
DevicePolicyManager.setUninstallBlocked
.
Set up the VPN service
The organization that uses your enterprise solution for Android sets up VPN.
- Install a VPN app that implements
VpnService
. You can find active VPN services by using an intent filter that matches the actionVpnService.SERVICE_INTERFACE
. - Declare a
VpnService
in the app’s manifest guarded by the permissionBIND_VPN_SERVICE
. - Configure the
VpnService
so it’s started by the system. Avoid setting the VPN app to start itself by listening for a system boot and controlling its own life cycle. - Set the managed configurations for the VPN app (see example below).
Enable the always-on VPN connection
The DPC can configure an always-on VPN connection through a specific app by
calling
DevicePolicyManager.setAlwaysOnVpnPackage()
.
This connection is automatically granted and persists after a reboot. If
lockdownEnabled
is false, network traffic may be unsecured from the time the
phone reboots and the VPN connects. This is useful if you don’t want to stop
network connectivity whenever the VPN fails, or if the VPN is not essential.
Verify the always-on VPN connection
The DPC can read the name of the package administering an always-on VPN
connection for the current user with
DevicePolicyManager.getAlwaysOnVpnPackage().
If there’s no such package, or the VPN was created within the system Settings
app, null
is returned.
Example
In the TestDPC app, AlwaysOnVpnFragment.java
uses these APIs to enable the setting for an always-on VPN connection.
In the following example:
- The managed
configurations of the
VPN service are set by the
DevicePolicyManager
using itssetApplicationRestrictions()
method. - Managed configurations use arbitrary key-value pairs and this example app uses them elsewhere to configure the VPN’s network settings (see Check Managed Configurations).
- The example adds the Android package installer to a denylist so it doesn’t update system packages over the VPN. All of the user’s network traffic within the work profile or device goes through this VPN app, except the package installer; its updates use the open Internet.
- The
DevicePolicyManager
then enables the always-on VPN connection for the VPN package usingsetAlwaysOnVpnPackage()
, and enabling lockdown mode.
Kotlin
// Set VPN's managed configurations val config = Bundle().apply { putString(Extras.VpnApp.ADDRESS, "192.0.2.0") putString(Extras.VpnApp.IDENTITY, "vpn.account1") putString(Extras.VpnApp.CERTIFICATE, "keystore://auth_certificate") putStringArray(Extras.VpnApp.DENYLIST, arrayOf("com.android.packageinstaller")) } val dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager val admin = myDeviceAdminReceiver.getComponentName(this) // Name of package to update managed configurations val vpnPackageName = "com.example.vpnservice" // Associate managed configurations with DeviceAdminReceiver dpm.setApplicationRestrictions(admin, vpnPackageName, config) // Enable always-on VPN connection through VPN package try { val lockdownEnabled = true dpm.setAlwaysOnVpnPackage(admin, vpnPackageName, lockdownEnabled) } catch (ex: Exception) { throw PolicyException() }
Java
// Set VPN's managed configurations final Bundle config = new Bundle(); config.putString(Extras.VpnApp.ADDRESS, "192.0.2.0"); config.putString(Extras.VpnApp.IDENTITY, "vpn.account1"); config.putString(Extras.VpnApp.CERTIFICATE, "keystore://auth_certificate"); config.putStringArray(Extras.VpnApp.DENYLIST, new String[]{"com.android.packageinstaller"}); DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); ComponentName admin = myDeviceAdminReceiver.getComponentName(this); // Name of package to update managed configurations final String vpnPackageName = "com.example.vpnservice"; // Associate managed configurations with DeviceAdminReceiver dpm.setApplicationRestrictions(admin, vpnPackageName, config); // Enable always-on VPN connection through VPN package try { boolean lockdownEnabled = true; dpm.setAlwaysOnVpnPackage(admin, vpnPackageName, lockdownEnabled)); } catch (Exception ex) { throw new PolicyException(...); }
Set up a single wireless network ID across regions
Running in either a device owner mode or profile owner mode, a device policy controller (DPC) can associate multiple certificate authority (CA) certificates with a single wireless network configuration. With this configuration, a device can connect to wireless access points that have the same network name, or service set identifier (SSID), but are configured with different CA certificates. This is useful if your organization’s wireless networks are located across multiple geographic regions, and each region requires a different certificate authority. For example, legal signatures can require a local authority that needs a regional CA.
Note: Android has supported
setCaCertificate
since API 18 (Jelly Bean), but IT admins must provision their networks
separately with each CA to ensure devices have seamless authentication at each
access point, regardless of their region.
Specify CA certificates to identify the server
To specify a list of X.509 certificates that identify the server using the same
SSID, include all relevant CAs in the wireless configuration using WifiEnterpriseConfig.setCaCertificates()
.
A server’s certificate is valid if its CA matches one of the given certificates.
Default names are automatically assigned to the certificates and used within the
configuration. The
WifiManager
installs the certificate and automatically saves the configuration when the
network is enabled, and removes the certificate when the configuration is
deleted.
To get all the CA certificates associated with the wireless configuration, use
WifiEnterpriseConfig.getCaCertificates()
to return a list of
X509Certificate
objects.
Add a wireless configuration using multiple CA certificates
- Verify the server’s identity:
- Load the X.509 CA certificates.
- Load the client’s private key and certificate. See Security with HTTPS and SSL for an example of how to read a certificate file.
- Create a new
WifiConfiguration
and set its SSID and key management. - Set up the
WifiEnterpriseConfig
instance on thisWifiConfiguration
.- Identify the server with a list of
X509Certificate
objects usingsetCaCertificates()
. - Set the client credentials, identity, and password.
- Set the Extensible Authentication Protocol (EAP) and Phase 2 method as part of establishing the connection.
- Identify the server with a list of
- Add the network with the
WifiManager
. - Enable the network. WifiManager automatically saves the configuration during setup.
This example ties the steps together:
Kotlin
// Verify the server's identity val caCert0 = getCaCert("cert0.crt") val caCert1 = getCaCert("cert1.crt") val clientKey = getClientKey() val clientCert = getClientCert() // Create Wi-Fi configuration val wifiConfig = WifiConfiguration().apply { SSID = "mynetwork" allowedKeyManagement.set(KeyMgmt.WPA_EAP) allowedKeyManagement.set(KeyMgmt.IEEE8021X) // Set up Wi-Fi enterprise configuration enterpriseConfig.setCaCertificates(arrayOf<X509Certificate>(caCert0, caCert1)) enterpriseConfig.setClientKeyEntry(clientKey, clientCert) enterpriseConfig.setIdentity("myusername") enterpriseConfig.setEapMethod(Eap.TLS) enterpriseConfig.setPhase2Method(Phase2.NONE) } // Add network val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager val netId = wifiManager.addNetwork(wifiConfig) // Enable network if (netId < 0) { // Error creating new network } else { wifiManager.enableNetwork(netId, true) }
Java
// Verify the server's identity X509Certificate caCert0 = getCaCert("cert0.crt"); X509Certificate caCert1 = getCaCert("cert1.crt"); PrivateKey clientKey = getClientKey(); X509Certificate clientCert = getClientCert(); // Create Wi-Fi configuration WifiConfiguration wifiConfig = new WifiConfiguration(); wifiConfig.SSID = "mynetwork"; wifiConfig.allowedKeyManagement.set(KeyMgmt.WPA_EAP); wifiConfig.allowedKeyManagement.set(KeyMgmt.IEEE8021X); // Set up Wi-Fi enterprise configuration wifiConfig.enterpriseConfig.setCaCertificates(new X509Certificate[] {caCert0, caCert1}); wifiConfig.enterpriseConfig.setClientKeyEntry(clientKey, clientCert); wifiConfig.enterpriseConfig.setIdentity("myusername"); wifiConfig.enterpriseConfig.setEapMethod(Eap.TLS); wifiConfig.enterpriseConfig.setPhase2Method(Phase2.NONE); // Add network WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); int netId = wifiManager.addNetwork(wifiConfig); // Enable network if (netId < 0) { // Error creating new network } else { wifiManager.enableNetwork(netId, true); }
Specify a separate dialer for the work profile
You can allowlist a separate dialer application to be used in a work profile.
This can be the dialer itself, or a Voice over IP (VoIP) app that implements the
ConnectionService
API for the calling backend. This provides the same integrated system UI dialing
experience to VoIP applications in the work profile, effectively making the work
dialer a core feature. Incoming calls to the work calling accounts are
differentiated from incoming calls to the personal calling accounts.
The user can choose to make and receive calls from the allowlisted work dialer
on a phone account. All calls made from that dialer, or incoming to the work
phone account, are recorded in the work profile’s
CallLog
provider. The work dialer maintains a work-only call log with only access to
work contacts. Incoming circuit-switch calls are handled by the primary dialer
and stored in a personal call log. If a work profile is deleted, the call log
associated with that work profile is deleted as well, as with all work profile
data.
Third-party apps must implement ConnectionService
Third-party VoIP apps that need to make phone calls and have those calls
integrated into the built-in phone app can implement the
ConnectionService
API. This is required for any VoIP service used for work calling. These apps
benefit by having their calls treated like traditional cellular calls, for
example, they show up in the built-in system dialer and the call log. If the
app implementing
ConnectionService
is installed in the work profile, it is only accessible by a dialer also
installed in that work profile.
Once the developer has implemented
ConnectionService
,
they should add it to the app’s manifest file and register a
PhoneAccount
with the
TelecomManager
.
A phone account represents a distinct method to place or receive phone calls,
and there can be multiple PhoneAccounts
for each
ConnectionService
. After the phone account is registered, the user
can enable it through the dialer settings.
System UI integration and notifications
The system UI provides users with a consistent and integrated dialing experience
for third-party apps that use the
ConnectionService
API as a backend to make calls. If using the app in a work profile, a briefcase
icon displays on incoming calls and in the status bar. An app that implements
ConnectionService
that is installed in the work profile can use the
system dialer or build a separate work dialer. These can be a single app or
separate apps.
The dialer application determines if it’s making or receiving a work call by
checking for the flag
android.telecom.Call.Details.PROPERTY_ENTERPRISE_CALL
.
If the call is a work call, the dialer indicates this to the user by adding a
work badge (the briefcase icon):
Kotlin
// Call placed through a work phone account. getCurrentCall() is defined by the // dialer. val call = getCurrentCall() if (call.hasProperty(android.telecom.Call.Details.PROPERTY_ENTERPRISE_CALL)) { // Set briefcase icon }
Java
// Call placed through a work phone account. getCurrentCall() is defined by the // dialer. Call call = getCurrentCall(); if (call.hasProperty(android.telecom.Call.Details.PROPERTY_ENTERPRISE_CALL)) { // Set briefcase icon }