网络和电话

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

DPC 应用可以在个人设备上以资料所有者模式运行,或在完全托管设备上以设备所有者模式运行。此表指示当 DPC 在资料所有者模式或设备所有者模式下运行时哪些功能可用。

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

跨资料访问工作联系人

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

与系统 UI 集成

系统 UI 使用公文包图标指示来电。 callLog 也显示该图标,用于指示呼入和呼出的工作电话。个人拨号器和联系人应用可以使用远程目录查找显示工作联系人的来电显示信息,因此无需将联系人同步到本地设备。消息应用可以执行本地来电显示和搜索。

Android 兼容性定义文档 (CDD) 包含在默认拨号器中显示工作联系人的要求,以及联系人和消息应用需要带有徽章以表明它们来自工作资料的要求。

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

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

控制主要资料中的工作联系人

DPC 控制搜索工作联系人的权限。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. 在应用的清单中声明一个由权限 BIND_VPN_SERVICE 保护的 VpnService
  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 证书以识别服务器

要指定一个 X.509 证书列表,用于使用相同的 SSID 识别服务器,请使用 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) 和第二阶段方法作为建立连接的一部分。
  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
}