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