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