设备管理员弃用。当由设备管理员调用时,某些管理员策略已被标记为已弃用。要了解详细信息并查看迁移选项,请参阅 设备管理员弃用。
从 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>
创建设备管理接收器
创建一个设备管理广播接收器,该接收器会收到与您声明支持的策略相关的事件通知。应用程序可以选择性地覆盖回调方法。
在示例应用程序“设备管理员”中,当设备管理员被用户停用时,配置的策略将从共享首选项中删除。您应该考虑实现与您的用例相关的业务逻辑。例如,应用程序可能会采取一些措施来通过实现某些删除设备上的敏感数据、禁用远程同步、提醒管理员等的组合来降低安全风险。
为了使广播接收器正常工作,请确保将其注册在 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(); } }
激活设备管理员
在执行任何策略之前,用户需要手动将应用程序激活为设备管理员。下面的代码段说明了如何触发设置活动,用户可以在其中激活您的应用程序。最好包含说明性文本以突出显示应用程序为何请求成为设备管理员,方法是在意图中指定 EXTRA_ADD_EXPLANATION
额外信息。
图 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); }
如果用户选择“激活”,则应用程序将成为设备管理员,并且可以开始配置和执行策略。
应用程序还需要做好准备来处理用户通过点击“取消”按钮、后退键或主页键而放弃激活过程的回退情况。因此,策略设置活动中的 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); }
此时,应用程序能够执行策略。虽然应用程序无法访问实际使用的屏幕锁定密码,但它可以通过设备策略管理器 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)); }