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