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