SecurityPolicy.java revision 899c5b866192a4c4a12413446d10e5d98dbf94fa
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(NotificationController.NOTIFICATION_ID_SECURITY_NEEDED,
367                notification);
368    }
369
370    /**
371     * Called from the notification's intent receiver to register that the notification can be
372     * cleared now.
373     */
374    public void clearNotification(long accountId) {
375        NotificationManager notificationManager =
376            (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
377        notificationManager.cancel(NotificationController.NOTIFICATION_ID_SECURITY_NEEDED);
378    }
379
380    /**
381     * API: Remote wipe (from server).  This is final, there is no confirmation.  It will only
382     * return to the caller if there is an unexpected failure.
383     */
384    public void remoteWipe() {
385        DevicePolicyManager dpm = getDPM();
386        if (dpm.isAdminActive(mAdminName)) {
387            dpm.wipeData(0);
388        } else {
389            Log.d(Email.LOG_TAG, "Could not remote wipe because not device admin.");
390        }
391    }
392
393    /**
394     * Class for tracking policies and reading/writing into accounts
395     */
396    public static class PolicySet implements Parcelable {
397
398        // Security (provisioning) flags
399            // bits 0..4: password length (0=no password required)
400        private static final int PASSWORD_LENGTH_MASK = 31;
401        private static final int PASSWORD_LENGTH_SHIFT = 0;
402        public static final int PASSWORD_LENGTH_MAX = 30;
403            // bits 5..8: password mode
404        private static final int PASSWORD_MODE_SHIFT = 5;
405        private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT;
406        public static final int PASSWORD_MODE_NONE = 0 << PASSWORD_MODE_SHIFT;
407        public static final int PASSWORD_MODE_SIMPLE = 1 << PASSWORD_MODE_SHIFT;
408        public static final int PASSWORD_MODE_STRONG = 2 << PASSWORD_MODE_SHIFT;
409            // bits 9..13: password failures -> wipe device (0=disabled)
410        private static final int PASSWORD_MAX_FAILS_SHIFT = 9;
411        private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT;
412        public static final int PASSWORD_MAX_FAILS_MAX = 31;
413            // bits 14..24: seconds to screen lock (0=not required)
414        private static final int SCREEN_LOCK_TIME_SHIFT = 14;
415        private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT;
416        public static final int SCREEN_LOCK_TIME_MAX = 2047;
417            // bit 25: remote wipe capability required
418        private static final int REQUIRE_REMOTE_WIPE = 1 << 25;
419            // bit 26..35: password expiration (days; 0=not required)
420        private static final int PASSWORD_EXPIRATION_SHIFT = 26;
421        private static final long PASSWORD_EXPIRATION_MASK = 1023L << PASSWORD_EXPIRATION_SHIFT;
422        public static final int PASSWORD_EXPIRATION_MAX = 1023;
423            // bit 35..42: password history (length; 0=not required)
424        private static final int PASSWORD_HISTORY_SHIFT = 36;
425        private static final long PASSWORD_HISTORY_MASK = 255L << PASSWORD_HISTORY_SHIFT;
426        public static final int PASSWORD_HISTORY_MAX = 255;
427            // bit 42..46: min complex characters (0=not required)
428        private static final int PASSWORD_COMPLEX_CHARS_SHIFT = 44;
429        private static final long PASSWORD_COMPLEX_CHARS_MASK = 31L << PASSWORD_COMPLEX_CHARS_SHIFT;
430        public static final int PASSWORD_COMPLEX_CHARS_MAX = 31;
431
432        /*package*/ final int mMinPasswordLength;
433        /*package*/ final int mPasswordMode;
434        /*package*/ final int mMaxPasswordFails;
435        /*package*/ final int mMaxScreenLockTime;
436        /*package*/ final boolean mRequireRemoteWipe;
437        /*package*/ final int mPasswordExpiration;
438        /*package*/ final int mPasswordHistory;
439        /*package*/ final int mPasswordComplexChars;
440
441        public int getMinPasswordLengthForTest() {
442            return mMinPasswordLength;
443        }
444
445        public int getPasswordModeForTest() {
446            return mPasswordMode;
447        }
448
449        public int getMaxPasswordFailsForTest() {
450            return mMaxPasswordFails;
451        }
452
453        public int getMaxScreenLockTimeForTest() {
454            return mMaxScreenLockTime;
455        }
456
457        public boolean isRequireRemoteWipeForTest() {
458            return mRequireRemoteWipe;
459        }
460
461        /**
462         * Create from raw values.
463         * @param minPasswordLength (0=not enforced)
464         * @param passwordMode
465         * @param maxPasswordFails (0=not enforced)
466         * @param maxScreenLockTime in seconds (0=not enforced)
467         * @param requireRemoteWipe
468         * @throws IllegalArgumentException for illegal arguments.
469         */
470        public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
471                int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpiration,
472                int passwordHistory, int passwordComplexChars) throws IllegalArgumentException {
473            // If we're not enforcing passwords, make sure we clean up related values, since EAS
474            // can send non-zero values for any or all of these
475            if (passwordMode == PASSWORD_MODE_NONE) {
476                maxPasswordFails = 0;
477                maxScreenLockTime = 0;
478                minPasswordLength = 0;
479                passwordComplexChars = 0;
480                passwordHistory = 0;
481                passwordExpiration = 0;
482            } else {
483                if ((passwordMode != PASSWORD_MODE_SIMPLE) &&
484                        (passwordMode != PASSWORD_MODE_STRONG)) {
485                    throw new IllegalArgumentException("password mode");
486                }
487                // If we're only requiring a simple password, set complex chars to zero; note
488                // that EAS can erroneously send non-zero values in this case
489                if (passwordMode == PASSWORD_MODE_SIMPLE) {
490                    passwordComplexChars = 0;
491                }
492                // The next four values have hard limits which cannot be supported if exceeded.
493                if (minPasswordLength > PASSWORD_LENGTH_MAX) {
494                    throw new IllegalArgumentException("password length");
495                }
496                if (passwordExpiration > PASSWORD_EXPIRATION_MAX) {
497                    throw new IllegalArgumentException("password expiration");
498                }
499                if (passwordHistory > PASSWORD_HISTORY_MAX) {
500                    throw new IllegalArgumentException("password history");
501                }
502                if (passwordComplexChars > PASSWORD_COMPLEX_CHARS_MAX) {
503                    throw new IllegalArgumentException("complex chars");
504                }
505                // This value can be reduced (which actually increases security) if necessary
506                if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
507                    maxPasswordFails = PASSWORD_MAX_FAILS_MAX;
508                }
509                // This value can be reduced (which actually increases security) if necessary
510                if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
511                    maxScreenLockTime = SCREEN_LOCK_TIME_MAX;
512                }
513            }
514            mMinPasswordLength = minPasswordLength;
515            mPasswordMode = passwordMode;
516            mMaxPasswordFails = maxPasswordFails;
517            mMaxScreenLockTime = maxScreenLockTime;
518            mRequireRemoteWipe = requireRemoteWipe;
519            mPasswordExpiration = passwordExpiration;
520            mPasswordHistory = passwordHistory;
521            mPasswordComplexChars = passwordComplexChars;
522        }
523
524        /**
525         * Create from values encoded in an account
526         * @param account
527         */
528        public PolicySet(Account account) {
529            this(account.mSecurityFlags);
530        }
531
532        /**
533         * Create from values encoded in an account flags int
534         */
535        public PolicySet(long flags) {
536            mMinPasswordLength =
537                (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT);
538            mPasswordMode =
539                (int) (flags & PASSWORD_MODE_MASK);
540            mMaxPasswordFails =
541                (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT);
542            mMaxScreenLockTime =
543                (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT);
544            mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
545            mPasswordExpiration =
546                (int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT);
547            mPasswordHistory =
548                (int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT);
549            mPasswordComplexChars =
550                (int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT);
551        }
552
553        /**
554         * Helper to map our internal encoding to DevicePolicyManager password modes.
555         */
556        public int getDPManagerPasswordQuality() {
557            switch (mPasswordMode) {
558                case PASSWORD_MODE_SIMPLE:
559                    return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
560                case PASSWORD_MODE_STRONG:
561                    return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
562                default:
563                    return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
564            }
565        }
566
567        /**
568         * Record flags (and a sync key for the flags) into an Account
569         * Note: the hash code is defined as the encoding used in Account
570         *
571         * @param account to write the values mSecurityFlags and mSecuritySyncKey
572         * @param syncKey the value to write into the account's mSecuritySyncKey
573         * @param update if true, also writes the account back to the provider (updating only
574         *  the fields changed by this API)
575         * @param context a context for writing to the provider
576         * @return true if the actual policies changed, false if no change (note, sync key
577         *  does not affect this)
578         */
579        public boolean writeAccount(Account account, String syncKey, boolean update,
580                Context context) {
581            long newFlags = getSecurityCode();
582            boolean dirty = (newFlags != account.mSecurityFlags);
583            account.mSecurityFlags = newFlags;
584            account.mSecuritySyncKey = syncKey;
585            if (update) {
586                if (account.isSaved()) {
587                    ContentValues cv = new ContentValues();
588                    cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
589                    cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
590                    account.update(context, cv);
591                } else {
592                    account.save(context);
593                }
594            }
595            return dirty;
596        }
597
598        @Override
599        public boolean equals(Object o) {
600            if (o instanceof PolicySet) {
601                PolicySet other = (PolicySet)o;
602                return (this.getSecurityCode() == other.getSecurityCode());
603            }
604            return false;
605        }
606
607        /**
608         * Supports Parcelable
609         */
610        public int describeContents() {
611            return 0;
612        }
613
614        /**
615         * Supports Parcelable
616         */
617        public static final Parcelable.Creator<PolicySet> CREATOR
618                = new Parcelable.Creator<PolicySet>() {
619            public PolicySet createFromParcel(Parcel in) {
620                return new PolicySet(in);
621            }
622
623            public PolicySet[] newArray(int size) {
624                return new PolicySet[size];
625            }
626        };
627
628        /**
629         * Supports Parcelable
630         */
631        public void writeToParcel(Parcel dest, int flags) {
632            dest.writeInt(mMinPasswordLength);
633            dest.writeInt(mPasswordMode);
634            dest.writeInt(mMaxPasswordFails);
635            dest.writeInt(mMaxScreenLockTime);
636            dest.writeInt(mRequireRemoteWipe ? 1 : 0);
637            dest.writeInt(mPasswordExpiration);
638            dest.writeInt(mPasswordHistory);
639            dest.writeInt(mPasswordComplexChars);
640        }
641
642        /**
643         * Supports Parcelable
644         */
645        public PolicySet(Parcel in) {
646            mMinPasswordLength = in.readInt();
647            mPasswordMode = in.readInt();
648            mMaxPasswordFails = in.readInt();
649            mMaxScreenLockTime = in.readInt();
650            mRequireRemoteWipe = in.readInt() == 1;
651            mPasswordExpiration = in.readInt();
652            mPasswordHistory = in.readInt();
653            mPasswordComplexChars = in.readInt();
654        }
655
656        @Override
657        public int hashCode() {
658            long code = getSecurityCode();
659            return (int) code;
660        }
661
662        public long getSecurityCode() {
663            long flags = 0;
664            flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
665            flags |= mPasswordMode;
666            flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
667            flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
668            if (mRequireRemoteWipe) {
669                flags |= REQUIRE_REMOTE_WIPE;
670            }
671            flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT;
672            flags |= (long)mPasswordExpiration << PASSWORD_EXPIRATION_SHIFT;
673            flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT;
674            return flags;
675        }
676
677        @Override
678        public String toString() {
679            return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
680                    + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
681                    + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe
682                    + " pw-expiration=" + mPasswordExpiration
683                    + " pw-history=" + mPasswordHistory
684                    + " pw-complex-chars=" + mPasswordComplexChars + "}";
685        }
686    }
687
688    /**
689     * If we are not the active device admin, try to become so.
690     *
691     * @return true if we are already active, false if we are not
692     */
693    public boolean isActiveAdmin() {
694        DevicePolicyManager dpm = getDPM();
695        return dpm.isAdminActive(mAdminName);
696    }
697
698    /**
699     * Report admin component name - for making calls into device policy manager
700     */
701    public ComponentName getAdminComponent() {
702        return mAdminName;
703    }
704
705    /**
706     * Internal handler for enabled->disabled transitions.  Resets all security keys
707     * forcing EAS to resync security state.
708     */
709    /* package */ void onAdminEnabled(boolean isEnabled) {
710        if (!isEnabled) {
711            // transition to disabled state
712            // Response:  clear *all* security state information from the accounts, forcing
713            // them back to the initial configurations requiring policy administration
714            ContentValues cv = new ContentValues();
715            cv.put(AccountColumns.SECURITY_FLAGS, 0);
716            cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
717            mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null);
718            updatePolicies(-1);
719        }
720    }
721
722    /**
723     * Device Policy administrator.  This is primarily a listener for device state changes.
724     * Note:  This is instantiated by incoming messages.
725     * Note:  We do not implement onPasswordFailed() because the default behavior of the
726     *        DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
727     */
728    public static class PolicyAdmin extends DeviceAdminReceiver {
729
730        /**
731         * Called after the administrator is first enabled.
732         */
733        @Override
734        public void onEnabled(Context context, Intent intent) {
735            SecurityPolicy.getInstance(context).onAdminEnabled(true);
736        }
737
738        /**
739         * Called prior to the administrator being disabled.
740         */
741        @Override
742        public void onDisabled(Context context, Intent intent) {
743            SecurityPolicy.getInstance(context).onAdminEnabled(false);
744        }
745
746        /**
747         * Called after the user has changed their password.
748         */
749        @Override
750        public void onPasswordChanged(Context context, Intent intent) {
751            Account.clearSecurityHoldOnAllAccounts(context);
752        }
753    }
754}
755