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