1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.apis.app; 18 19 import com.example.android.apis.R; 20 21 import android.app.ActivityManager; 22 import android.app.AlertDialog; 23 import android.app.admin.DeviceAdminReceiver; 24 import android.app.admin.DevicePolicyManager; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.Intent; 29 import android.os.Bundle; 30 import android.preference.CheckBoxPreference; 31 import android.preference.EditTextPreference; 32 import android.preference.ListPreference; 33 import android.preference.Preference; 34 import android.preference.Preference.OnPreferenceChangeListener; 35 import android.preference.Preference.OnPreferenceClickListener; 36 import android.preference.PreferenceActivity; 37 import android.preference.PreferenceCategory; 38 import android.preference.PreferenceFragment; 39 import android.preference.PreferenceScreen; 40 import android.text.TextUtils; 41 import android.util.Log; 42 import android.widget.Toast; 43 44 import java.util.List; 45 46 /** 47 * This activity provides a comprehensive UI for exploring and operating the DevicePolicyManager 48 * api. It consists of two primary modules: 49 * 50 * 1: A device policy controller, implemented here as a series of preference fragments. Each 51 * one contains code to monitor and control a particular subset of device policies. 52 * 53 * 2: A DeviceAdminReceiver, to receive updates from the DevicePolicyManager when certain aspects 54 * of the device security status have changed. 55 */ 56 public class DeviceAdminSample extends PreferenceActivity { 57 58 // Miscellaneous utilities and definitions 59 private static final String TAG = "DeviceAdminSample"; 60 61 private static final int REQUEST_CODE_ENABLE_ADMIN = 1; 62 private static final int REQUEST_CODE_START_ENCRYPTION = 2; 63 64 private static final long MS_PER_MINUTE = 60 * 1000; 65 private static final long MS_PER_HOUR = 60 * MS_PER_MINUTE; 66 private static final long MS_PER_DAY = 24 * MS_PER_HOUR; 67 68 // The following keys are used to find each preference item 69 private static final String KEY_ENABLE_ADMIN = "key_enable_admin"; 70 private static final String KEY_DISABLE_CAMERA = "key_disable_camera"; 71 private static final String KEY_DISABLE_NOTIFICATIONS = "key_disable_notifications"; 72 private static final String KEY_DISABLE_UNREDACTED = "key_disable_unredacted"; 73 private static final String KEY_DISABLE_TRUST_AGENTS = "key_disable_trust_agents"; 74 private static final String KEY_DISABLE_KEYGUARD_WIDGETS = "key_disable_keyguard_widgets"; 75 private static final String KEY_DISABLE_KEYGUARD_SECURE_CAMERA 76 = "key_disable_keyguard_secure_camera"; 77 78 private static final String KEY_CATEGORY_QUALITY = "key_category_quality"; 79 private static final String KEY_SET_PASSWORD = "key_set_password"; 80 private static final String KEY_RESET_PASSWORD = "key_reset_password"; 81 private static final String KEY_QUALITY = "key_quality"; 82 private static final String KEY_MIN_LENGTH = "key_minimum_length"; 83 private static final String KEY_MIN_LETTERS = "key_minimum_letters"; 84 private static final String KEY_MIN_NUMERIC = "key_minimum_numeric"; 85 private static final String KEY_MIN_LOWER_CASE = "key_minimum_lower_case"; 86 private static final String KEY_MIN_UPPER_CASE = "key_minimum_upper_case"; 87 private static final String KEY_MIN_SYMBOLS = "key_minimum_symbols"; 88 private static final String KEY_MIN_NON_LETTER = "key_minimum_non_letter"; 89 90 private static final String KEY_CATEGORY_EXPIRATION = "key_category_expiration"; 91 private static final String KEY_HISTORY = "key_history"; 92 private static final String KEY_EXPIRATION_TIMEOUT = "key_expiration_timeout"; 93 private static final String KEY_EXPIRATION_STATUS = "key_expiration_status"; 94 95 private static final String KEY_CATEGORY_LOCK_WIPE = "key_category_lock_wipe"; 96 private static final String KEY_MAX_TIME_SCREEN_LOCK = "key_max_time_screen_lock"; 97 private static final String KEY_MAX_FAILS_BEFORE_WIPE = "key_max_fails_before_wipe"; 98 private static final String KEY_LOCK_SCREEN = "key_lock_screen"; 99 private static final String KEY_WIPE_DATA = "key_wipe_data"; 100 private static final String KEY_WIP_DATA_ALL = "key_wipe_data_all"; 101 102 private static final String KEY_CATEGORY_ENCRYPTION = "key_category_encryption"; 103 private static final String KEY_REQUIRE_ENCRYPTION = "key_require_encryption"; 104 private static final String KEY_ACTIVATE_ENCRYPTION = "key_activate_encryption"; 105 106 // Interaction with the DevicePolicyManager 107 DevicePolicyManager mDPM; 108 ComponentName mDeviceAdminSample; 109 110 @Override 111 protected void onCreate(Bundle savedInstanceState) { 112 super.onCreate(savedInstanceState); 113 114 // Prepare to work with the DPM 115 mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); 116 mDeviceAdminSample = new ComponentName(this, DeviceAdminSampleReceiver.class); 117 } 118 119 /** 120 * We override this method to provide PreferenceActivity with the top-level preference headers. 121 */ 122 @Override 123 public void onBuildHeaders(List<Header> target) { 124 loadHeadersFromResource(R.xml.device_admin_headers, target); 125 } 126 127 /** 128 * Helper to determine if we are an active admin 129 */ 130 private boolean isActiveAdmin() { 131 return mDPM.isAdminActive(mDeviceAdminSample); 132 } 133 134 @Override 135 protected boolean isValidFragment(String fragmentName) { 136 return GeneralFragment.class.getName().equals(fragmentName) 137 || QualityFragment.class.getName().equals(fragmentName) 138 || ExpirationFragment.class.getName().equals(fragmentName) 139 || LockWipeFragment.class.getName().equals(fragmentName) 140 || EncryptionFragment.class.getName().equals(fragmentName); 141 } 142 143 /** 144 * Common fragment code for DevicePolicyManager access. Provides two shared elements: 145 * 146 * 1. Provides instance variables to access activity/context, DevicePolicyManager, etc. 147 * 2. Provides support for the "set password" button(s) shared by multiple fragments. 148 */ 149 public static class AdminSampleFragment extends PreferenceFragment 150 implements OnPreferenceChangeListener, OnPreferenceClickListener{ 151 152 // Useful instance variables 153 protected DeviceAdminSample mActivity; 154 protected DevicePolicyManager mDPM; 155 protected ComponentName mDeviceAdminSample; 156 protected boolean mAdminActive; 157 158 // Optional shared UI 159 private PreferenceScreen mSetPassword; 160 private EditTextPreference mResetPassword; 161 162 @Override 163 public void onActivityCreated(Bundle savedInstanceState) { 164 super.onActivityCreated(savedInstanceState); 165 166 // Retrieve the useful instance variables 167 mActivity = (DeviceAdminSample) getActivity(); 168 mDPM = mActivity.mDPM; 169 mDeviceAdminSample = mActivity.mDeviceAdminSample; 170 mAdminActive = mActivity.isActiveAdmin(); 171 172 // Configure the shared UI elements (if they exist) 173 mResetPassword = (EditTextPreference) findPreference(KEY_RESET_PASSWORD); 174 mSetPassword = (PreferenceScreen) findPreference(KEY_SET_PASSWORD); 175 176 if (mResetPassword != null) { 177 mResetPassword.setOnPreferenceChangeListener(this); 178 } 179 if (mSetPassword != null) { 180 mSetPassword.setOnPreferenceClickListener(this); 181 } 182 } 183 184 @Override 185 public void onResume() { 186 super.onResume(); 187 mAdminActive = mActivity.isActiveAdmin(); 188 reloadSummaries(); 189 // Resetting the password via API is available only to active admins 190 if (mResetPassword != null) { 191 mResetPassword.setEnabled(mAdminActive); 192 } 193 } 194 195 /** 196 * Called automatically at every onResume. Should also call explicitly any time a 197 * policy changes that may affect other policy values. 198 */ 199 protected void reloadSummaries() { 200 if (mSetPassword != null) { 201 if (mAdminActive) { 202 // Show password-sufficient status under Set Password button 203 boolean sufficient = mDPM.isActivePasswordSufficient(); 204 mSetPassword.setSummary(sufficient ? 205 R.string.password_sufficient : R.string.password_insufficient); 206 } else { 207 mSetPassword.setSummary(null); 208 } 209 } 210 } 211 212 protected void postReloadSummaries() { 213 getView().post(new Runnable() { 214 @Override 215 public void run() { 216 reloadSummaries(); 217 } 218 }); 219 } 220 221 @Override 222 public boolean onPreferenceClick(Preference preference) { 223 if (mSetPassword != null && preference == mSetPassword) { 224 Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); 225 startActivity(intent); 226 return true; 227 } 228 return false; 229 } 230 231 @Override 232 public boolean onPreferenceChange(Preference preference, Object newValue) { 233 if (mResetPassword != null && preference == mResetPassword) { 234 doResetPassword((String)newValue); 235 return true; 236 } 237 return false; 238 } 239 240 /** 241 * This is dangerous, so we prevent automated tests from doing it, and we 242 * remind the user after we do it. 243 */ 244 private void doResetPassword(String newPassword) { 245 if (alertIfMonkey(mActivity, R.string.monkey_reset_password)) { 246 return; 247 } 248 mDPM.resetPassword(newPassword, DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY); 249 AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); 250 String message = mActivity.getString(R.string.reset_password_warning, newPassword); 251 builder.setMessage(message); 252 builder.setPositiveButton(R.string.reset_password_ok, null); 253 builder.show(); 254 } 255 256 /** 257 * Simple helper for summaries showing local & global (aggregate) policy settings 258 */ 259 protected String localGlobalSummary(Object local, Object global) { 260 return getString(R.string.status_local_global, local, global); 261 } 262 } 263 264 /** 265 * PreferenceFragment for "general" preferences. 266 */ 267 public static class GeneralFragment extends AdminSampleFragment 268 implements OnPreferenceChangeListener { 269 // UI elements 270 private CheckBoxPreference mEnableCheckbox; 271 private CheckBoxPreference mDisableCameraCheckbox; 272 private CheckBoxPreference mDisableKeyguardWidgetsCheckbox; 273 private CheckBoxPreference mDisableKeyguardSecureCameraCheckbox; 274 private CheckBoxPreference mDisableKeyguardNotificationCheckbox; 275 private CheckBoxPreference mDisableKeyguardTrustAgentCheckbox; 276 private CheckBoxPreference mDisableKeyguardUnredactedCheckbox; 277 278 @Override 279 public void onCreate(Bundle savedInstanceState) { 280 super.onCreate(savedInstanceState); 281 addPreferencesFromResource(R.xml.device_admin_general); 282 mEnableCheckbox = (CheckBoxPreference) findPreference(KEY_ENABLE_ADMIN); 283 mEnableCheckbox.setOnPreferenceChangeListener(this); 284 285 mDisableCameraCheckbox = (CheckBoxPreference) findPreference(KEY_DISABLE_CAMERA); 286 mDisableCameraCheckbox.setOnPreferenceChangeListener(this); 287 288 mDisableKeyguardWidgetsCheckbox = 289 (CheckBoxPreference) findPreference(KEY_DISABLE_KEYGUARD_WIDGETS); 290 mDisableKeyguardWidgetsCheckbox.setOnPreferenceChangeListener(this); 291 292 mDisableKeyguardSecureCameraCheckbox = 293 (CheckBoxPreference) findPreference(KEY_DISABLE_KEYGUARD_SECURE_CAMERA); 294 mDisableKeyguardSecureCameraCheckbox.setOnPreferenceChangeListener(this); 295 296 mDisableKeyguardNotificationCheckbox = 297 (CheckBoxPreference) findPreference(KEY_DISABLE_NOTIFICATIONS); 298 mDisableKeyguardNotificationCheckbox.setOnPreferenceChangeListener(this); 299 300 mDisableKeyguardUnredactedCheckbox = 301 (CheckBoxPreference) findPreference(KEY_DISABLE_UNREDACTED); 302 mDisableKeyguardUnredactedCheckbox.setOnPreferenceChangeListener(this); 303 304 mDisableKeyguardTrustAgentCheckbox = 305 (CheckBoxPreference) findPreference(KEY_DISABLE_TRUST_AGENTS); 306 mDisableKeyguardTrustAgentCheckbox.setOnPreferenceChangeListener(this); 307 } 308 309 // At onResume time, reload UI with current values as required 310 @Override 311 public void onResume() { 312 super.onResume(); 313 mEnableCheckbox.setChecked(mAdminActive); 314 enableDeviceCapabilitiesArea(mAdminActive); 315 316 if (mAdminActive) { 317 mDPM.setCameraDisabled(mDeviceAdminSample, mDisableCameraCheckbox.isChecked()); 318 mDPM.setKeyguardDisabledFeatures(mDeviceAdminSample, createKeyguardDisabledFlag()); 319 reloadSummaries(); 320 } 321 } 322 323 int createKeyguardDisabledFlag() { 324 int flags = DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE; 325 flags |= mDisableKeyguardWidgetsCheckbox.isChecked() ? 326 DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL : 0; 327 flags |= mDisableKeyguardSecureCameraCheckbox.isChecked() ? 328 DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA : 0; 329 flags |= mDisableKeyguardNotificationCheckbox.isChecked() ? 330 DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS : 0; 331 flags |= mDisableKeyguardUnredactedCheckbox.isChecked() ? 332 DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS : 0; 333 flags |= mDisableKeyguardTrustAgentCheckbox.isChecked() ? 334 DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS : 0; 335 return flags; 336 } 337 338 @Override 339 public boolean onPreferenceChange(Preference preference, Object newValue) { 340 if (super.onPreferenceChange(preference, newValue)) { 341 return true; 342 } 343 boolean value = (Boolean) newValue; 344 if (preference == mEnableCheckbox) { 345 if (value != mAdminActive) { 346 if (value) { 347 // Launch the activity to have the user enable our admin. 348 Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); 349 intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample); 350 intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, 351 mActivity.getString(R.string.add_admin_extra_app_text)); 352 startActivityForResult(intent, REQUEST_CODE_ENABLE_ADMIN); 353 // return false - don't update checkbox until we're really active 354 return false; 355 } else { 356 mDPM.removeActiveAdmin(mDeviceAdminSample); 357 enableDeviceCapabilitiesArea(false); 358 mAdminActive = false; 359 } 360 } 361 } else if (preference == mDisableCameraCheckbox) { 362 mDPM.setCameraDisabled(mDeviceAdminSample, value); 363 // Delay update because the change is only applied after exiting this method. 364 postReloadSummaries(); 365 } else if (preference == mDisableKeyguardWidgetsCheckbox 366 || preference == mDisableKeyguardSecureCameraCheckbox 367 || preference == mDisableKeyguardNotificationCheckbox 368 || preference == mDisableKeyguardUnredactedCheckbox 369 || preference == mDisableKeyguardTrustAgentCheckbox) { 370 // Delay update because the change is only applied after exiting this method. 371 getView().post(new Runnable() { 372 @Override 373 public void run() { 374 mDPM.setKeyguardDisabledFeatures(mDeviceAdminSample, 375 createKeyguardDisabledFlag()); 376 } 377 }); 378 postReloadSummaries(); 379 } 380 return true; 381 } 382 383 @Override 384 protected void reloadSummaries() { 385 super.reloadSummaries(); 386 String cameraSummary = getString(mDPM.getCameraDisabled(mDeviceAdminSample) 387 ? R.string.camera_disabled : R.string.camera_enabled); 388 mDisableCameraCheckbox.setSummary(cameraSummary); 389 390 int disabled = mDPM.getKeyguardDisabledFeatures(mDeviceAdminSample); 391 392 String keyguardWidgetSummary = getString( 393 (disabled & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0 ? 394 R.string.keyguard_widgets_disabled : R.string.keyguard_widgets_enabled); 395 mDisableKeyguardWidgetsCheckbox.setSummary(keyguardWidgetSummary); 396 397 String keyguardSecureCameraSummary = getString( 398 (disabled & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0 ? 399 R.string.keyguard_secure_camera_disabled : R.string.keyguard_secure_camera_enabled); 400 mDisableKeyguardSecureCameraCheckbox.setSummary(keyguardSecureCameraSummary); 401 402 String keyguardSecureNotificationsSummary = getString( 403 (disabled & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) != 0 ? 404 R.string.keyguard_secure_notifications_disabled 405 : R.string.keyguard_secure_notifications_enabled); 406 mDisableKeyguardNotificationCheckbox.setSummary(keyguardSecureNotificationsSummary); 407 408 String keyguardUnredactedSummary = getString( 409 (disabled & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) != 0 410 ? R.string.keyguard_unredacted_notifications_disabled 411 : R.string.keyguard_unredacted_notifications_enabled); 412 mDisableKeyguardUnredactedCheckbox.setSummary(keyguardUnredactedSummary); 413 414 String keyguardEnableTrustAgentSummary = getString( 415 (disabled & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0 ? 416 R.string.keyguard_trust_agents_disabled 417 : R.string.keyguard_trust_agents_enabled); 418 mDisableKeyguardTrustAgentCheckbox.setSummary(keyguardEnableTrustAgentSummary); 419 } 420 421 /** Updates the device capabilities area (dis/enabling) as the admin is (de)activated */ 422 private void enableDeviceCapabilitiesArea(boolean enabled) { 423 mDisableCameraCheckbox.setEnabled(enabled); 424 mDisableKeyguardWidgetsCheckbox.setEnabled(enabled); 425 mDisableKeyguardSecureCameraCheckbox.setEnabled(enabled); 426 mDisableKeyguardNotificationCheckbox.setEnabled(enabled); 427 mDisableKeyguardUnredactedCheckbox.setEnabled(enabled); 428 mDisableKeyguardTrustAgentCheckbox.setEnabled(enabled); 429 } 430 } 431 432 /** 433 * PreferenceFragment for "password quality" preferences. 434 */ 435 public static class QualityFragment extends AdminSampleFragment 436 implements OnPreferenceChangeListener { 437 438 // Password quality values 439 // This list must match the list found in samples/ApiDemos/res/values/arrays.xml 440 final static int[] mPasswordQualityValues = new int[] { 441 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 442 DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 443 DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, 444 DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, 445 DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, 446 DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, 447 DevicePolicyManager.PASSWORD_QUALITY_COMPLEX 448 }; 449 450 // Password quality values (as strings, for the ListPreference entryValues) 451 // This list must match the list found in samples/ApiDemos/res/values/arrays.xml 452 final static String[] mPasswordQualityValueStrings = new String[] { 453 String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED), 454 String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING), 455 String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC), 456 String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX), 457 String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), 458 String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC), 459 String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) 460 }; 461 462 // UI elements 463 private PreferenceCategory mQualityCategory; 464 private ListPreference mPasswordQuality; 465 private EditTextPreference mMinLength; 466 private EditTextPreference mMinLetters; 467 private EditTextPreference mMinNumeric; 468 private EditTextPreference mMinLowerCase; 469 private EditTextPreference mMinUpperCase; 470 private EditTextPreference mMinSymbols; 471 private EditTextPreference mMinNonLetter; 472 473 @Override 474 public void onCreate(Bundle savedInstanceState) { 475 super.onCreate(savedInstanceState); 476 addPreferencesFromResource(R.xml.device_admin_quality); 477 478 mQualityCategory = (PreferenceCategory) findPreference(KEY_CATEGORY_QUALITY); 479 mPasswordQuality = (ListPreference) findPreference(KEY_QUALITY); 480 mMinLength = (EditTextPreference) findPreference(KEY_MIN_LENGTH); 481 mMinLetters = (EditTextPreference) findPreference(KEY_MIN_LETTERS); 482 mMinNumeric = (EditTextPreference) findPreference(KEY_MIN_NUMERIC); 483 mMinLowerCase = (EditTextPreference) findPreference(KEY_MIN_LOWER_CASE); 484 mMinUpperCase = (EditTextPreference) findPreference(KEY_MIN_UPPER_CASE); 485 mMinSymbols = (EditTextPreference) findPreference(KEY_MIN_SYMBOLS); 486 mMinNonLetter = (EditTextPreference) findPreference(KEY_MIN_NON_LETTER); 487 488 mPasswordQuality.setOnPreferenceChangeListener(this); 489 mMinLength.setOnPreferenceChangeListener(this); 490 mMinLetters.setOnPreferenceChangeListener(this); 491 mMinNumeric.setOnPreferenceChangeListener(this); 492 mMinLowerCase.setOnPreferenceChangeListener(this); 493 mMinUpperCase.setOnPreferenceChangeListener(this); 494 mMinSymbols.setOnPreferenceChangeListener(this); 495 mMinNonLetter.setOnPreferenceChangeListener(this); 496 497 // Finish setup of the quality dropdown 498 mPasswordQuality.setEntryValues(mPasswordQualityValueStrings); 499 } 500 501 @Override 502 public void onResume() { 503 super.onResume(); 504 mQualityCategory.setEnabled(mAdminActive); 505 } 506 507 /** 508 * Update the summaries of each item to show the local setting and the global setting. 509 */ 510 @Override 511 protected void reloadSummaries() { 512 super.reloadSummaries(); 513 // Show numeric settings for each policy API 514 int local, global; 515 local = mDPM.getPasswordQuality(mDeviceAdminSample); 516 global = mDPM.getPasswordQuality(null); 517 mPasswordQuality.setSummary( 518 localGlobalSummary(qualityValueToString(local), qualityValueToString(global))); 519 local = mDPM.getPasswordMinimumLength(mDeviceAdminSample); 520 global = mDPM.getPasswordMinimumLength(null); 521 mMinLength.setSummary(localGlobalSummary(local, global)); 522 local = mDPM.getPasswordMinimumLetters(mDeviceAdminSample); 523 global = mDPM.getPasswordMinimumLetters(null); 524 mMinLetters.setSummary(localGlobalSummary(local, global)); 525 local = mDPM.getPasswordMinimumNumeric(mDeviceAdminSample); 526 global = mDPM.getPasswordMinimumNumeric(null); 527 mMinNumeric.setSummary(localGlobalSummary(local, global)); 528 local = mDPM.getPasswordMinimumLowerCase(mDeviceAdminSample); 529 global = mDPM.getPasswordMinimumLowerCase(null); 530 mMinLowerCase.setSummary(localGlobalSummary(local, global)); 531 local = mDPM.getPasswordMinimumUpperCase(mDeviceAdminSample); 532 global = mDPM.getPasswordMinimumUpperCase(null); 533 mMinUpperCase.setSummary(localGlobalSummary(local, global)); 534 local = mDPM.getPasswordMinimumSymbols(mDeviceAdminSample); 535 global = mDPM.getPasswordMinimumSymbols(null); 536 mMinSymbols.setSummary(localGlobalSummary(local, global)); 537 local = mDPM.getPasswordMinimumNonLetter(mDeviceAdminSample); 538 global = mDPM.getPasswordMinimumNonLetter(null); 539 mMinNonLetter.setSummary(localGlobalSummary(local, global)); 540 } 541 542 @Override 543 public boolean onPreferenceChange(Preference preference, Object newValue) { 544 if (super.onPreferenceChange(preference, newValue)) { 545 return true; 546 } 547 String valueString = (String)newValue; 548 if (TextUtils.isEmpty(valueString)) { 549 return false; 550 } 551 int value = 0; 552 try { 553 value = Integer.parseInt(valueString); 554 } catch (NumberFormatException nfe) { 555 String warning = mActivity.getString(R.string.number_format_warning, valueString); 556 Toast.makeText(mActivity, warning, Toast.LENGTH_SHORT).show(); 557 } 558 if (preference == mPasswordQuality) { 559 mDPM.setPasswordQuality(mDeviceAdminSample, value); 560 } else if (preference == mMinLength) { 561 mDPM.setPasswordMinimumLength(mDeviceAdminSample, value); 562 } else if (preference == mMinLetters) { 563 mDPM.setPasswordMinimumLetters(mDeviceAdminSample, value); 564 } else if (preference == mMinNumeric) { 565 mDPM.setPasswordMinimumNumeric(mDeviceAdminSample, value); 566 } else if (preference == mMinLowerCase) { 567 mDPM.setPasswordMinimumLowerCase(mDeviceAdminSample, value); 568 } else if (preference == mMinUpperCase) { 569 mDPM.setPasswordMinimumUpperCase(mDeviceAdminSample, value); 570 } else if (preference == mMinSymbols) { 571 mDPM.setPasswordMinimumSymbols(mDeviceAdminSample, value); 572 } else if (preference == mMinNonLetter) { 573 mDPM.setPasswordMinimumNonLetter(mDeviceAdminSample, value); 574 } 575 // Delay update because the change is only applied after exiting this method. 576 postReloadSummaries(); 577 return true; 578 } 579 580 private String qualityValueToString(int quality) { 581 for (int i= 0; i < mPasswordQualityValues.length; i++) { 582 if (mPasswordQualityValues[i] == quality) { 583 String[] qualities = 584 mActivity.getResources().getStringArray(R.array.password_qualities); 585 return qualities[i]; 586 } 587 } 588 return "(0x" + Integer.toString(quality, 16) + ")"; 589 } 590 } 591 592 /** 593 * PreferenceFragment for "password expiration" preferences. 594 */ 595 public static class ExpirationFragment extends AdminSampleFragment 596 implements OnPreferenceChangeListener, OnPreferenceClickListener { 597 private PreferenceCategory mExpirationCategory; 598 private EditTextPreference mHistory; 599 private EditTextPreference mExpirationTimeout; 600 private PreferenceScreen mExpirationStatus; 601 602 @Override 603 public void onCreate(Bundle savedInstanceState) { 604 super.onCreate(savedInstanceState); 605 addPreferencesFromResource(R.xml.device_admin_expiration); 606 607 mExpirationCategory = (PreferenceCategory) findPreference(KEY_CATEGORY_EXPIRATION); 608 mHistory = (EditTextPreference) findPreference(KEY_HISTORY); 609 mExpirationTimeout = (EditTextPreference) findPreference(KEY_EXPIRATION_TIMEOUT); 610 mExpirationStatus = (PreferenceScreen) findPreference(KEY_EXPIRATION_STATUS); 611 612 mHistory.setOnPreferenceChangeListener(this); 613 mExpirationTimeout.setOnPreferenceChangeListener(this); 614 mExpirationStatus.setOnPreferenceClickListener(this); 615 } 616 617 @Override 618 public void onResume() { 619 super.onResume(); 620 mExpirationCategory.setEnabled(mAdminActive); 621 } 622 623 /** 624 * Update the summaries of each item to show the local setting and the global setting. 625 */ 626 @Override 627 protected void reloadSummaries() { 628 super.reloadSummaries(); 629 630 int local, global; 631 local = mDPM.getPasswordHistoryLength(mDeviceAdminSample); 632 global = mDPM.getPasswordHistoryLength(null); 633 mHistory.setSummary(localGlobalSummary(local, global)); 634 635 long localLong, globalLong; 636 localLong = mDPM.getPasswordExpirationTimeout(mDeviceAdminSample); 637 globalLong = mDPM.getPasswordExpirationTimeout(null); 638 mExpirationTimeout.setSummary(localGlobalSummary( 639 localLong / MS_PER_MINUTE, globalLong / MS_PER_MINUTE)); 640 641 String expirationStatus = getExpirationStatus(); 642 mExpirationStatus.setSummary(expirationStatus); 643 } 644 645 @Override 646 public boolean onPreferenceChange(Preference preference, Object newValue) { 647 if (super.onPreferenceChange(preference, newValue)) { 648 return true; 649 } 650 String valueString = (String)newValue; 651 if (TextUtils.isEmpty(valueString)) { 652 return false; 653 } 654 int value = 0; 655 try { 656 value = Integer.parseInt(valueString); 657 } catch (NumberFormatException nfe) { 658 String warning = mActivity.getString(R.string.number_format_warning, valueString); 659 Toast.makeText(mActivity, warning, Toast.LENGTH_SHORT).show(); 660 } 661 if (preference == mHistory) { 662 mDPM.setPasswordHistoryLength(mDeviceAdminSample, value); 663 } else if (preference == mExpirationTimeout) { 664 mDPM.setPasswordExpirationTimeout(mDeviceAdminSample, value * MS_PER_MINUTE); 665 } 666 // Delay update because the change is only applied after exiting this method. 667 postReloadSummaries(); 668 return true; 669 } 670 671 @Override 672 public boolean onPreferenceClick(Preference preference) { 673 if (super.onPreferenceClick(preference)) { 674 return true; 675 } 676 if (preference == mExpirationStatus) { 677 String expirationStatus = getExpirationStatus(); 678 mExpirationStatus.setSummary(expirationStatus); 679 return true; 680 } 681 return false; 682 } 683 684 /** 685 * Create a summary string describing the expiration status for the sample app, 686 * as well as the global (aggregate) status. 687 */ 688 private String getExpirationStatus() { 689 // expirations are absolute; convert to relative for display 690 long localExpiration = mDPM.getPasswordExpiration(mDeviceAdminSample); 691 long globalExpiration = mDPM.getPasswordExpiration(null); 692 long now = System.currentTimeMillis(); 693 694 // local expiration 695 String local; 696 if (localExpiration == 0) { 697 local = mActivity.getString(R.string.expiration_status_none); 698 } else { 699 localExpiration -= now; 700 String dms = timeToDaysMinutesSeconds(mActivity, Math.abs(localExpiration)); 701 if (localExpiration >= 0) { 702 local = mActivity.getString(R.string.expiration_status_future, dms); 703 } else { 704 local = mActivity.getString(R.string.expiration_status_past, dms); 705 } 706 } 707 708 // global expiration 709 String global; 710 if (globalExpiration == 0) { 711 global = mActivity.getString(R.string.expiration_status_none); 712 } else { 713 globalExpiration -= now; 714 String dms = timeToDaysMinutesSeconds(mActivity, Math.abs(globalExpiration)); 715 if (globalExpiration >= 0) { 716 global = mActivity.getString(R.string.expiration_status_future, dms); 717 } else { 718 global = mActivity.getString(R.string.expiration_status_past, dms); 719 } 720 } 721 return mActivity.getString(R.string.status_local_global, local, global); 722 } 723 } 724 725 /** 726 * PreferenceFragment for "lock screen & wipe" preferences. 727 */ 728 public static class LockWipeFragment extends AdminSampleFragment 729 implements OnPreferenceChangeListener, OnPreferenceClickListener { 730 private PreferenceCategory mLockWipeCategory; 731 private EditTextPreference mMaxTimeScreenLock; 732 private EditTextPreference mMaxFailures; 733 private PreferenceScreen mLockScreen; 734 private PreferenceScreen mWipeData; 735 private PreferenceScreen mWipeAppData; 736 737 @Override 738 public void onCreate(Bundle savedInstanceState) { 739 super.onCreate(savedInstanceState); 740 addPreferencesFromResource(R.xml.device_admin_lock_wipe); 741 742 mLockWipeCategory = (PreferenceCategory) findPreference(KEY_CATEGORY_LOCK_WIPE); 743 mMaxTimeScreenLock = (EditTextPreference) findPreference(KEY_MAX_TIME_SCREEN_LOCK); 744 mMaxFailures = (EditTextPreference) findPreference(KEY_MAX_FAILS_BEFORE_WIPE); 745 mLockScreen = (PreferenceScreen) findPreference(KEY_LOCK_SCREEN); 746 mWipeData = (PreferenceScreen) findPreference(KEY_WIPE_DATA); 747 mWipeAppData = (PreferenceScreen) findPreference(KEY_WIP_DATA_ALL); 748 749 mMaxTimeScreenLock.setOnPreferenceChangeListener(this); 750 mMaxFailures.setOnPreferenceChangeListener(this); 751 mLockScreen.setOnPreferenceClickListener(this); 752 mWipeData.setOnPreferenceClickListener(this); 753 mWipeAppData.setOnPreferenceClickListener(this); 754 } 755 756 @Override 757 public void onResume() { 758 super.onResume(); 759 mLockWipeCategory.setEnabled(mAdminActive); 760 } 761 762 /** 763 * Update the summaries of each item to show the local setting and the global setting. 764 */ 765 @Override 766 protected void reloadSummaries() { 767 super.reloadSummaries(); 768 769 long localLong, globalLong; 770 localLong = mDPM.getMaximumTimeToLock(mDeviceAdminSample); 771 globalLong = mDPM.getMaximumTimeToLock(null); 772 mMaxTimeScreenLock.setSummary(localGlobalSummary( 773 localLong / MS_PER_MINUTE, globalLong / MS_PER_MINUTE)); 774 775 int local, global; 776 local = mDPM.getMaximumFailedPasswordsForWipe(mDeviceAdminSample); 777 global = mDPM.getMaximumFailedPasswordsForWipe(null); 778 mMaxFailures.setSummary(localGlobalSummary(local, global)); 779 } 780 781 @Override 782 public boolean onPreferenceChange(Preference preference, Object newValue) { 783 if (super.onPreferenceChange(preference, newValue)) { 784 return true; 785 } 786 String valueString = (String)newValue; 787 if (TextUtils.isEmpty(valueString)) { 788 return false; 789 } 790 int value = 0; 791 try { 792 value = Integer.parseInt(valueString); 793 } catch (NumberFormatException nfe) { 794 String warning = mActivity.getString(R.string.number_format_warning, valueString); 795 Toast.makeText(mActivity, warning, Toast.LENGTH_SHORT).show(); 796 } 797 if (preference == mMaxTimeScreenLock) { 798 mDPM.setMaximumTimeToLock(mDeviceAdminSample, value * MS_PER_MINUTE); 799 } else if (preference == mMaxFailures) { 800 if (alertIfMonkey(mActivity, R.string.monkey_wipe_data)) { 801 return true; 802 } 803 mDPM.setMaximumFailedPasswordsForWipe(mDeviceAdminSample, value); 804 } 805 // Delay update because the change is only applied after exiting this method. 806 postReloadSummaries(); 807 return true; 808 } 809 810 @Override 811 public boolean onPreferenceClick(Preference preference) { 812 if (super.onPreferenceClick(preference)) { 813 return true; 814 } 815 if (preference == mLockScreen) { 816 if (alertIfMonkey(mActivity, R.string.monkey_lock_screen)) { 817 return true; 818 } 819 mDPM.lockNow(); 820 return true; 821 } else if (preference == mWipeData || preference == mWipeAppData) { 822 if (alertIfMonkey(mActivity, R.string.monkey_wipe_data)) { 823 return true; 824 } 825 promptForRealDeviceWipe(preference == mWipeAppData); 826 return true; 827 } 828 return false; 829 } 830 831 /** 832 * Wiping data is real, so we don't want it to be easy. Show two alerts before wiping. 833 */ 834 private void promptForRealDeviceWipe(final boolean wipeAllData) { 835 final DeviceAdminSample activity = mActivity; 836 837 AlertDialog.Builder builder = new AlertDialog.Builder(activity); 838 builder.setMessage(R.string.wipe_warning_first); 839 builder.setPositiveButton(R.string.wipe_warning_first_ok, 840 new DialogInterface.OnClickListener() { 841 @Override 842 public void onClick(DialogInterface dialog, int which) { 843 AlertDialog.Builder builder = new AlertDialog.Builder(activity); 844 if (wipeAllData) { 845 builder.setMessage(R.string.wipe_warning_second_full); 846 } else { 847 builder.setMessage(R.string.wipe_warning_second); 848 } 849 builder.setPositiveButton(R.string.wipe_warning_second_ok, 850 new DialogInterface.OnClickListener() { 851 @Override 852 public void onClick(DialogInterface dialog, int which) { 853 boolean stillActive = mActivity.isActiveAdmin(); 854 if (stillActive) { 855 mDPM.wipeData(wipeAllData 856 ? DevicePolicyManager.WIPE_EXTERNAL_STORAGE : 0); 857 } 858 } 859 }); 860 builder.setNegativeButton(R.string.wipe_warning_second_no, null); 861 builder.show(); 862 } 863 }); 864 builder.setNegativeButton(R.string.wipe_warning_first_no, null); 865 builder.show(); 866 } 867 } 868 869 /** 870 * PreferenceFragment for "encryption" preferences. 871 */ 872 public static class EncryptionFragment extends AdminSampleFragment 873 implements OnPreferenceChangeListener, OnPreferenceClickListener { 874 private PreferenceCategory mEncryptionCategory; 875 private CheckBoxPreference mRequireEncryption; 876 private PreferenceScreen mActivateEncryption; 877 878 @Override 879 public void onCreate(Bundle savedInstanceState) { 880 super.onCreate(savedInstanceState); 881 addPreferencesFromResource(R.xml.device_admin_encryption); 882 883 mEncryptionCategory = (PreferenceCategory) findPreference(KEY_CATEGORY_ENCRYPTION); 884 mRequireEncryption = (CheckBoxPreference) findPreference(KEY_REQUIRE_ENCRYPTION); 885 mActivateEncryption = (PreferenceScreen) findPreference(KEY_ACTIVATE_ENCRYPTION); 886 887 mRequireEncryption.setOnPreferenceChangeListener(this); 888 mActivateEncryption.setOnPreferenceClickListener(this); 889 } 890 891 @Override 892 public void onResume() { 893 super.onResume(); 894 mEncryptionCategory.setEnabled(mAdminActive); 895 mRequireEncryption.setChecked(mDPM.getStorageEncryption(mDeviceAdminSample)); 896 } 897 898 /** 899 * Update the summaries of each item to show the local setting and the global setting. 900 */ 901 @Override 902 protected void reloadSummaries() { 903 super.reloadSummaries(); 904 905 boolean local, global; 906 local = mDPM.getStorageEncryption(mDeviceAdminSample); 907 global = mDPM.getStorageEncryption(null); 908 mRequireEncryption.setSummary(localGlobalSummary(local, global)); 909 910 int deviceStatusCode = mDPM.getStorageEncryptionStatus(); 911 String deviceStatus = statusCodeToString(deviceStatusCode); 912 String status = mActivity.getString(R.string.status_device_encryption, deviceStatus); 913 mActivateEncryption.setSummary(status); 914 } 915 916 @Override 917 public boolean onPreferenceChange(Preference preference, Object newValue) { 918 if (super.onPreferenceChange(preference, newValue)) { 919 return true; 920 } 921 if (preference == mRequireEncryption) { 922 boolean newActive = (Boolean) newValue; 923 mDPM.setStorageEncryption(mDeviceAdminSample, newActive); 924 // Delay update because the change is only applied after exiting this method. 925 postReloadSummaries(); 926 return true; 927 } 928 return true; 929 } 930 931 @Override 932 public boolean onPreferenceClick(Preference preference) { 933 if (super.onPreferenceClick(preference)) { 934 return true; 935 } 936 if (preference == mActivateEncryption) { 937 if (alertIfMonkey(mActivity, R.string.monkey_encryption)) { 938 return true; 939 } 940 // Check to see if encryption is even supported on this device (it's optional). 941 if (mDPM.getStorageEncryptionStatus() == 942 DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) { 943 AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); 944 builder.setMessage(R.string.encryption_not_supported); 945 builder.setPositiveButton(R.string.encryption_not_supported_ok, null); 946 builder.show(); 947 return true; 948 } 949 // Launch the activity to activate encryption. May or may not return! 950 Intent intent = new Intent(DevicePolicyManager.ACTION_START_ENCRYPTION); 951 startActivityForResult(intent, REQUEST_CODE_START_ENCRYPTION); 952 return true; 953 } 954 return false; 955 } 956 957 private String statusCodeToString(int newStatusCode) { 958 int newStatus = R.string.encryption_status_unknown; 959 switch (newStatusCode) { 960 case DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED: 961 newStatus = R.string.encryption_status_unsupported; 962 break; 963 case DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE: 964 newStatus = R.string.encryption_status_inactive; 965 break; 966 case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING: 967 newStatus = R.string.encryption_status_activating; 968 break; 969 case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE: 970 newStatus = R.string.encryption_status_active; 971 break; 972 } 973 return mActivity.getString(newStatus); 974 } 975 } 976 977 /** 978 * Simple converter used for long expiration times reported in mSec. 979 */ 980 private static String timeToDaysMinutesSeconds(Context context, long time) { 981 long days = time / MS_PER_DAY; 982 long hours = (time / MS_PER_HOUR) % 24; 983 long minutes = (time / MS_PER_MINUTE) % 60; 984 return context.getString(R.string.status_days_hours_minutes, days, hours, minutes); 985 } 986 987 /** 988 * If the "user" is a monkey, post an alert and notify the caller. This prevents automated 989 * test frameworks from stumbling into annoying or dangerous operations. 990 */ 991 private static boolean alertIfMonkey(Context context, int stringId) { 992 if (ActivityManager.isUserAMonkey()) { 993 AlertDialog.Builder builder = new AlertDialog.Builder(context); 994 builder.setMessage(stringId); 995 builder.setPositiveButton(R.string.monkey_ok, null); 996 builder.show(); 997 return true; 998 } else { 999 return false; 1000 } 1001 } 1002 1003 /** 1004 * Sample implementation of a DeviceAdminReceiver. Your controller must provide one, 1005 * although you may or may not implement all of the methods shown here. 1006 * 1007 * All callbacks are on the UI thread and your implementations should not engage in any 1008 * blocking operations, including disk I/O. 1009 */ 1010 public static class DeviceAdminSampleReceiver extends DeviceAdminReceiver { 1011 void showToast(Context context, String msg) { 1012 String status = context.getString(R.string.admin_receiver_status, msg); 1013 Toast.makeText(context, status, Toast.LENGTH_SHORT).show(); 1014 } 1015 1016 @Override 1017 public void onReceive(Context context, Intent intent) { 1018 if (intent.getAction() == ACTION_DEVICE_ADMIN_DISABLE_REQUESTED) { 1019 abortBroadcast(); 1020 } 1021 super.onReceive(context, intent); 1022 } 1023 1024 @Override 1025 public void onEnabled(Context context, Intent intent) { 1026 showToast(context, context.getString(R.string.admin_receiver_status_enabled)); 1027 } 1028 1029 @Override 1030 public CharSequence onDisableRequested(Context context, Intent intent) { 1031 return context.getString(R.string.admin_receiver_status_disable_warning); 1032 } 1033 1034 @Override 1035 public void onDisabled(Context context, Intent intent) { 1036 showToast(context, context.getString(R.string.admin_receiver_status_disabled)); 1037 } 1038 1039 @Override 1040 public void onPasswordChanged(Context context, Intent intent) { 1041 showToast(context, context.getString(R.string.admin_receiver_status_pw_changed)); 1042 } 1043 1044 @Override 1045 public void onPasswordFailed(Context context, Intent intent) { 1046 showToast(context, context.getString(R.string.admin_receiver_status_pw_failed)); 1047 } 1048 1049 @Override 1050 public void onPasswordSucceeded(Context context, Intent intent) { 1051 showToast(context, context.getString(R.string.admin_receiver_status_pw_succeeded)); 1052 } 1053 1054 @Override 1055 public void onPasswordExpiring(Context context, Intent intent) { 1056 DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( 1057 Context.DEVICE_POLICY_SERVICE); 1058 long expr = dpm.getPasswordExpiration( 1059 new ComponentName(context, DeviceAdminSampleReceiver.class)); 1060 long delta = expr - System.currentTimeMillis(); 1061 boolean expired = delta < 0L; 1062 String message = context.getString(expired ? 1063 R.string.expiration_status_past : R.string.expiration_status_future); 1064 showToast(context, message); 1065 Log.v(TAG, message); 1066 } 1067 } 1068 } 1069