网络和电话

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

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

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

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

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

与系统 UI 集成

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

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

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

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

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

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

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

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

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

关于始终在线 VPN 连接

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