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