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