设备管理弃用。部分管理员政策在由设备管理员调用时被标记为已弃用。如需了解详情并查看迁移选项,请参阅 设备管理弃用。
从 Android 2.2(API 级别 8)开始,Android 平台通过设备管理 API 提供系统级设备管理功能。
在本课中,您将学习如何创建一个安全感知型应用,通过强制执行设备管理政策来管理对其内容的访问权限。具体来说,可以对应用进行配置,以确保在向用户显示受限内容之前设置足够强度的屏幕锁定密码。
定义并声明政策
首先,您需要定义要在职能层面支持的政策类型。政策可能涵盖屏幕锁定密码强度、失效超时和加密等。
您必须在 res/xml/device_admin.xml
文件中声明所选政策集,该政策集将由应用强制执行。Android 清单还应引用声明的政策集。
每项声明的政策都对应于 DevicePolicyManager
中一定数量的相关设备政策方法(定义密码最小长度和最小大写字符数量是两个示例)。如果应用尝试调用的方法未在 XML 中声明相应政策,则会导致在运行时出现 SecurityException
。如果应用打算管理其他类型的政策,则还可以使用 force-lock
等其他权限。稍后您将看到,在设备管理员激活过程中,系统会在系统屏幕上向用户显示已声明的政策列表。
以下代码段在 res/xml/device_admin.xml
中声明了限制密码政策:
<device-admin xmlns:android="http://schemas.android.com/apk/res/android"> <uses-policies> <limit-password /> </uses-policies> </device-admin>
Android 清单中引用的政策���明 XML:
<receiver android:name=".Policy$PolicyAdmin" android:permission="android.permission.BIND_DEVICE_ADMIN"> <meta-data android:name="android.app.device_admin" android:resource="@xml/device_admin" /> <intent-filter> <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" /> </intent-filter> </receiver>
创建设备管理接收器
创建设备管理功能广播接收器,用于接收与您声明支持的政策相关的事件的通知。应用可以选择性地替换回调方法。
在示例应用 Device Admin 中,当用户停用设备管理员时,已配置的政策会从共享偏好设置中清除。您应考虑实现与您的用例相关的业务逻辑。例如,应用可能会采取一些操作来降低安全风险,方法是实现删除设备上的敏感数据、停用远程同步、提醒管理员等某种组合。
为了让广播接收器正常工作,请确保在 Android 清单中注册它,如上面的代码段所示。
Kotlin
class PolicyAdmin : DeviceAdminReceiver() { override fun onDisabled(context: Context, intent: Intent) { // Called when the app is about to be deactivated as a device administrator. // Deletes previously stored password policy. super.onDisabled(context, intent) context.getSharedPreferences(APP_PREF, Activity.MODE_PRIVATE).edit().apply { clear() apply() } } }
Java
public static class PolicyAdmin extends DeviceAdminReceiver { @Override public void onDisabled(Context context, Intent intent) { // Called when the app is about to be deactivated as a device administrator. // Deletes previously stored password policy. super.onDisabled(context, intent); SharedPreferences prefs = context.getSharedPreferences(APP_PREF, Activity.MODE_PRIVATE); prefs.edit().clear().commit(); } }
激活设备管理器
在强制执行任何政策之前,用户都需要以设备管理员的身份手动激活应用。以下代码段说明了如何触发设置 activity,供用户激活您的应用。一种很好的做法是在 intent 中指定 EXTRA_ADD_EXPLANATION
extra,从而添加说明文本来向用户强调应用为何请求成为设备管理员。
图 1. 用户激活屏幕,您可以在该屏幕上提供设备政策的说明。
Kotlin
if (!policy.isAdminActive()) { val activateDeviceAdminIntent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) activateDeviceAdminIntent.putExtra( DevicePolicyManager.EXTRA_DEVICE_ADMIN, policy.getPolicyAdmin() ) // It is good practice to include the optional explanation text to // explain to user why the application is requesting to be a device // administrator. The system will display this message on the activation // screen. activateDeviceAdminIntent.putExtra( DevicePolicyManager.EXTRA_ADD_EXPLANATION, resources.getString(R.string.device_admin_activation_message) ) startActivityForResult(activateDeviceAdminIntent, REQ_ACTIVATE_DEVICE_ADMIN) }
Java
if (!policy.isAdminActive()) { Intent activateDeviceAdminIntent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); activateDeviceAdminIntent.putExtra( DevicePolicyManager.EXTRA_DEVICE_ADMIN, policy.getPolicyAdmin()); // It is good practice to include the optional explanation text to // explain to user why the application is requesting to be a device // administrator. The system will display this message on the activation // screen. activateDeviceAdminIntent.putExtra( DevicePolicyManager.EXTRA_ADD_EXPLANATION, getResources().getString(R.string.device_admin_activation_message)); startActivityForResult(activateDeviceAdminIntent, REQ_ACTIVATE_DEVICE_ADMIN); }
如果用户选择“启用”,应用会成为设备管理员,并且可以开始配置和强制执行该政策。
应用还需要做好准备,以应对用户通过点击“Cancel”按钮、“返回”键或“主屏幕”键放弃激活流程的预设情况。因此,政策设置 activity 中的 onResume()
需要具有用于重新评估条件的逻辑,并在需要时向用户显示设备管理员激活选项。
实现设备政策控制器
成功激活设备管理器后,应用会使用请求的政策配置设备政策管理器。请注意,Android 会随每个版本添加新的政策。如果在支持旧版平台的情况下使用新政策,则最好在应用中执行版本检查。例如,“密码最小大小写”政策仅适用于 API 级别 11 (Honeycomb) 及更高级别。以下代码演示了如何在运行时检查版本。
Kotlin
private lateinit var dpm: DevicePolicyManager private lateinit var policyAdmin: ComponentName dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager policyAdmin = ComponentName(context, PolicyAdmin::class.java) dpm.apply { setPasswordQuality(policyAdmin, PASSWORD_QUALITY_VALUES[passwordQuality]) setPasswordMinimumLength(policyAdmin, passwordLength) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { setPasswordMinimumUpperCase(policyAdmin, passwordMinUpperCase) } }
Java
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); ComponentName policyAdmin = new ComponentName(context, PolicyAdmin.class); dpm.setPasswordQuality(policyAdmin, PASSWORD_QUALITY_VALUES[passwordQuality]); dpm.setPasswordMinimumLength(policyAdmin, passwordLength); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { dpm.setPasswordMinimumUpperCase(policyAdmin, passwordMinUpperCase); }
此时,应用能够强制执行该政策。虽然应用无法访问实际所用的屏幕锁定密码,但可以通过 Device Policy Manager API 确定现有密码是否符合要求的政策。如果发现现有的屏幕锁定密码不够用,则设备管理 API 不会自动采取纠正措施。该应用负责在“设置”应用中明确启动系统密码更改屏幕。例如:
Kotlin
if (!dpm.isActivePasswordSufficient) { // Triggers password change screen in Settings. Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD).also { intent -> startActivity(intent) } }
Java
if (!dpm.isActivePasswordSufficient()) { ... // Triggers password change screen in Settings. Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); startActivity(intent); }
通常,用户可以从可用的锁定机制中选择一种,例如“无”“图案”“PIN 码(数字)”或“密码”(字母数字)。配置密码政策时,会停用弱于政策中所定义密码类型的密码类型。例如,如果配置了“数字”密码质量,则用户可以仅选择 PIN 码(数字)或密码(字母数字)密码。
通过设置适当的屏幕锁定密码来妥善保护设备后,应用将允许访问受保护的内容。
Kotlin
when { !dpm.isAdminActive(policyAdmin) -> { // Activates device administrator. ... } !dpm.isActivePasswordSufficient -> { // Launches password set-up screen in Settings. ... } else -> { // Grants access to secure content. ... startActivity(Intent(context, SecureActivity::class.java)) } }
Java
if (!dpm.isAdminActive(..)) { // Activates device administrator. ... } else if (!dpm.isActivePasswordSufficient()) { // Launches password set-up screen in Settings. ... } else { // Grants access to secure content. ... startActivity(new Intent(context, SecureActivity.class)); }