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