裝置管理員淘汰資料。裝置管理員叫用的部分管理員政策,已將其標示為已淘汰。詳情請參閱「 裝置管理員淘汰項目」一文,進一步瞭解及遷移選項。
從 Android 2.2 (API 級別 8) 開始,Android 平台透過 Device Administration API 提供系統層級的裝置管理功能。
在本課程中,您將學到如何建立安全感知應用程式,藉由強制執行裝置管理政策,管理內容存取權。具體來說,您可以透過設定,讓應用程式在向使用者顯示受限制的內容前,確保已設定充分強度的螢幕鎖定密碼。
定義並宣告政策
首先,您必須在功能層級定義支援的政策類型。政策可能涵蓋螢幕鎖定密碼強度、到期時間逾時、加密等政策。
您必須在 res/xml/device_admin.xml
檔案中宣告所選政策集,應用程式將強制執行這些政策。Android 資訊清單也應參照宣告的政策集。
每項宣告的政策都會對應到 DevicePolicyManager
中一些相關的裝置政策方法 (定義密碼長度下限和大寫字元數下限是兩個範例)。如果應用程式嘗試叫用方法中未宣告相對應的政策,這會導致執行階段發生 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); }
此時,應用程式即可強制執行政策。雖然應用程式無法存取實際螢幕鎖定密碼,但可透過 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)); }