Networking and telephony

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.

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.

  1. Install a VPN app that implements VpnService. You can find active VPN services by using an intent filter that matches the action VpnService.SERVICE_INTERFACE.
  2. Declare a VpnService in the app’s manifest guarded by the permission BIND_VPN_SERVICE.
  3. 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.
  4. 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 its setApplicationRestrictions() 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 using setAlwaysOnVpnPackage(), 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

  1. Verify the server’s identity:
    1. Load the X.509 CA certificates.
    2. Load the client’s private key and certificate. See Security with HTTPS and SSL for an example of how to read a certificate file.
  2. Create a new WifiConfiguration and set its SSID and key management.
  3. Set up the WifiEnterpriseConfig instance on this WifiConfiguration.
    1. Identify the server with a list of X509Certificate objects using setCaCertificates().
    2. Set the client credentials, identity, and password.
    3. Set the Extensible Authentication Protocol (EAP) and Phase 2 method as part of establishing the connection.
  4. Add the network with the WifiManager.
  5. 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
}