网络和电话

本指南中的功能描述了您可以在 设备策略控制器 (DPC) 应用中实现的网络和电话管理功能。本文档包含代码示例,您还可以使用 Test DPC 应用作为 Android 企业功能示例代码的来源。

DPC 应用可以在个人设备上的配置文件所有者模式或完全托管设备上的设备所有者模式下运行。此表指示 DPC 在 配置文件所有者模式或设备所有者模式下运行时可用的功能。

功能 配置文件所有者 设备所有者
跨配置文件访问工作联系人
确保工作流量的安全网络连接
在各个区域设置单个无线网络 ID
为工作配置文件指定单独的拨号器

跨配置文件访问工作联系人

EMM 可以允许用户个人资料访问其工作联系人,以便用户可以通过本地搜索和远程目录查找访问其个人和工作联系人。在个人设备上,个人资料中的单个拨号器可以拨打和接听个人电话以及工作电话。此外,工作联系人已很好地集成到系统 UI 中。如果工作配置文件已加密,则其数据对个人资料不可用。

与系统 UI 集成

系统 UI 使用公文包图标指示传入的工作呼叫。该 callLog 还显示图标以指定传入和传出的工作呼叫。个人拨号器和联系人应用可以使用远程目录查找显示工作联系人的呼叫者 ID 信息,因此不需要在本地设备上已同步联系人。消息应用可以进行本地呼叫者 ID 和搜索。

Android 兼容性定义文档 (CDD) 包括在默认拨号器中显示工作联系人的要求,以及联系人应用和消息应用带徽章以指示它们来自工作配置文件的要求。

工作联系人可访问且可搜索

用户可以从其个人资料访问和拨打工作联系人,这些联系人显示在拨号器应用的搜索屏幕中。用户可以使用自动完成功能搜索本地同步到设备的工作联系人,并通过远程目录查找列出。

控制主配置文件中的工作联系人

DPC 控制搜索工作联系人的权限。在配置文件所有者模式下运行时,DPC 管理工作联系人对个人资料的可见性。有关更多信息,请参阅 构建设备策略控制器

默认情况下启用个人资料搜索工作联系人。

确保工作流量的安全网络连接

在设备拥有者模式或配置文件拥有者模式下运行时,设备策略控制器 (DPC) 可以使用始终在线虚拟专用网络 (VPN) 连接强制应用程序通过指定的 VPN 应用传递流量,并且无法绕过。通过使用始终在线 VPN 连接,DPC 可以确保工作配置文件或托管设备的网络流量通过 VPN 服务,并且无需用户干预。此过程为工作配置文件内的持续流量创建安全的网络连接。

关于始终在线 VPN 连接

作为系统框架的一部分,VPN 路由会自动管理,因此用户无法绕过 VPN 服务。如果设备处于锁定模式且 VPN 服务断开连接,则流量无法泄漏到开放的互联网。对于实现 VpnService 的应用程序,始终在线 VPN 提供了一个框架,用于通过受信任的服务器管理安全的 VPN 连接并保持其连接状态。无论连接是通过 Wi-Fi 还是蜂窝网络,VPN 服务都会在应用程序更新期间自动重新启动连接。如果设备重新启动,框架也会重新启动 VPN 连接。

用户无法感知到与 VPN 服务的连接。对于公司拥有的设备,用户无需在始终在线模式下确认 VPN 的同意对话框。用户的 VPN 网络设置允许手动启用始终在线连接。

如果 DISALLOW_CONFIG_VPNtrue,则用户将无法配置 VPN。启用 DISALLOW_DEBUGGING_FEATURES 以限制用户使用 adb 调试命令覆盖始终在线 VPN。要阻止用户卸载 VPN,请调用 DevicePolicyManager.setUninstallBlocked

设置 VPN 服务

使用您的 Android 企业解决方案的组织将设置 VPN。

  1. 安装一个实现了 VpnService 的 VPN 应用。您可以使用与操作 VpnService.SERVICE_INTERFACE 匹配的意图过滤器查找活动的 VPN 服务。
  2. 在应用的清单中声明一个 VpnService,并由权限 BIND_VPN_SERVICE 保护。
  3. 配置 VpnService,使其由系统启动。避免通过侦听系统启动并控制其自身生命周期来设置 VPN 应用自行启动。
  4. 设置 VPN 应用的 托管配置(请参阅下面的 示例)。

启用始终在线 VPN 连接

DPC 可以通过调用 DevicePolicyManager.setAlwaysOnVpnPackage() 通过特定应用配置始终在线 VPN 连接。

此连接会自动授予并在重新启动后持续存在。如果 lockdownEnabled 为 false,则从手机重新启动到 VPN 连接这段时间网络流量可能是不安全的。如果您不希望在 VPN 失败时停止网络连接,或者 VPN 不是必需的,这将很有用。

验证始终在线 VPN 连接

DPC 可以使用 DevicePolicyManager.getAlwaysOnVpnPackage(). 读取当前用户管理始终在线 VPN 连接的软件包的名称。

如果没有这样的软件包,或者 VPN 是在系统“设置”应用中创建的,则返回 null

示例

TestDPC 应用中,AlwaysOnVpnFragment.java 使用这些 API 启用始终在线 VPN 连接的设置。

在以下示例中

  • VPN 服务的 托管配置DevicePolicyManager 使用其 setApplicationRestrictions() 方法设置。
  • 托管配置使用任意键值对,此示例应用在其他地方使用它们来配置 VPN 的网络设置(请参阅 检查托管配置)。
  • 此示例将 Android 软件包安装程序添加到拒绝列表中,以便它不会通过 VPN 更新系统软件包。用户在工作配置文件或设备中的所有网络流量都将通过此 VPN 应用,但软件包安装程序除外;其更新使用开放的互联网。
  • 然后,DevicePolicyManager 使用 setAlwaysOnVpnPackage() 启用 VPN 软件包的始终在线 VPN 连接,并启用锁定模式。

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(...);
}

在各个区域设置单个无线网络 ID

在设备拥有者模式或配置文件拥有者模式下运行时,设备策略控制器 (DPC) 可以将多个证书颁发机构 (CA) 证书与单个无线网络配置相关联。通过此配置,设备可以连接到具有相同网络名称或服务集标识符 (SSID) 但配置了不同 CA 证书的无线接入点。如果您的组织的无线网络位于多个地理区域,并且每个区域都需要不同的证书颁发机构,这将非常有用。例如,法律签名可能需要一个需要区域 CA 的本地机构。

注意:Android 自 API 18(Jelly Bean)起就支持 setCaCertificate,但 IT 管理员必须使用每个 CA 分别预配其网络,以确保设备在每个接入点都能实现无缝身份验证,而无论其所在区域。

指定 CA 证书以识别服务器

要指定用于识别使用相同 SSID 的服务器的 X.509 证书列表,请使用 WifiEnterpriseConfig.setCaCertificates() 将所有相关 CA 包含在无线配置中。

如果服务器的 CA 与给定的证书之一匹配,则该服务器的证书有效。默认名称会自动分配给证书并在配置中使用。当网络启用时,WifiManager 会安装证书并自动保存配置,并在删除配置时删除证书。

要获取与无线配置关联的所有 CA 证书,请使用 WifiEnterpriseConfig.getCaCertificates() 返回 X509Certificate 对象列表。

使用多个 CA 证书添加无线配置

  1. 验证服务器的身份
    1. 加载 X.509 CA 证书。
    2. 加载客户端的私钥和证书。有关如何读取证书文件的示例,请参阅 使用 HTTPS 和 SSL 保护安全
  2. 创建一个新的 WifiConfiguration 并设置其 SSID 和密钥管理。
  3. 在此 WifiConfiguration 上设置 WifiEnterpriseConfig 实例。
    1. 使用 setCaCertificates() 使用 X509Certificate 对象列表识别服务器。
    2. 设置客户端凭据、身份和密码。
    3. 将可扩展身份验证协议 (EAP) 和第 2 阶段方法设置为建立连接的一部分。
  4. 使用 WifiManager 添加网络。
  5. 启用网络。WifiManager 在设置过程中会自动保存配置。

此示例将这些步骤结合在一起

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);
}

为工作配置文件指定单独的拨号器

您可以允许将单独的拨号程序应用用于工作配置文件。这可以是拨号程序本身,也可以是实现了 ConnectionService API 用于呼叫后端的 Voice over IP (VoIP) 应用。这为工作配置文件中的 VoIP 应用提供了相同的集成系统 UI 拨号体验,有效地将工作拨号程序作为核心功能。呼叫到工作呼叫帐户的传入呼叫与呼叫到个人呼叫帐户的传入呼叫区分开来。

用户可以选择通过允许的工作拨号程序拨打和接听电话帐户的电话。通过该拨号程序拨打的或呼叫到工作电话帐户的传入呼叫均记录在工作配置文件的 CallLog 提供程序中。工作拨号程序维护一个仅工作通话记录,并且只能访问工作联系人。传入的电路交换呼叫由主拨号程序处理并存储在个人通话记录中。如果删除工作配置文件,则与该工作配置文件关联的通话记录也将被删除,就像所有工作配置文件数据一样。

第三方应用必须实现 ConnectionService

需要拨打电话并将这些电话集成到内置电话应用中的第三方 VoIP 应用可以实现 ConnectionService API。这是用于工作呼叫的任何 VoIP 服务所必需的。这些应用可以通过将其呼叫视为传统的蜂窝呼叫来获益,例如,它们显示在内置的系统拨号程序和通话记录中。如果在工作配置文件中安装了实现 ConnectionService 的应用,则该应用只能由也安装在该工作配置文件中的拨号程序访问。

开发人员实现 ConnectionService 后,应将其添加到应用的清单文件中,并使用 TelecomManager 注册一个 PhoneAccount。电话帐户表示一种拨打或接听电话的不同方法,每个 ConnectionService 可以有多个 PhoneAccounts。注册电话帐户后,用户可以通过拨号程序设置启用它。

系统 UI 集成和通知

系统 UI 为用户提供一致且集成的拨号体验,适用于使用 ConnectionService API 作为后端进行呼叫的第三方应用。如果在工作配置文件中使用应用,则在来电和状态栏中会显示公文包图标。安装在工作配置文件中的实现 ConnectionService 的应用可以使用系统拨号器或构建单独的工作拨号器。这些可以是单个应用或单独的应用。

拨号器应用通过检查标志 android.telecom.Call.Details.PROPERTY_ENTERPRISE_CALL 来确定它是在进行工作呼叫还是接收工作呼叫。如果呼叫是工作呼叫,则拨号器会通过添加工作徽章(公文包图标)来向用户指示这一点。

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
}