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