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