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