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                                 Log.w(TAG, "setTrustAgentConfigurat() is disabled");
    410                                 // mDPM.setTrustAgentConfiguration(mDeviceAdminSample, agent, bundle);
    411                             }
    412                         } else {
    413                             Log.w(TAG, "Invalid component: " + component);
    414                         }
    415                     }
    416                 }
    417             });
    418         }
    419 
    420         @Override
    421         protected void reloadSummaries() {
    422             super.reloadSummaries();
    423             String cameraSummary = getString(mDPM.getCameraDisabled(mDeviceAdminSample)
    424                     ? R.string.camera_disabled : R.string.camera_enabled);
    425             mDisableCameraCheckbox.setSummary(cameraSummary);
    426 
    427             int disabled = mDPM.getKeyguardDisabledFeatures(mDeviceAdminSample);
    428 
    429             String keyguardWidgetSummary = getString(
    430                     (disabled & DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL) != 0 ?
    431                             R.string.keyguard_widgets_disabled : R.string.keyguard_widgets_enabled);
    432             mDisableKeyguardWidgetsCheckbox.setSummary(keyguardWidgetSummary);
    433 
    434             String keyguardSecureCameraSummary = getString(
    435                 (disabled & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0 ?
    436                 R.string.keyguard_secure_camera_disabled : R.string.keyguard_secure_camera_enabled);
    437             mDisableKeyguardSecureCameraCheckbox.setSummary(keyguardSecureCameraSummary);
    438 
    439             String keyguardSecureNotificationsSummary = getString(
    440                     (disabled & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) != 0 ?
    441                         R.string.keyguard_secure_notifications_disabled
    442                         : R.string.keyguard_secure_notifications_enabled);
    443             mDisableKeyguardNotificationCheckbox.setSummary(keyguardSecureNotificationsSummary);
    444 
    445             String keyguardUnredactedSummary = getString(
    446                     (disabled & DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) != 0
    447                         ? R.string.keyguard_unredacted_notifications_disabled
    448                         : R.string.keyguard_unredacted_notifications_enabled);
    449             mDisableKeyguardUnredactedCheckbox.setSummary(keyguardUnredactedSummary);
    450 
    451             String keyguardEnableTrustAgentSummary = getString(
    452                     (disabled & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0 ?
    453                         R.string.keyguard_trust_agents_disabled
    454                         : R.string.keyguard_trust_agents_enabled);
    455             mDisableKeyguardTrustAgentCheckbox.setSummary(keyguardEnableTrustAgentSummary);
    456 
    457             final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
    458             final boolean trustDisabled =
    459                     (disabled & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0;
    460             String component = prefs.getString(mTrustAgentComponent.getKey(), null);
    461             mTrustAgentComponent.setSummary(component);
    462             mTrustAgentComponent.setEnabled(trustDisabled);
    463 
    464             String features = prefs.getString(mTrustAgentFeatures.getKey(), null);
    465             mTrustAgentFeatures.setSummary(features);
    466             mTrustAgentFeatures.setEnabled(trustDisabled);
    467         }
    468 
    469         /** Updates the device capabilities area (dis/enabling) as the admin is (de)activated */
    470         private void enableDeviceCapabilitiesArea(boolean enabled) {
    471             mDisableCameraCheckbox.setEnabled(enabled);
    472             mDisableKeyguardWidgetsCheckbox.setEnabled(enabled);
    473             mDisableKeyguardSecureCameraCheckbox.setEnabled(enabled);
    474             mDisableKeyguardNotificationCheckbox.setEnabled(enabled);
    475             mDisableKeyguardUnredactedCheckbox.setEnabled(enabled);
    476             mDisableKeyguardTrustAgentCheckbox.setEnabled(enabled);
    477             mTrustAgentComponent.setEnabled(enabled);
    478             mTrustAgentFeatures.setEnabled(enabled);
    479         }
    480     }
    481 
    482     /**
    483      * PreferenceFragment for "password quality" preferences.
    484      */
    485     public static class QualityFragment extends AdminSampleFragment
    486             implements OnPreferenceChangeListener {
    487 
    488         // Password quality values
    489         // This list must match the list found in samples/ApiDemos/res/values/arrays.xml
    490         final static int[] mPasswordQualityValues = new int[] {
    491             DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
    492             DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
    493             DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
    494             DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
    495             DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
    496             DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
    497             DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
    498         };
    499 
    500         // Password quality values (as strings, for the ListPreference entryValues)
    501         // This list must match the list found in samples/ApiDemos/res/values/arrays.xml
    502         final static String[] mPasswordQualityValueStrings = new String[] {
    503             String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED),
    504             String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING),
    505             String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
    506             String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
    507             String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
    508             String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC),
    509             String.valueOf(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX)
    510         };
    511 
    512         // UI elements
    513         private PreferenceCategory mQualityCategory;
    514         private ListPreference mPasswordQuality;
    515         private EditTextPreference mMinLength;
    516         private EditTextPreference mMinLetters;
    517         private EditTextPreference mMinNumeric;
    518         private EditTextPreference mMinLowerCase;
    519         private EditTextPreference mMinUpperCase;
    520         private EditTextPreference mMinSymbols;
    521         private EditTextPreference mMinNonLetter;
    522 
    523         @Override
    524         public void onCreate(Bundle savedInstanceState) {
    525             super.onCreate(savedInstanceState);
    526             addPreferencesFromResource(R.xml.device_admin_quality);
    527 
    528             mQualityCategory = (PreferenceCategory) findPreference(KEY_CATEGORY_QUALITY);
    529             mPasswordQuality = (ListPreference) findPreference(KEY_QUALITY);
    530             mMinLength = (EditTextPreference) findPreference(KEY_MIN_LENGTH);
    531             mMinLetters = (EditTextPreference) findPreference(KEY_MIN_LETTERS);
    532             mMinNumeric = (EditTextPreference) findPreference(KEY_MIN_NUMERIC);
    533             mMinLowerCase = (EditTextPreference) findPreference(KEY_MIN_LOWER_CASE);
    534             mMinUpperCase = (EditTextPreference) findPreference(KEY_MIN_UPPER_CASE);
    535             mMinSymbols = (EditTextPreference) findPreference(KEY_MIN_SYMBOLS);
    536             mMinNonLetter = (EditTextPreference) findPreference(KEY_MIN_NON_LETTER);
    537 
    538             mPasswordQuality.setOnPreferenceChangeListener(this);
    539             mMinLength.setOnPreferenceChangeListener(this);
    540             mMinLetters.setOnPreferenceChangeListener(this);
    541             mMinNumeric.setOnPreferenceChangeListener(this);
    542             mMinLowerCase.setOnPreferenceChangeListener(this);
    543             mMinUpperCase.setOnPreferenceChangeListener(this);
    544             mMinSymbols.setOnPreferenceChangeListener(this);
    545             mMinNonLetter.setOnPreferenceChangeListener(this);
    546 
    547             // Finish setup of the quality dropdown
    548             mPasswordQuality.setEntryValues(mPasswordQualityValueStrings);
    549         }
    550 
    551         @Override
    552         public void onResume() {
    553             super.onResume();
    554             mQualityCategory.setEnabled(mAdminActive);
    555         }
    556 
    557         /**
    558          * Update the summaries of each item to show the local setting and the global setting.
    559          */
    560         @Override
    561         protected void reloadSummaries() {
    562             super.reloadSummaries();
    563             // Show numeric settings for each policy API
    564             int local, global;
    565             local = mDPM.getPasswordQuality(mDeviceAdminSample);
    566             global = mDPM.getPasswordQuality(null);
    567             mPasswordQuality.setSummary(
    568                     localGlobalSummary(qualityValueToString(local), qualityValueToString(global)));
    569             local = mDPM.getPasswordMinimumLength(mDeviceAdminSample);
    570             global = mDPM.getPasswordMinimumLength(null);
    571             mMinLength.setSummary(localGlobalSummary(local, global));
    572             local = mDPM.getPasswordMinimumLetters(mDeviceAdminSample);
    573             global = mDPM.getPasswordMinimumLetters(null);
    574             mMinLetters.setSummary(localGlobalSummary(local, global));
    575             local = mDPM.getPasswordMinimumNumeric(mDeviceAdminSample);
    576             global = mDPM.getPasswordMinimumNumeric(null);
    577             mMinNumeric.setSummary(localGlobalSummary(local, global));
    578             local = mDPM.getPasswordMinimumLowerCase(mDeviceAdminSample);
    579             global = mDPM.getPasswordMinimumLowerCase(null);
    580             mMinLowerCase.setSummary(localGlobalSummary(local, global));
    581             local = mDPM.getPasswordMinimumUpperCase(mDeviceAdminSample);
    582             global = mDPM.getPasswordMinimumUpperCase(null);
    583             mMinUpperCase.setSummary(localGlobalSummary(local, global));
    584             local = mDPM.getPasswordMinimumSymbols(mDeviceAdminSample);
    585             global = mDPM.getPasswordMinimumSymbols(null);
    586             mMinSymbols.setSummary(localGlobalSummary(local, global));
    587             local = mDPM.getPasswordMinimumNonLetter(mDeviceAdminSample);
    588             global = mDPM.getPasswordMinimumNonLetter(null);
    589             mMinNonLetter.setSummary(localGlobalSummary(local, global));
    590         }
    591 
    592         @Override
    593         public boolean onPreferenceChange(Preference preference, Object newValue) {
    594             if (super.onPreferenceChange(preference, newValue)) {
    595                 return true;
    596             }
    597             String valueString = (String)newValue;
    598             if (TextUtils.isEmpty(valueString)) {
    599                 return false;
    600             }
    601             int value = 0;
    602             try {
    603                 value = Integer.parseInt(valueString);
    604             } catch (NumberFormatException nfe) {
    605                 String warning = mActivity.getString(R.string.number_format_warning, valueString);
    606                 Toast.makeText(mActivity, warning, Toast.LENGTH_SHORT).show();
    607             }
    608             if (preference == mPasswordQuality) {
    609                 mDPM.setPasswordQuality(mDeviceAdminSample, value);
    610             } else if (preference == mMinLength) {
    611                 mDPM.setPasswordMinimumLength(mDeviceAdminSample, value);
    612             } else if (preference == mMinLetters) {
    613                 mDPM.setPasswordMinimumLetters(mDeviceAdminSample, value);
    614             } else if (preference == mMinNumeric) {
    615                 mDPM.setPasswordMinimumNumeric(mDeviceAdminSample, value);
    616             } else if (preference == mMinLowerCase) {
    617                 mDPM.setPasswordMinimumLowerCase(mDeviceAdminSample, value);
    618             } else if (preference == mMinUpperCase) {
    619                 mDPM.setPasswordMinimumUpperCase(mDeviceAdminSample, value);
    620             } else if (preference == mMinSymbols) {
    621                 mDPM.setPasswordMinimumSymbols(mDeviceAdminSample, value);
    622             } else if (preference == mMinNonLetter) {
    623                 mDPM.setPasswordMinimumNonLetter(mDeviceAdminSample, value);
    624             }
    625             // Delay update because the change is only applied after exiting this method.
    626             postReloadSummaries();
    627             return true;
    628         }
    629 
    630         private String qualityValueToString(int quality) {
    631             for (int i=  0; i < mPasswordQualityValues.length; i++) {
    632                 if (mPasswordQualityValues[i] == quality) {
    633                     String[] qualities =
    634                         mActivity.getResources().getStringArray(R.array.password_qualities);
    635                     return qualities[i];
    636                 }
    637             }
    638             return "(0x" + Integer.toString(quality, 16) + ")";
    639         }
    640     }
    641 
    642     /**
    643      * PreferenceFragment for "password expiration" preferences.
    644      */
    645     public static class ExpirationFragment extends AdminSampleFragment
    646             implements OnPreferenceChangeListener, OnPreferenceClickListener {
    647         private PreferenceCategory mExpirationCategory;
    648         private EditTextPreference mHistory;
    649         private EditTextPreference mExpirationTimeout;
    650         private PreferenceScreen mExpirationStatus;
    651 
    652         @Override
    653         public void onCreate(Bundle savedInstanceState) {
    654             super.onCreate(savedInstanceState);
    655             addPreferencesFromResource(R.xml.device_admin_expiration);
    656 
    657             mExpirationCategory = (PreferenceCategory) findPreference(KEY_CATEGORY_EXPIRATION);
    658             mHistory = (EditTextPreference) findPreference(KEY_HISTORY);
    659             mExpirationTimeout = (EditTextPreference) findPreference(KEY_EXPIRATION_TIMEOUT);
    660             mExpirationStatus = (PreferenceScreen) findPreference(KEY_EXPIRATION_STATUS);
    661 
    662             mHistory.setOnPreferenceChangeListener(this);
    663             mExpirationTimeout.setOnPreferenceChangeListener(this);
    664             mExpirationStatus.setOnPreferenceClickListener(this);
    665         }
    666 
    667         @Override
    668         public void onResume() {
    669             super.onResume();
    670             mExpirationCategory.setEnabled(mAdminActive);
    671         }
    672 
    673         /**
    674          * Update the summaries of each item to show the local setting and the global setting.
    675          */
    676         @Override
    677         protected void reloadSummaries() {
    678             super.reloadSummaries();
    679 
    680             int local, global;
    681             local = mDPM.getPasswordHistoryLength(mDeviceAdminSample);
    682             global = mDPM.getPasswordHistoryLength(null);
    683             mHistory.setSummary(localGlobalSummary(local, global));
    684 
    685             long localLong, globalLong;
    686             localLong = mDPM.getPasswordExpirationTimeout(mDeviceAdminSample);
    687             globalLong = mDPM.getPasswordExpirationTimeout(null);
    688             mExpirationTimeout.setSummary(localGlobalSummary(
    689                     localLong / MS_PER_MINUTE, globalLong / MS_PER_MINUTE));
    690 
    691             String expirationStatus = getExpirationStatus();
    692             mExpirationStatus.setSummary(expirationStatus);
    693         }
    694 
    695         @Override
    696         public boolean onPreferenceChange(Preference preference, Object newValue) {
    697             if (super.onPreferenceChange(preference, newValue)) {
    698                 return true;
    699             }
    700             String valueString = (String)newValue;
    701             if (TextUtils.isEmpty(valueString)) {
    702                 return false;
    703             }
    704             int value = 0;
    705             try {
    706                 value = Integer.parseInt(valueString);
    707             } catch (NumberFormatException nfe) {
    708                 String warning = mActivity.getString(R.string.number_format_warning, valueString);
    709                 Toast.makeText(mActivity, warning, Toast.LENGTH_SHORT).show();
    710             }
    711             if (preference == mHistory) {
    712                 mDPM.setPasswordHistoryLength(mDeviceAdminSample, value);
    713             } else if (preference == mExpirationTimeout) {
    714                 mDPM.setPasswordExpirationTimeout(mDeviceAdminSample, value * MS_PER_MINUTE);
    715             }
    716             // Delay update because the change is only applied after exiting this method.
    717             postReloadSummaries();
    718             return true;
    719         }
    720 
    721         @Override
    722         public boolean onPreferenceClick(Preference preference) {
    723             if (super.onPreferenceClick(preference)) {
    724                 return true;
    725             }
    726             if (preference == mExpirationStatus) {
    727                 String expirationStatus = getExpirationStatus();
    728                 mExpirationStatus.setSummary(expirationStatus);
    729                 return true;
    730             }
    731             return false;
    732         }
    733 
    734         /**
    735          * Create a summary string describing the expiration status for the sample app,
    736          * as well as the global (aggregate) status.
    737          */
    738         private String getExpirationStatus() {
    739             // expirations are absolute;  convert to relative for display
    740             long localExpiration = mDPM.getPasswordExpiration(mDeviceAdminSample);
    741             long globalExpiration = mDPM.getPasswordExpiration(null);
    742             long now = System.currentTimeMillis();
    743 
    744             // local expiration
    745             String local;
    746             if (localExpiration == 0) {
    747                 local = mActivity.getString(R.string.expiration_status_none);
    748             } else {
    749                 localExpiration -= now;
    750                 String dms = timeToDaysMinutesSeconds(mActivity, Math.abs(localExpiration));
    751                 if (localExpiration >= 0) {
    752                     local = mActivity.getString(R.string.expiration_status_future, dms);
    753                 } else {
    754                     local = mActivity.getString(R.string.expiration_status_past, dms);
    755                 }
    756             }
    757 
    758             // global expiration
    759             String global;
    760             if (globalExpiration == 0) {
    761                 global = mActivity.getString(R.string.expiration_status_none);
    762             } else {
    763                 globalExpiration -= now;
    764                 String dms = timeToDaysMinutesSeconds(mActivity, Math.abs(globalExpiration));
    765                 if (globalExpiration >= 0) {
    766                     global = mActivity.getString(R.string.expiration_status_future, dms);
    767                 } else {
    768                     global = mActivity.getString(R.string.expiration_status_past, dms);
    769                 }
    770             }
    771             return mActivity.getString(R.string.status_local_global, local, global);
    772         }
    773     }
    774 
    775     /**
    776      * PreferenceFragment for "lock screen & wipe" preferences.
    777      */
    778     public static class LockWipeFragment extends AdminSampleFragment
    779             implements OnPreferenceChangeListener, OnPreferenceClickListener {
    780         private PreferenceCategory mLockWipeCategory;
    781         private EditTextPreference mMaxTimeScreenLock;
    782         private EditTextPreference mMaxFailures;
    783         private PreferenceScreen mLockScreen;
    784         private PreferenceScreen mWipeData;
    785         private PreferenceScreen mWipeAppData;
    786 
    787         @Override
    788         public void onCreate(Bundle savedInstanceState) {
    789             super.onCreate(savedInstanceState);
    790             addPreferencesFromResource(R.xml.device_admin_lock_wipe);
    791 
    792             mLockWipeCategory = (PreferenceCategory) findPreference(KEY_CATEGORY_LOCK_WIPE);
    793             mMaxTimeScreenLock = (EditTextPreference) findPreference(KEY_MAX_TIME_SCREEN_LOCK);
    794             mMaxFailures = (EditTextPreference) findPreference(KEY_MAX_FAILS_BEFORE_WIPE);
    795             mLockScreen = (PreferenceScreen) findPreference(KEY_LOCK_SCREEN);
    796             mWipeData = (PreferenceScreen) findPreference(KEY_WIPE_DATA);
    797             mWipeAppData = (PreferenceScreen) findPreference(KEY_WIP_DATA_ALL);
    798 
    799             mMaxTimeScreenLock.setOnPreferenceChangeListener(this);
    800             mMaxFailures.setOnPreferenceChangeListener(this);
    801             mLockScreen.setOnPreferenceClickListener(this);
    802             mWipeData.setOnPreferenceClickListener(this);
    803             mWipeAppData.setOnPreferenceClickListener(this);
    804         }
    805 
    806         @Override
    807         public void onResume() {
    808             super.onResume();
    809             mLockWipeCategory.setEnabled(mAdminActive);
    810         }
    811 
    812         /**
    813          * Update the summaries of each item to show the local setting and the global setting.
    814          */
    815         @Override
    816         protected void reloadSummaries() {
    817             super.reloadSummaries();
    818 
    819             long localLong, globalLong;
    820             localLong = mDPM.getMaximumTimeToLock(mDeviceAdminSample);
    821             globalLong = mDPM.getMaximumTimeToLock(null);
    822             mMaxTimeScreenLock.setSummary(localGlobalSummary(
    823                     localLong / MS_PER_MINUTE, globalLong / MS_PER_MINUTE));
    824 
    825             int local, global;
    826             local = mDPM.getMaximumFailedPasswordsForWipe(mDeviceAdminSample);
    827             global = mDPM.getMaximumFailedPasswordsForWipe(null);
    828             mMaxFailures.setSummary(localGlobalSummary(local, global));
    829         }
    830 
    831         @Override
    832         public boolean onPreferenceChange(Preference preference, Object newValue) {
    833             if (super.onPreferenceChange(preference, newValue)) {
    834                 return true;
    835             }
    836             String valueString = (String)newValue;
    837             if (TextUtils.isEmpty(valueString)) {
    838                 return false;
    839             }
    840             int value = 0;
    841             try {
    842                 value = Integer.parseInt(valueString);
    843             } catch (NumberFormatException nfe) {
    844                 String warning = mActivity.getString(R.string.number_format_warning, valueString);
    845                 Toast.makeText(mActivity, warning, Toast.LENGTH_SHORT).show();
    846             }
    847             if (preference == mMaxTimeScreenLock) {
    848                 mDPM.setMaximumTimeToLock(mDeviceAdminSample, value * MS_PER_MINUTE);
    849             } else if (preference == mMaxFailures) {
    850                 if (alertIfMonkey(mActivity, R.string.monkey_wipe_data)) {
    851                     return true;
    852                 }
    853                 mDPM.setMaximumFailedPasswordsForWipe(mDeviceAdminSample, value);
    854             }
    855             // Delay update because the change is only applied after exiting this method.
    856             postReloadSummaries();
    857             return true;
    858         }
    859 
    860         @Override
    861         public boolean onPreferenceClick(Preference preference) {
    862             if (super.onPreferenceClick(preference)) {
    863                 return true;
    864             }
    865             if (preference == mLockScreen) {
    866                 if (alertIfMonkey(mActivity, R.string.monkey_lock_screen)) {
    867                     return true;
    868                 }
    869                 mDPM.lockNow();
    870                 return true;
    871             } else if (preference == mWipeData || preference == mWipeAppData) {
    872                 if (alertIfMonkey(mActivity, R.string.monkey_wipe_data)) {
    873                     return true;
    874                 }
    875                 promptForRealDeviceWipe(preference == mWipeAppData);
    876                 return true;
    877             }
    878             return false;
    879         }
    880 
    881         /**
    882          * Wiping data is real, so we don't want it to be easy.  Show two alerts before wiping.
    883          */
    884         private void promptForRealDeviceWipe(final boolean wipeAllData) {
    885             final DeviceAdminSample activity = mActivity;
    886 
    887             AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    888             builder.setMessage(R.string.wipe_warning_first);
    889             builder.setPositiveButton(R.string.wipe_warning_first_ok,
    890                     new DialogInterface.OnClickListener() {
    891                 @Override
    892                 public void onClick(DialogInterface dialog, int which) {
    893                     AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    894                     if (wipeAllData) {
    895                         builder.setMessage(R.string.wipe_warning_second_full);
    896                     } else {
    897                         builder.setMessage(R.string.wipe_warning_second);
    898                     }
    899                     builder.setPositiveButton(R.string.wipe_warning_second_ok,
    900                             new DialogInterface.OnClickListener() {
    901                         @Override
    902                         public void onClick(DialogInterface dialog, int which) {
    903                             boolean stillActive = mActivity.isActiveAdmin();
    904                             if (stillActive) {
    905                                 mDPM.wipeData(wipeAllData
    906                                         ? DevicePolicyManager.WIPE_EXTERNAL_STORAGE : 0);
    907                             }
    908                         }
    909                     });
    910                     builder.setNegativeButton(R.string.wipe_warning_second_no, null);
    911                     builder.show();
    912                 }
    913             });
    914             builder.setNegativeButton(R.string.wipe_warning_first_no, null);
    915             builder.show();
    916         }
    917     }
    918 
    919     /**
    920      * PreferenceFragment for "encryption" preferences.
    921      */
    922     public static class EncryptionFragment extends AdminSampleFragment
    923             implements OnPreferenceChangeListener, OnPreferenceClickListener {
    924         private PreferenceCategory mEncryptionCategory;
    925         private CheckBoxPreference mRequireEncryption;
    926         private PreferenceScreen mActivateEncryption;
    927 
    928         @Override
    929         public void onCreate(Bundle savedInstanceState) {
    930             super.onCreate(savedInstanceState);
    931             addPreferencesFromResource(R.xml.device_admin_encryption);
    932 
    933             mEncryptionCategory = (PreferenceCategory) findPreference(KEY_CATEGORY_ENCRYPTION);
    934             mRequireEncryption = (CheckBoxPreference) findPreference(KEY_REQUIRE_ENCRYPTION);
    935             mActivateEncryption = (PreferenceScreen) findPreference(KEY_ACTIVATE_ENCRYPTION);
    936 
    937             mRequireEncryption.setOnPreferenceChangeListener(this);
    938             mActivateEncryption.setOnPreferenceClickListener(this);
    939         }
    940 
    941         @Override
    942         public void onResume() {
    943             super.onResume();
    944             mEncryptionCategory.setEnabled(mAdminActive);
    945             mRequireEncryption.setChecked(mDPM.getStorageEncryption(mDeviceAdminSample));
    946         }
    947 
    948         /**
    949          * Update the summaries of each item to show the local setting and the global setting.
    950          */
    951         @Override
    952         protected void reloadSummaries() {
    953             super.reloadSummaries();
    954 
    955             boolean local, global;
    956             local = mDPM.getStorageEncryption(mDeviceAdminSample);
    957             global = mDPM.getStorageEncryption(null);
    958             mRequireEncryption.setSummary(localGlobalSummary(local, global));
    959 
    960             int deviceStatusCode = mDPM.getStorageEncryptionStatus();
    961             String deviceStatus = statusCodeToString(deviceStatusCode);
    962             String status = mActivity.getString(R.string.status_device_encryption, deviceStatus);
    963             mActivateEncryption.setSummary(status);
    964         }
    965 
    966         @Override
    967         public boolean onPreferenceChange(Preference preference, Object newValue) {
    968             if (super.onPreferenceChange(preference, newValue)) {
    969                 return true;
    970             }
    971             if (preference == mRequireEncryption) {
    972                 boolean newActive = (Boolean) newValue;
    973                 mDPM.setStorageEncryption(mDeviceAdminSample, newActive);
    974                 // Delay update because the change is only applied after exiting this method.
    975                 postReloadSummaries();
    976                 return true;
    977             }
    978             return true;
    979         }
    980 
    981         @Override
    982         public boolean onPreferenceClick(Preference preference) {
    983             if (super.onPreferenceClick(preference)) {
    984                 return true;
    985             }
    986             if (preference == mActivateEncryption) {
    987                 if (alertIfMonkey(mActivity, R.string.monkey_encryption)) {
    988                     return true;
    989                 }
    990                 // Check to see if encryption is even supported on this device (it's optional).
    991                 if (mDPM.getStorageEncryptionStatus() ==
    992                         DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) {
    993                     AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
    994                     builder.setMessage(R.string.encryption_not_supported);
    995                     builder.setPositiveButton(R.string.encryption_not_supported_ok, null);
    996                     builder.show();
    997                     return true;
    998                 }
    999                 // Launch the activity to activate encryption.  May or may not return!
   1000                 Intent intent = new Intent(DevicePolicyManager.ACTION_START_ENCRYPTION);
   1001                 startActivityForResult(intent, REQUEST_CODE_START_ENCRYPTION);
   1002                 return true;
   1003             }
   1004             return false;
   1005         }
   1006 
   1007         private String statusCodeToString(int newStatusCode) {
   1008             int newStatus = R.string.encryption_status_unknown;
   1009             switch (newStatusCode) {
   1010                 case DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED:
   1011                     newStatus = R.string.encryption_status_unsupported;
   1012                     break;
   1013                 case DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE:
   1014                     newStatus = R.string.encryption_status_inactive;
   1015                     break;
   1016                 case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING:
   1017                     newStatus = R.string.encryption_status_activating;
   1018                     break;
   1019                 case DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE:
   1020                     newStatus = R.string.encryption_status_active;
   1021                     break;
   1022             }
   1023             return mActivity.getString(newStatus);
   1024         }
   1025     }
   1026 
   1027     /**
   1028      * Simple converter used for long expiration times reported in mSec.
   1029      */
   1030     private static String timeToDaysMinutesSeconds(Context context, long time) {
   1031         long days = time / MS_PER_DAY;
   1032         long hours = (time / MS_PER_HOUR) % 24;
   1033         long minutes = (time / MS_PER_MINUTE) % 60;
   1034         return context.getString(R.string.status_days_hours_minutes, days, hours, minutes);
   1035     }
   1036 
   1037     /**
   1038      * If the "user" is a monkey, post an alert and notify the caller.  This prevents automated
   1039      * test frameworks from stumbling into annoying or dangerous operations.
   1040      */
   1041     private static boolean alertIfMonkey(Context context, int stringId) {
   1042         if (ActivityManager.isUserAMonkey()) {
   1043             AlertDialog.Builder builder = new AlertDialog.Builder(context);
   1044             builder.setMessage(stringId);
   1045             builder.setPositiveButton(R.string.monkey_ok, null);
   1046             builder.show();
   1047             return true;
   1048         } else {
   1049             return false;
   1050         }
   1051     }
   1052 
   1053     /**
   1054      * Sample implementation of a DeviceAdminReceiver.  Your controller must provide one,
   1055      * although you may or may not implement all of the methods shown here.
   1056      *
   1057      * All callbacks are on the UI thread and your implementations should not engage in any
   1058      * blocking operations, including disk I/O.
   1059      */
   1060     public static class DeviceAdminSampleReceiver extends DeviceAdminReceiver {
   1061         void showToast(Context context, String msg) {
   1062             String status = context.getString(R.string.admin_receiver_status, msg);
   1063             Toast.makeText(context, status, Toast.LENGTH_SHORT).show();
   1064         }
   1065 
   1066         @Override
   1067         public void onReceive(Context context, Intent intent) {
   1068             if (intent.getAction() == ACTION_DEVICE_ADMIN_DISABLE_REQUESTED) {
   1069                 abortBroadcast();
   1070             }
   1071             super.onReceive(context, intent);
   1072         }
   1073 
   1074         @Override
   1075         public void onEnabled(Context context, Intent intent) {
   1076             showToast(context, context.getString(R.string.admin_receiver_status_enabled));
   1077         }
   1078 
   1079         @Override
   1080         public CharSequence onDisableRequested(Context context, Intent intent) {
   1081             return context.getString(R.string.admin_receiver_status_disable_warning);
   1082         }
   1083 
   1084         @Override
   1085         public void onDisabled(Context context, Intent intent) {
   1086             showToast(context, context.getString(R.string.admin_receiver_status_disabled));
   1087         }
   1088 
   1089         @Override
   1090         public void onPasswordChanged(Context context, Intent intent) {
   1091             showToast(context, context.getString(R.string.admin_receiver_status_pw_changed));
   1092         }
   1093 
   1094         @Override
   1095         public void onPasswordFailed(Context context, Intent intent) {
   1096             showToast(context, context.getString(R.string.admin_receiver_status_pw_failed));
   1097         }
   1098 
   1099         @Override
   1100         public void onPasswordSucceeded(Context context, Intent intent) {
   1101             showToast(context, context.getString(R.string.admin_receiver_status_pw_succeeded));
   1102         }
   1103 
   1104         @Override
   1105         public void onPasswordExpiring(Context context, Intent intent) {
   1106             DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
   1107                     Context.DEVICE_POLICY_SERVICE);
   1108             long expr = dpm.getPasswordExpiration(
   1109                     new ComponentName(context, DeviceAdminSampleReceiver.class));
   1110             long delta = expr - System.currentTimeMillis();
   1111             boolean expired = delta < 0L;
   1112             String message = context.getString(expired ?
   1113                     R.string.expiration_status_past : R.string.expiration_status_future);
   1114             showToast(context, message);
   1115             Log.v(TAG, message);
   1116         }
   1117     }
   1118 }
   1119