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