SecurityPolicy.java revision c263810b08943541135a24e2b7520692152455cc
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.ContentValues;
32import android.content.Context;
33import android.content.Intent;
34import android.database.Cursor;
35import android.media.AudioManager;
36import android.net.Uri;
37import android.os.Parcel;
38import android.os.Parcelable;
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, 0, 0, 0);
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
64    /**
65     * Get the security policy instance
66     */
67    public synchronized static SecurityPolicy getInstance(Context context) {
68        if (sInstance == null) {
69            sInstance = new SecurityPolicy(context);
70        }
71        return sInstance;
72    }
73
74    /**
75     * Private constructor (one time only)
76     */
77    private SecurityPolicy(Context context) {
78        mContext = context.getApplicationContext();
79        mDPM = null;
80        mAdminName = new ComponentName(context, PolicyAdmin.class);
81        mAggregatePolicy = null;
82    }
83
84    /**
85     * For testing only: Inject context into already-created instance
86     */
87    /* package */ void setContext(Context context) {
88        mContext = context;
89    }
90
91    /**
92     * Compute the aggregate policy for all accounts that require it, and record it.
93     *
94     * The business logic is as follows:
95     *  min password length         take the max
96     *  password mode               take the max (strongest mode)
97     *  max password fails          take the min
98     *  max screen lock time        take the min
99     *  require remote wipe         take the max (logical or)
100     *  password history            take the max (strongest mode)
101     *  password expiration         take the max (strongest mode)
102     *  password complex chars      take the max (strongest mode)
103     *
104     * @return a policy representing the strongest aggregate.  If no policy sets are defined,
105     * a lightweight "nothing required" policy will be returned.  Never null.
106     */
107    /*package*/ PolicySet computeAggregatePolicy() {
108        boolean policiesFound = false;
109
110        int minPasswordLength = Integer.MIN_VALUE;
111        int passwordMode = Integer.MIN_VALUE;
112        int maxPasswordFails = Integer.MAX_VALUE;
113        int maxScreenLockTime = Integer.MAX_VALUE;
114        boolean requireRemoteWipe = false;
115        int passwordHistory = Integer.MIN_VALUE;
116        int passwordExpiration = Integer.MIN_VALUE;
117        int passwordComplexChars = Integer.MIN_VALUE;
118
119        Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
120                ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null);
121        try {
122            while (c.moveToNext()) {
123                long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS);
124                if (flags != 0) {
125                    PolicySet p = new PolicySet(flags);
126                    minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
127                    passwordMode  = Math.max(p.mPasswordMode, passwordMode);
128                    if (p.mMaxPasswordFails > 0) {
129                        maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
130                    }
131                    if (p.mMaxScreenLockTime > 0) {
132                        maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime);
133                    }
134                    if (p.mPasswordHistory > 0) {
135                        passwordHistory = Math.max(p.mPasswordHistory, passwordHistory);
136                    }
137                    if (p.mPasswordExpiration > 0) {
138                        passwordExpiration = Math.max(p.mPasswordExpiration, passwordExpiration);
139                    }
140                    if (p.mPasswordComplexChars > 0) {
141                        passwordComplexChars = Math.max(p.mPasswordComplexChars,
142                                passwordComplexChars);
143                    }
144                    requireRemoteWipe |= p.mRequireRemoteWipe;
145                    policiesFound = true;
146                }
147            }
148        } finally {
149            c.close();
150        }
151        if (policiesFound) {
152            // final cleanup pass converts any untouched min/max values to zero (not specified)
153            if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0;
154            if (passwordMode == Integer.MIN_VALUE) passwordMode = 0;
155            if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0;
156            if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0;
157            if (passwordHistory == Integer.MIN_VALUE) passwordHistory = 0;
158            if (passwordExpiration == Integer.MIN_VALUE) passwordExpiration = 0;
159            if (passwordComplexChars == Integer.MIN_VALUE) passwordComplexChars = 0;
160
161            return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
162                    maxScreenLockTime, requireRemoteWipe, passwordExpiration, passwordHistory,
163                    passwordComplexChars);
164        } else {
165            return NO_POLICY_SET;
166        }
167    }
168
169    /**
170     * Return updated aggregate policy, from cached value if possible
171     */
172    public synchronized PolicySet getAggregatePolicy() {
173        if (mAggregatePolicy == null) {
174            mAggregatePolicy = computeAggregatePolicy();
175        }
176        return mAggregatePolicy;
177    }
178
179    /**
180     * Get the dpm.  This mainly allows us to make some utility calls without it, for testing.
181     */
182    private synchronized DevicePolicyManager getDPM() {
183        if (mDPM == null) {
184            mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
185        }
186        return mDPM;
187    }
188
189    /**
190     * API: Report that policies may have been updated due to rewriting values in an Account.
191     * @param accountId the account that has been updated, -1 if unknown/deleted
192     */
193    public synchronized void updatePolicies(long accountId) {
194        mAggregatePolicy = null;
195    }
196
197    /**
198     * API: Report that policies may have been updated *and* the caller vouches that the
199     * change is a reduction in policies.  This forces an immediate change to device state.
200     * Typically used when deleting accounts, although we may use it for server-side policy
201     * rollbacks.
202     */
203    public void reducePolicies() {
204        updatePolicies(-1);
205        setActivePolicies();
206    }
207
208    /**
209     * API: Query used to determine if a given policy is "active" (the device is operating at
210     * the required security level).
211     *
212     * This can be used when syncing a specific account, by passing a specific set of policies
213     * for that account.  Or, it can be used at any time to compare the device
214     * state against the aggregate set of device policies stored in all accounts.
215     *
216     * This method is for queries only, and does not trigger any change in device state.
217     *
218     * @param policies the policies requested, or null to check aggregate stored policies
219     * @return true if the policies are active, false if not active
220     */
221    public boolean isActive(PolicySet policies) {
222        // select aggregate set if needed
223        if (policies == null) {
224            policies = getAggregatePolicy();
225        }
226        // quick check for the "empty set" of no policies
227        if (policies == NO_POLICY_SET) {
228            return true;
229        }
230        DevicePolicyManager dpm = getDPM();
231        if (dpm.isAdminActive(mAdminName)) {
232            // check each policy explicitly
233            if (policies.mMinPasswordLength > 0) {
234                if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) {
235                    return false;
236                }
237            }
238            if (policies.mPasswordMode > 0) {
239                if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
240                    return false;
241                }
242                if (!dpm.isActivePasswordSufficient()) {
243                    return false;
244                }
245            }
246            if (policies.mMaxScreenLockTime > 0) {
247                // Note, we use seconds, dpm uses milliseconds
248                if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) {
249                    return false;
250                }
251            }
252            if (policies.mPasswordExpiration > 0) {
253                // TODO Complete when DPM supports this
254            }
255            if (policies.mPasswordHistory > 0) {
256                if (dpm.getPasswordHistoryLength(mAdminName) < policies.mPasswordHistory) {
257                    return false;
258                }
259            }
260            if (policies.mPasswordComplexChars > 0) {
261                if (dpm.getPasswordMinimumNonLetter(mAdminName) < policies.mPasswordComplexChars) {
262                    return false;
263                }
264            }
265            // password failures are counted locally - no test required here
266            // no check required for remote wipe (it's supported, if we're the admin)
267
268            // making it this far means we passed!
269            return true;
270        }
271        // return false, not active
272        return false;
273    }
274
275    /**
276     * Set the requested security level based on the aggregate set of requests.
277     * If the set is empty, we release our device administration.  If the set is non-empty,
278     * we only proceed if we are already active as an admin.
279     */
280    public void setActivePolicies() {
281        DevicePolicyManager dpm = getDPM();
282        // compute aggregate set of policies
283        PolicySet policies = getAggregatePolicy();
284        // if empty set, detach from policy manager
285        if (policies == NO_POLICY_SET) {
286            dpm.removeActiveAdmin(mAdminName);
287        } else if (dpm.isAdminActive(mAdminName)) {
288            // set each policy in the policy manager
289            // password mode & length
290            dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality());
291            dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength);
292            // screen lock time
293            dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000);
294            // local wipe (failed passwords limit)
295            dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails);
296            // password expiration (days until a password expires)
297            // TODO set this when DPM allows it
298            // password history length (number of previous passwords that may not be reused)
299            dpm.setPasswordHistoryLength(mAdminName, policies.mPasswordHistory);
300            // password minimum complex characters
301            dpm.setPasswordMinimumNonLetter(mAdminName, policies.mPasswordComplexChars);
302        }
303    }
304
305    /**
306     * API: Set/Clear the "hold" flag in any account.  This flag serves a dual purpose:
307     * Setting it gives us an indication that it was blocked, and clearing it gives EAS a
308     * signal to try syncing again.
309     */
310    public void setAccountHoldFlag(Account account, boolean newState) {
311        if (newState) {
312            account.mFlags |= Account.FLAGS_SECURITY_HOLD;
313        } else {
314            account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
315        }
316        ContentValues cv = new ContentValues();
317        cv.put(AccountColumns.FLAGS, account.mFlags);
318        account.update(mContext, cv);
319    }
320
321    /**
322     * API: Sync service should call this any time a sync fails due to isActive() returning false.
323     * This will kick off the notify-acquire-admin-state process and/or increase the security level.
324     * The caller needs to write the required policies into this account before making this call.
325     * Should not be called from UI thread - uses DB lookups to prepare new notifications
326     *
327     * @param accountId the account for which sync cannot proceed
328     */
329    public void policiesRequired(long accountId) {
330        Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
331        // Mark the account as "on hold".
332        setAccountHoldFlag(account, true);
333        // Otherwise, 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 implements Parcelable {
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            // bit 26..35: password expiration (days; 0=not required)
419        private static final int PASSWORD_EXPIRATION_SHIFT = 26;
420        private static final long PASSWORD_EXPIRATION_MASK = 1023L << PASSWORD_EXPIRATION_SHIFT;
421        public static final int PASSWORD_EXPIRATION_MAX = 1023;
422            // bit 35..42: password history (length; 0=not required)
423        private static final int PASSWORD_HISTORY_SHIFT = 36;
424        private static final long PASSWORD_HISTORY_MASK = 255L << PASSWORD_HISTORY_SHIFT;
425        public static final int PASSWORD_HISTORY_MAX = 255;
426            // bit 42..46: min complex characters (0=not required)
427        private static final int PASSWORD_COMPLEX_CHARS_SHIFT = 44;
428        private static final long PASSWORD_COMPLEX_CHARS_MASK = 31L << PASSWORD_COMPLEX_CHARS_SHIFT;
429        public static final int PASSWORD_COMPLEX_CHARS_MAX = 31;
430
431        /*package*/ final int mMinPasswordLength;
432        /*package*/ final int mPasswordMode;
433        /*package*/ final int mMaxPasswordFails;
434        /*package*/ final int mMaxScreenLockTime;
435        /*package*/ final boolean mRequireRemoteWipe;
436        /*package*/ final int mPasswordExpiration;
437        /*package*/ final int mPasswordHistory;
438        /*package*/ final int mPasswordComplexChars;
439
440        public int getMinPasswordLength() {
441            return mMinPasswordLength;
442        }
443
444        public int getPasswordMode() {
445            return mPasswordMode;
446        }
447
448        public int getMaxPasswordFails() {
449            return mMaxPasswordFails;
450        }
451
452        public int getMaxScreenLockTime() {
453            return mMaxScreenLockTime;
454        }
455
456        public boolean isRequireRemoteWipe() {
457            return mRequireRemoteWipe;
458        }
459
460        /**
461         * Create from raw values.
462         * @param minPasswordLength (0=not enforced)
463         * @param passwordMode
464         * @param maxPasswordFails (0=not enforced)
465         * @param maxScreenLockTime in seconds (0=not enforced)
466         * @param requireRemoteWipe
467         * @throws IllegalArgumentException for illegal arguments.
468         */
469        public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
470                int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpiration,
471                int passwordHistory, int passwordComplexChars) throws IllegalArgumentException {
472            // If we're not enforcing passwords, make sure we clean up related values, since EAS
473            // can send non-zero values for any or all of these
474            if (passwordMode == PASSWORD_MODE_NONE) {
475                maxPasswordFails = 0;
476                maxScreenLockTime = 0;
477                minPasswordLength = 0;
478                passwordComplexChars = 0;
479                passwordHistory = 0;
480                passwordExpiration = 0;
481            } else {
482                if ((passwordMode != PASSWORD_MODE_SIMPLE) &&
483                        (passwordMode != PASSWORD_MODE_STRONG)) {
484                    throw new IllegalArgumentException("password mode");
485                }
486                // The next four values have hard limits which cannot be supported if exceeded.
487                if (minPasswordLength > PASSWORD_LENGTH_MAX) {
488                    throw new IllegalArgumentException("password length");
489                }
490                if (passwordExpiration > PASSWORD_EXPIRATION_MAX) {
491                    throw new IllegalArgumentException("password expiration");
492                }
493                if (passwordHistory > PASSWORD_HISTORY_MAX) {
494                    throw new IllegalArgumentException("password history");
495                }
496                if (passwordComplexChars > PASSWORD_COMPLEX_CHARS_MAX) {
497                    throw new IllegalArgumentException("complex chars");
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            mPasswordExpiration = passwordExpiration;
514            mPasswordHistory = passwordHistory;
515            mPasswordComplexChars = passwordComplexChars;
516        }
517
518        /**
519         * Create from values encoded in an account
520         * @param account
521         */
522        public PolicySet(Account account) {
523            this(account.mSecurityFlags);
524        }
525
526        /**
527         * Create from values encoded in an account flags int
528         */
529        public PolicySet(long flags) {
530            mMinPasswordLength =
531                (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT);
532            mPasswordMode =
533                (int) (flags & PASSWORD_MODE_MASK);
534            mMaxPasswordFails =
535                (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT);
536            mMaxScreenLockTime =
537                (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT);
538            mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
539            mPasswordExpiration =
540                (int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT);
541            mPasswordHistory =
542                (int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT);
543            mPasswordComplexChars =
544                (int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT);
545        }
546
547        /**
548         * Helper to map our internal encoding to DevicePolicyManager password modes.
549         */
550        public int getDPManagerPasswordQuality() {
551            switch (mPasswordMode) {
552                case PASSWORD_MODE_SIMPLE:
553                    return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
554                case PASSWORD_MODE_STRONG:
555                    return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
556                default:
557                    return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
558            }
559        }
560
561        /**
562         * Record flags (and a sync key for the flags) into an Account
563         * Note: the hash code is defined as the encoding used in Account
564         *
565         * @param account to write the values mSecurityFlags and mSecuritySyncKey
566         * @param syncKey the value to write into the account's mSecuritySyncKey
567         * @param update if true, also writes the account back to the provider (updating only
568         *  the fields changed by this API)
569         * @param context a context for writing to the provider
570         * @return true if the actual policies changed, false if no change (note, sync key
571         *  does not affect this)
572         */
573        public boolean writeAccount(Account account, String syncKey, boolean update,
574                Context context) {
575            long newFlags = getSecurityCode();
576            boolean dirty = (newFlags != account.mSecurityFlags);
577            account.mSecurityFlags = newFlags;
578            account.mSecuritySyncKey = syncKey;
579            if (update) {
580                if (account.isSaved()) {
581                    ContentValues cv = new ContentValues();
582                    cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
583                    cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
584                    account.update(context, cv);
585                } else {
586                    account.save(context);
587                }
588            }
589            return dirty;
590        }
591
592        @Override
593        public boolean equals(Object o) {
594            if (o instanceof PolicySet) {
595                PolicySet other = (PolicySet)o;
596                return (this.getSecurityCode() == other.getSecurityCode());
597            }
598            return false;
599        }
600
601        /**
602         * Supports Parcelable
603         */
604        public int describeContents() {
605            return 0;
606        }
607
608        /**
609         * Supports Parcelable
610         */
611        public static final Parcelable.Creator<PolicySet> CREATOR
612                = new Parcelable.Creator<PolicySet>() {
613            public PolicySet createFromParcel(Parcel in) {
614                return new PolicySet(in);
615            }
616
617            public PolicySet[] newArray(int size) {
618                return new PolicySet[size];
619            }
620        };
621
622        /**
623         * Supports Parcelable
624         */
625        public void writeToParcel(Parcel dest, int flags) {
626            dest.writeInt(mMinPasswordLength);
627            dest.writeInt(mPasswordMode);
628            dest.writeInt(mMaxPasswordFails);
629            dest.writeInt(mMaxScreenLockTime);
630            dest.writeInt(mRequireRemoteWipe ? 1 : 0);
631            dest.writeInt(mPasswordExpiration);
632            dest.writeInt(mPasswordHistory);
633            dest.writeInt(mPasswordComplexChars);
634        }
635
636        /**
637         * Supports Parcelable
638         */
639        public PolicySet(Parcel in) {
640            mMinPasswordLength = in.readInt();
641            mPasswordMode = in.readInt();
642            mMaxPasswordFails = in.readInt();
643            mMaxScreenLockTime = in.readInt();
644            mRequireRemoteWipe = in.readInt() == 1;
645            mPasswordExpiration = in.readInt();
646            mPasswordHistory = in.readInt();
647            mPasswordComplexChars = in.readInt();
648        }
649
650        @Override
651        public int hashCode() {
652            long code = getSecurityCode();
653            return (int) code;
654        }
655
656        public long getSecurityCode() {
657            long flags = 0;
658            flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
659            flags |= mPasswordMode;
660            flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
661            flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
662            if (mRequireRemoteWipe) {
663                flags |= REQUIRE_REMOTE_WIPE;
664            }
665            flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT;
666            flags |= (long)mPasswordExpiration << PASSWORD_EXPIRATION_SHIFT;
667            flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT;
668            return flags;
669        }
670
671        @Override
672        public String toString() {
673            return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
674                    + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
675                    + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe
676                    + " pw-expiration=" + mPasswordExpiration
677                    + " pw-history=" + mPasswordHistory
678                    + " pw-complex-chars=" + mPasswordComplexChars + "}";
679        }
680    }
681
682    /**
683     * If we are not the active device admin, try to become so.
684     *
685     * @return true if we are already active, false if we are not
686     */
687    public boolean isActiveAdmin() {
688        DevicePolicyManager dpm = getDPM();
689        return dpm.isAdminActive(mAdminName);
690    }
691
692    /**
693     * Report admin component name - for making calls into device policy manager
694     */
695    public ComponentName getAdminComponent() {
696        return mAdminName;
697    }
698
699    /**
700     * Internal handler for enabled->disabled transitions.  Resets all security keys
701     * forcing EAS to resync security state.
702     */
703    /* package */ void onAdminEnabled(boolean isEnabled) {
704        if (!isEnabled) {
705            // transition to disabled state
706            // Response:  clear *all* security state information from the accounts, forcing
707            // them back to the initial configurations requiring policy administration
708            ContentValues cv = new ContentValues();
709            cv.put(AccountColumns.SECURITY_FLAGS, 0);
710            cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
711            mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null);
712            updatePolicies(-1);
713        }
714    }
715
716    /**
717     * Device Policy administrator.  This is primarily a listener for device state changes.
718     * Note:  This is instantiated by incoming messages.
719     * Note:  We do not implement onPasswordFailed() because the default behavior of the
720     *        DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
721     */
722    public static class PolicyAdmin extends DeviceAdminReceiver {
723
724        /**
725         * Called after the administrator is first enabled.
726         */
727        @Override
728        public void onEnabled(Context context, Intent intent) {
729            SecurityPolicy.getInstance(context).onAdminEnabled(true);
730        }
731
732        /**
733         * Called prior to the administrator being disabled.
734         */
735        @Override
736        public void onDisabled(Context context, Intent intent) {
737            SecurityPolicy.getInstance(context).onAdminEnabled(false);
738        }
739
740        /**
741         * Called after the user has changed their password.
742         */
743        @Override
744        public void onPasswordChanged(Context context, Intent intent) {
745            Account.clearSecurityHoldOnAllAccounts(context);
746        }
747    }
748}
749