SecurityPolicy.java revision 968be441b4c253668c4ee1c7a3f8e4b0eb12cf24
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.android.email;
18
19import com.android.email.activity.setup.AccountSecurity;
20import com.android.email.provider.EmailContent;
21import com.android.email.provider.EmailContent.Account;
22import com.android.email.provider.EmailContent.AccountColumns;
23import com.android.email.service.MailService;
24
25import android.app.Notification;
26import android.app.NotificationManager;
27import android.app.PendingIntent;
28import android.app.admin.DeviceAdminReceiver;
29import android.app.admin.DevicePolicyManager;
30import android.content.ComponentName;
31import android.content.ContentResolver;
32import android.content.ContentUris;
33import android.content.ContentValues;
34import android.content.Context;
35import android.content.Intent;
36import android.database.Cursor;
37import android.media.AudioManager;
38import android.net.Uri;
39import android.util.Log;
40
41/**
42 * Utility functions to support reading and writing security policies, and handshaking the device
43 * into and out of various security states.
44 */
45public class SecurityPolicy {
46
47    private static SecurityPolicy sInstance = null;
48    private Context mContext;
49    private DevicePolicyManager mDPM;
50    private ComponentName mAdminName;
51    private PolicySet mAggregatePolicy;
52
53    /* package */ static final PolicySet NO_POLICY_SET =
54            new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false);
55
56    /**
57     * This projection on Account is for scanning/reading
58     */
59    private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] {
60        AccountColumns.ID, AccountColumns.SECURITY_FLAGS
61    };
62    private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1;
63    // Note, this handles the NULL case to deal with older accounts where the column was added
64    private static final String WHERE_ACCOUNT_SECURITY_NONZERO =
65        Account.SECURITY_FLAGS + " IS NOT NULL AND " + Account.SECURITY_FLAGS + "!=0";
66
67    /**
68     * This projection on Account is for clearing the "security hold" column.  Also includes
69     * the security flags column, so we can use it for selecting.
70     */
71    private static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] {
72        AccountColumns.ID, AccountColumns.FLAGS, AccountColumns.SECURITY_FLAGS
73    };
74    private static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
75    private static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1;
76
77   /**
78    * These are hardcoded limits based on knowledge of the current DevicePolicyManager
79    * and screen lock mechanisms.  Wherever possible, these should be replaced with queries of
80    * dynamic capabilities of the device (e.g. what password modes are supported?)
81    */
82   private static final int LIMIT_MIN_PASSWORD_LENGTH = 16;
83   private static final int LIMIT_PASSWORD_MODE = PolicySet.PASSWORD_MODE_STRONG;
84   private static final int LIMIT_SCREENLOCK_TIME = PolicySet.SCREEN_LOCK_TIME_MAX;
85
86    /**
87     * Get the security policy instance
88     */
89    public synchronized static SecurityPolicy getInstance(Context context) {
90        if (sInstance == null) {
91            sInstance = new SecurityPolicy(context);
92        }
93        return sInstance;
94    }
95
96    /**
97     * Private constructor (one time only)
98     */
99    private SecurityPolicy(Context context) {
100        mContext = context.getApplicationContext();
101        mDPM = null;
102        mAdminName = new ComponentName(context, PolicyAdmin.class);
103        mAggregatePolicy = null;
104    }
105
106    /**
107     * For testing only: Inject context into already-created instance
108     */
109    /* package */ void setContext(Context context) {
110        mContext = context;
111    }
112
113    /**
114     * Compute the aggregate policy for all accounts that require it, and record it.
115     *
116     * The business logic is as follows:
117     *  min password length         take the max
118     *  password mode               take the max (strongest mode)
119     *  max password fails          take the min
120     *  max screen lock time        take the min
121     *  require remote wipe         take the max (logical or)
122     *
123     * @return a policy representing the strongest aggregate.  If no policy sets are defined,
124     * a lightweight "nothing required" policy will be returned.  Never null.
125     */
126    /* package */ PolicySet computeAggregatePolicy() {
127        boolean policiesFound = false;
128
129        int minPasswordLength = Integer.MIN_VALUE;
130        int passwordMode = Integer.MIN_VALUE;
131        int maxPasswordFails = Integer.MAX_VALUE;
132        int maxScreenLockTime = Integer.MAX_VALUE;
133        boolean requireRemoteWipe = false;
134
135        Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
136                ACCOUNT_SECURITY_PROJECTION, WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
137        try {
138            while (c.moveToNext()) {
139                int flags = c.getInt(ACCOUNT_SECURITY_COLUMN_FLAGS);
140                if (flags != 0) {
141                    PolicySet p = new PolicySet(flags);
142                    minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
143                    passwordMode  = Math.max(p.mPasswordMode, passwordMode);
144                    if (p.mMaxPasswordFails > 0) {
145                        maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
146                    }
147                    if (p.mMaxScreenLockTime > 0) {
148                        maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime);
149                    }
150                    requireRemoteWipe |= p.mRequireRemoteWipe;
151                    policiesFound = true;
152                }
153            }
154        } finally {
155            c.close();
156        }
157        if (policiesFound) {
158            // final cleanup pass converts any untouched min/max values to zero (not specified)
159            if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0;
160            if (passwordMode == Integer.MIN_VALUE) passwordMode = 0;
161            if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0;
162            if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0;
163
164            return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
165                    maxScreenLockTime, requireRemoteWipe);
166        } else {
167            return NO_POLICY_SET;
168        }
169    }
170
171    /**
172     * Return updated aggregate policy, from cached value if possible
173     */
174    public synchronized PolicySet getAggregatePolicy() {
175        if (mAggregatePolicy == null) {
176            mAggregatePolicy = computeAggregatePolicy();
177        }
178        return mAggregatePolicy;
179    }
180
181    /**
182     * Get the dpm.  This mainly allows us to make some utility calls without it, for testing.
183     */
184    private synchronized DevicePolicyManager getDPM() {
185        if (mDPM == null) {
186            mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
187        }
188        return mDPM;
189    }
190
191    /**
192     * API: Query used to determine if a given policy is "possible" (irrespective of current
193     * device state.  This is used when creating new accounts.
194     *
195     * TODO: This is hardcoded based on knowledge of the current DevicePolicyManager
196     * and screen lock mechanisms.  It would be nice to replace these tests with something
197     * more dynamic.
198     *
199     * @param policies the policies requested
200     * @return true if the policies are supported, false if not supported
201     */
202    public boolean isSupported(PolicySet policies) {
203        if (policies.mMinPasswordLength > LIMIT_MIN_PASSWORD_LENGTH) {
204            return false;
205        }
206        if (policies.mPasswordMode > LIMIT_PASSWORD_MODE ) {
207            return false;
208        }
209        // No limit on password fail count
210        if (policies.mMaxScreenLockTime > LIMIT_SCREENLOCK_TIME ) {
211            return false;
212        }
213        // No limit on remote wipe capable
214
215        return true;
216    }
217
218    /**
219     * API: Report that policies may have been updated due to rewriting values in an Account.
220     * @param accountId the account that has been updated, -1 if unknown/deleted
221     */
222    public synchronized void updatePolicies(long accountId) {
223        mAggregatePolicy = null;
224    }
225
226    /**
227     * API: Report that policies may have been updated *and* the caller vouches that the
228     * change is a reduction in policies.  This forces an immediate change to device state.
229     * Typically used when deleting accounts, although we may use it for server-side policy
230     * rollbacks.
231     */
232    public void reducePolicies() {
233        updatePolicies(-1);
234        setActivePolicies();
235    }
236
237    /**
238     * API: Query used to determine if a given policy is "active" (the device is operating at
239     * the required security level).
240     *
241     * This can be used when syncing a specific account, by passing a specific set of policies
242     * for that account.  Or, it can be used at any time to compare the device
243     * state against the aggregate set of device policies stored in all accounts.
244     *
245     * This method is for queries only, and does not trigger any change in device state.
246     *
247     * @param policies the policies requested, or null to check aggregate stored policies
248     * @return true if the policies are active, false if not active
249     */
250    public boolean isActive(PolicySet policies) {
251        // select aggregate set if needed
252        if (policies == null) {
253            policies = getAggregatePolicy();
254        }
255        // quick check for the "empty set" of no policies
256        if (policies == NO_POLICY_SET) {
257            return true;
258        }
259        DevicePolicyManager dpm = getDPM();
260        if (dpm.isAdminActive(mAdminName)) {
261            // check each policy explicitly
262            if (policies.mMinPasswordLength > 0) {
263                if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) {
264                    return false;
265                }
266            }
267            if (policies.mPasswordMode > 0) {
268                if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
269                    return false;
270                }
271                if (!dpm.isActivePasswordSufficient()) {
272                    return false;
273                }
274            }
275            if (policies.mMaxScreenLockTime > 0) {
276                // Note, we use seconds, dpm uses milliseconds
277                if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) {
278                    return false;
279                }
280            }
281            // password failures are counted locally - no test required here
282            // no check required for remote wipe (it's supported, if we're the admin)
283
284            // making it this far means we passed!
285            return true;
286        }
287        // return false, not active
288        return false;
289    }
290
291    /**
292     * Set the requested security level based on the aggregate set of requests.
293     * If the set is empty, we release our device administration.  If the set is non-empty,
294     * we only proceed if we are already active as an admin.
295     */
296    public void setActivePolicies() {
297        DevicePolicyManager dpm = getDPM();
298        // compute aggregate set of policies
299        PolicySet policies = getAggregatePolicy();
300        // if empty set, detach from policy manager
301        if (policies == NO_POLICY_SET) {
302            dpm.removeActiveAdmin(mAdminName);
303        } else if (dpm.isAdminActive(mAdminName)) {
304            // set each policy in the policy manager
305            // password mode & length
306            dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality());
307            dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength);
308            // screen lock time
309            dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000);
310            // local wipe (failed passwords limit)
311            dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails);
312        }
313    }
314
315    /**
316     * API: Set/Clear the "hold" flag in any account.  This flag serves a dual purpose:
317     * Setting it gives us an indication that it was blocked, and clearing it gives EAS a
318     * signal to try syncing again.
319     */
320    public void setAccountHoldFlag(Account account, boolean newState) {
321        if (newState) {
322            account.mFlags |= Account.FLAGS_SECURITY_HOLD;
323        } else {
324            account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
325        }
326        ContentValues cv = new ContentValues();
327        cv.put(AccountColumns.FLAGS, account.mFlags);
328        account.update(mContext, cv);
329    }
330
331    /**
332     * Clear all account hold flags that are set.  This will trigger watchers, and in particular
333     * will cause EAS to try and resync the account(s).
334     */
335    public void clearAccountHoldFlags() {
336        ContentResolver resolver = mContext.getContentResolver();
337        Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
338                WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
339        try {
340            while (c.moveToNext()) {
341                int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
342                if (0 != (flags & Account.FLAGS_SECURITY_HOLD)) {
343                    ContentValues cv = new ContentValues();
344                    cv.put(AccountColumns.FLAGS, flags & ~Account.FLAGS_SECURITY_HOLD);
345                    long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
346                    Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
347                    resolver.update(uri, cv, null, null);
348                }
349            }
350        } finally {
351            c.close();
352        }
353    }
354
355    /**
356     * API: Sync service should call this any time a sync fails due to isActive() returning false.
357     * This will kick off the notify-acquire-admin-state process and/or increase the security level.
358     * The caller needs to write the required policies into this account before making this call.
359     * Should not be called from UI thread - uses DB lookups to prepare new notifications
360     *
361     * @param accountId the account for which sync cannot proceed
362     */
363    public void policiesRequired(long accountId) {
364        Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
365
366        // Mark the account as "on hold".
367        setAccountHoldFlag(account, true);
368
369        // Put up a notification
370        String tickerText = mContext.getString(R.string.security_notification_ticker_fmt,
371                account.getDisplayName());
372        String contentTitle = mContext.getString(R.string.security_notification_content_title);
373        String contentText = account.getDisplayName();
374        String ringtoneString = account.getRingtone();
375        Uri ringTone = (ringtoneString == null) ? null : Uri.parse(ringtoneString);
376        boolean vibrate = 0 != (account.mFlags & Account.FLAGS_VIBRATE_ALWAYS);
377        boolean vibrateWhenSilent = 0 != (account.mFlags & Account.FLAGS_VIBRATE_WHEN_SILENT);
378
379        Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, accountId);
380        PendingIntent pending =
381            PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
382
383        Notification notification = new Notification(R.drawable.stat_notify_email_generic,
384                tickerText, System.currentTimeMillis());
385        notification.setLatestEventInfo(mContext, contentTitle, contentText, pending);
386
387        // Use the account's notification rules for sound & vibrate (but always notify)
388        AudioManager audioManager =
389            (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
390        boolean nowSilent =
391            audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
392        notification.sound = ringTone;
393
394        if (vibrate || (vibrateWhenSilent && nowSilent)) {
395            notification.defaults |= Notification.DEFAULT_VIBRATE;
396        }
397        notification.flags |= Notification.FLAG_SHOW_LIGHTS;
398        notification.defaults |= Notification.DEFAULT_LIGHTS;
399
400        NotificationManager notificationManager =
401            (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
402        notificationManager.notify(MailService.NOTIFICATION_ID_SECURITY_NEEDED, notification);
403    }
404
405    /**
406     * Called from the notification's intent receiver to register that the notification can be
407     * cleared now.
408     */
409    public void clearNotification(long accountId) {
410        NotificationManager notificationManager =
411            (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
412        notificationManager.cancel(MailService.NOTIFICATION_ID_SECURITY_NEEDED);
413    }
414
415    /**
416     * API: Remote wipe (from server).  This is final, there is no confirmation.  It will only
417     * return to the caller if there is an unexpected failure.
418     */
419    public void remoteWipe() {
420        DevicePolicyManager dpm = getDPM();
421        if (dpm.isAdminActive(mAdminName)) {
422            dpm.wipeData(0);
423        } else {
424            Log.d(Email.LOG_TAG, "Could not remote wipe because not device admin.");
425        }
426    }
427
428    /**
429     * Class for tracking policies and reading/writing into accounts
430     */
431    public static class PolicySet {
432
433        // Security (provisioning) flags
434            // bits 0..4: password length (0=no password required)
435        private static final int PASSWORD_LENGTH_MASK = 31;
436        private static final int PASSWORD_LENGTH_SHIFT = 0;
437        public static final int PASSWORD_LENGTH_MAX = 30;
438        private static final int PASSWORD_LENGTH_EXCEEDED = 31;
439            // bits 5..8: password mode
440        private static final int PASSWORD_MODE_SHIFT = 5;
441        private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT;
442        public static final int PASSWORD_MODE_NONE = 0 << PASSWORD_MODE_SHIFT;
443        public static final int PASSWORD_MODE_SIMPLE = 1 << PASSWORD_MODE_SHIFT;
444        public static final int PASSWORD_MODE_STRONG = 2 << PASSWORD_MODE_SHIFT;
445            // bits 9..13: password failures -> wipe device (0=disabled)
446        private static final int PASSWORD_MAX_FAILS_SHIFT = 9;
447        private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT;
448        public static final int PASSWORD_MAX_FAILS_MAX = 31;
449            // bits 14..24: seconds to screen lock (0=not required)
450        private static final int SCREEN_LOCK_TIME_SHIFT = 14;
451        private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT;
452        public static final int SCREEN_LOCK_TIME_MAX = 2047;
453            // bit 25: remote wipe capability required
454        private static final int REQUIRE_REMOTE_WIPE = 1 << 25;
455
456        public final int mMinPasswordLength;
457        public final int mPasswordMode;
458        public final int mMaxPasswordFails;
459        public final int mMaxScreenLockTime;
460        public final boolean mRequireRemoteWipe;
461
462        /**
463         * Create from raw values.
464         * @param minPasswordLength (0=not enforced)
465         * @param passwordMode
466         * @param maxPasswordFails (0=not enforced)
467         * @param maxScreenLockTime in seconds (0=not enforced)
468         * @param requireRemoteWipe
469         * @throws IllegalArgumentException for illegal arguments.
470         */
471        public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
472                int maxScreenLockTime, boolean requireRemoteWipe) throws IllegalArgumentException {
473            // This value has a hard limit which cannot be supported if exceeded.  Setting the
474            // exceeded value will force isSupported() to return false.
475            if (minPasswordLength > PASSWORD_LENGTH_MAX) {
476                minPasswordLength = PASSWORD_LENGTH_EXCEEDED;
477            }
478            if (passwordMode < PASSWORD_MODE_NONE
479                    || passwordMode > PASSWORD_MODE_STRONG) {
480                throw new IllegalArgumentException("password mode");
481            }
482            // This value can be reduced (which actually increases security) if necessary
483            if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
484                maxPasswordFails = PASSWORD_MAX_FAILS_MAX;
485            }
486            // This value can be reduced (which actually increases security) if necessary
487            if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
488                maxScreenLockTime = SCREEN_LOCK_TIME_MAX;
489            }
490
491            mMinPasswordLength = minPasswordLength;
492            mPasswordMode = passwordMode;
493            mMaxPasswordFails = maxPasswordFails;
494            mMaxScreenLockTime = maxScreenLockTime;
495            mRequireRemoteWipe = requireRemoteWipe;
496        }
497
498        /**
499         * Create from values encoded in an account
500         * @param account
501         */
502        public PolicySet(Account account) {
503            this(account.mSecurityFlags);
504        }
505
506        /**
507         * Create from values encoded in an account flags int
508         */
509        public PolicySet(int flags) {
510            mMinPasswordLength =
511                (flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT;
512            mPasswordMode =
513                (flags & PASSWORD_MODE_MASK);
514            mMaxPasswordFails =
515                (flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT;
516            mMaxScreenLockTime =
517                (flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT;
518            mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
519        }
520
521        /**
522         * Helper to map our internal encoding to DevicePolicyManager password modes.
523         */
524        public int getDPManagerPasswordQuality() {
525            switch (mPasswordMode) {
526                case PASSWORD_MODE_SIMPLE:
527                    return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
528                case PASSWORD_MODE_STRONG:
529                    return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
530                default:
531                    return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
532            }
533        }
534
535        /**
536         * Record flags (and a sync key for the flags) into an Account
537         * Note: the hash code is defined as the encoding used in Account
538         *
539         * @param account to write the values mSecurityFlags and mSecuritySyncKey
540         * @param syncKey the value to write into the account's mSecuritySyncKey
541         * @param update if true, also writes the account back to the provider (updating only
542         *  the fields changed by this API)
543         * @param context a context for writing to the provider
544         * @return true if the actual policies changed, false if no change (note, sync key
545         *  does not affect this)
546         */
547        public boolean writeAccount(Account account, String syncKey, boolean update,
548                Context context) {
549            int newFlags = hashCode();
550            boolean dirty = (newFlags != account.mSecurityFlags);
551            account.mSecurityFlags = newFlags;
552            account.mSecuritySyncKey = syncKey;
553            if (update) {
554                if (account.isSaved()) {
555                    ContentValues cv = new ContentValues();
556                    cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
557                    cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
558                    account.update(context, cv);
559                } else {
560                    account.save(context);
561                }
562            }
563            return dirty;
564        }
565
566        @Override
567        public boolean equals(Object o) {
568            if (o instanceof PolicySet) {
569                PolicySet other = (PolicySet)o;
570                return (this.mMinPasswordLength == other.mMinPasswordLength)
571                        && (this.mPasswordMode == other.mPasswordMode)
572                        && (this.mMaxPasswordFails == other.mMaxPasswordFails)
573                        && (this.mMaxScreenLockTime == other.mMaxScreenLockTime)
574                        && (this.mRequireRemoteWipe == other.mRequireRemoteWipe);
575            }
576            return false;
577        }
578
579        /**
580         * Note: the hash code is defined as the encoding used in Account
581         */
582        @Override
583        public int hashCode() {
584            int flags = 0;
585            flags = mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
586            flags |= mPasswordMode;
587            flags |= mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
588            flags |= mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
589            if (mRequireRemoteWipe) {
590                flags |= REQUIRE_REMOTE_WIPE;
591            }
592            return flags;
593        }
594
595        @Override
596        public String toString() {
597            return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
598                    + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
599                    + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe + "}";
600        }
601    }
602
603    /**
604     * If we are not the active device admin, try to become so.
605     *
606     * @return true if we are already active, false if we are not
607     */
608    public boolean isActiveAdmin() {
609        DevicePolicyManager dpm = getDPM();
610        return dpm.isAdminActive(mAdminName);
611    }
612
613    /**
614     * Report admin component name - for making calls into device policy manager
615     */
616    public ComponentName getAdminComponent() {
617        return mAdminName;
618    }
619
620    /**
621     * Internal handler for enabled->disabled transitions.  Resets all security keys
622     * forcing EAS to resync security state.
623     */
624    /* package */ void onAdminEnabled(boolean isEnabled) {
625        if (!isEnabled) {
626            // transition to disabled state
627            // Response:  clear *all* security state information from the accounts, forcing
628            // them back to the initial configurations requiring policy administration
629            ContentValues cv = new ContentValues();
630            cv.put(AccountColumns.SECURITY_FLAGS, 0);
631            cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
632            mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null);
633            updatePolicies(-1);
634        }
635    }
636
637    /**
638     * Device Policy administrator.  This is primarily a listener for device state changes.
639     * Note:  This is instantiated by incoming messages.
640     * Note:  We do not implement onPasswordFailed() because the default behavior of the
641     *        DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
642     */
643    public static class PolicyAdmin extends DeviceAdminReceiver {
644
645        /**
646         * Called after the administrator is first enabled.
647         */
648        @Override
649        public void onEnabled(Context context, Intent intent) {
650            SecurityPolicy.getInstance(context).onAdminEnabled(true);
651        }
652
653        /**
654         * Called prior to the administrator being disabled.
655         */
656        @Override
657        public void onDisabled(Context context, Intent intent) {
658            SecurityPolicy.getInstance(context).onAdminEnabled(false);
659        }
660
661        /**
662         * Called after the user has changed their password.
663         */
664        @Override
665        public void onPasswordChanged(Context context, Intent intent) {
666            SecurityPolicy.getInstance(context).clearAccountHoldFlags();
667        }
668    }
669}
670