SecurityPolicy.java revision 61911d4ff70132fa21c5ee7a987303479e8ef6ae
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                // If we're only requiring a simple password, set complex chars to zero; note
487                // that EAS can erroneously send non-zero values in this case
488                if (passwordMode == PASSWORD_MODE_SIMPLE) {
489                    passwordComplexChars = 0;
490                }
491                // The next four values have hard limits which cannot be supported if exceeded.
492                if (minPasswordLength > PASSWORD_LENGTH_MAX) {
493                    throw new IllegalArgumentException("password length");
494                }
495                if (passwordExpiration > PASSWORD_EXPIRATION_MAX) {
496                    throw new IllegalArgumentException("password expiration");
497                }
498                if (passwordHistory > PASSWORD_HISTORY_MAX) {
499                    throw new IllegalArgumentException("password history");
500                }
501                if (passwordComplexChars > PASSWORD_COMPLEX_CHARS_MAX) {
502                    throw new IllegalArgumentException("complex chars");
503                }
504                // This value can be reduced (which actually increases security) if necessary
505                if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
506                    maxPasswordFails = PASSWORD_MAX_FAILS_MAX;
507                }
508                // This value can be reduced (which actually increases security) if necessary
509                if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
510                    maxScreenLockTime = SCREEN_LOCK_TIME_MAX;
511                }
512            }
513            mMinPasswordLength = minPasswordLength;
514            mPasswordMode = passwordMode;
515            mMaxPasswordFails = maxPasswordFails;
516            mMaxScreenLockTime = maxScreenLockTime;
517            mRequireRemoteWipe = requireRemoteWipe;
518            mPasswordExpiration = passwordExpiration;
519            mPasswordHistory = passwordHistory;
520            mPasswordComplexChars = passwordComplexChars;
521        }
522
523        /**
524         * Create from values encoded in an account
525         * @param account
526         */
527        public PolicySet(Account account) {
528            this(account.mSecurityFlags);
529        }
530
531        /**
532         * Create from values encoded in an account flags int
533         */
534        public PolicySet(long flags) {
535            mMinPasswordLength =
536                (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT);
537            mPasswordMode =
538                (int) (flags & PASSWORD_MODE_MASK);
539            mMaxPasswordFails =
540                (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT);
541            mMaxScreenLockTime =
542                (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT);
543            mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
544            mPasswordExpiration =
545                (int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT);
546            mPasswordHistory =
547                (int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT);
548            mPasswordComplexChars =
549                (int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT);
550        }
551
552        /**
553         * Helper to map our internal encoding to DevicePolicyManager password modes.
554         */
555        public int getDPManagerPasswordQuality() {
556            switch (mPasswordMode) {
557                case PASSWORD_MODE_SIMPLE:
558                    return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
559                case PASSWORD_MODE_STRONG:
560                    return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
561                default:
562                    return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
563            }
564        }
565
566        /**
567         * Record flags (and a sync key for the flags) into an Account
568         * Note: the hash code is defined as the encoding used in Account
569         *
570         * @param account to write the values mSecurityFlags and mSecuritySyncKey
571         * @param syncKey the value to write into the account's mSecuritySyncKey
572         * @param update if true, also writes the account back to the provider (updating only
573         *  the fields changed by this API)
574         * @param context a context for writing to the provider
575         * @return true if the actual policies changed, false if no change (note, sync key
576         *  does not affect this)
577         */
578        public boolean writeAccount(Account account, String syncKey, boolean update,
579                Context context) {
580            long newFlags = getSecurityCode();
581            boolean dirty = (newFlags != account.mSecurityFlags);
582            account.mSecurityFlags = newFlags;
583            account.mSecuritySyncKey = syncKey;
584            if (update) {
585                if (account.isSaved()) {
586                    ContentValues cv = new ContentValues();
587                    cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
588                    cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
589                    account.update(context, cv);
590                } else {
591                    account.save(context);
592                }
593            }
594            return dirty;
595        }
596
597        @Override
598        public boolean equals(Object o) {
599            if (o instanceof PolicySet) {
600                PolicySet other = (PolicySet)o;
601                return (this.getSecurityCode() == other.getSecurityCode());
602            }
603            return false;
604        }
605
606        /**
607         * Supports Parcelable
608         */
609        public int describeContents() {
610            return 0;
611        }
612
613        /**
614         * Supports Parcelable
615         */
616        public static final Parcelable.Creator<PolicySet> CREATOR
617                = new Parcelable.Creator<PolicySet>() {
618            public PolicySet createFromParcel(Parcel in) {
619                return new PolicySet(in);
620            }
621
622            public PolicySet[] newArray(int size) {
623                return new PolicySet[size];
624            }
625        };
626
627        /**
628         * Supports Parcelable
629         */
630        public void writeToParcel(Parcel dest, int flags) {
631            dest.writeInt(mMinPasswordLength);
632            dest.writeInt(mPasswordMode);
633            dest.writeInt(mMaxPasswordFails);
634            dest.writeInt(mMaxScreenLockTime);
635            dest.writeInt(mRequireRemoteWipe ? 1 : 0);
636            dest.writeInt(mPasswordExpiration);
637            dest.writeInt(mPasswordHistory);
638            dest.writeInt(mPasswordComplexChars);
639        }
640
641        /**
642         * Supports Parcelable
643         */
644        public PolicySet(Parcel in) {
645            mMinPasswordLength = in.readInt();
646            mPasswordMode = in.readInt();
647            mMaxPasswordFails = in.readInt();
648            mMaxScreenLockTime = in.readInt();
649            mRequireRemoteWipe = in.readInt() == 1;
650            mPasswordExpiration = in.readInt();
651            mPasswordHistory = in.readInt();
652            mPasswordComplexChars = in.readInt();
653        }
654
655        @Override
656        public int hashCode() {
657            long code = getSecurityCode();
658            return (int) code;
659        }
660
661        public long getSecurityCode() {
662            long flags = 0;
663            flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
664            flags |= mPasswordMode;
665            flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
666            flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
667            if (mRequireRemoteWipe) {
668                flags |= REQUIRE_REMOTE_WIPE;
669            }
670            flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT;
671            flags |= (long)mPasswordExpiration << PASSWORD_EXPIRATION_SHIFT;
672            flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT;
673            return flags;
674        }
675
676        @Override
677        public String toString() {
678            return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
679                    + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
680                    + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe
681                    + " pw-expiration=" + mPasswordExpiration
682                    + " pw-history=" + mPasswordHistory
683                    + " pw-complex-chars=" + mPasswordComplexChars + "}";
684        }
685    }
686
687    /**
688     * If we are not the active device admin, try to become so.
689     *
690     * @return true if we are already active, false if we are not
691     */
692    public boolean isActiveAdmin() {
693        DevicePolicyManager dpm = getDPM();
694        return dpm.isAdminActive(mAdminName);
695    }
696
697    /**
698     * Report admin component name - for making calls into device policy manager
699     */
700    public ComponentName getAdminComponent() {
701        return mAdminName;
702    }
703
704    /**
705     * Internal handler for enabled->disabled transitions.  Resets all security keys
706     * forcing EAS to resync security state.
707     */
708    /* package */ void onAdminEnabled(boolean isEnabled) {
709        if (!isEnabled) {
710            // transition to disabled state
711            // Response:  clear *all* security state information from the accounts, forcing
712            // them back to the initial configurations requiring policy administration
713            ContentValues cv = new ContentValues();
714            cv.put(AccountColumns.SECURITY_FLAGS, 0);
715            cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
716            mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null);
717            updatePolicies(-1);
718        }
719    }
720
721    /**
722     * Device Policy administrator.  This is primarily a listener for device state changes.
723     * Note:  This is instantiated by incoming messages.
724     * Note:  We do not implement onPasswordFailed() because the default behavior of the
725     *        DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
726     */
727    public static class PolicyAdmin extends DeviceAdminReceiver {
728
729        /**
730         * Called after the administrator is first enabled.
731         */
732        @Override
733        public void onEnabled(Context context, Intent intent) {
734            SecurityPolicy.getInstance(context).onAdminEnabled(true);
735        }
736
737        /**
738         * Called prior to the administrator being disabled.
739         */
740        @Override
741        public void onDisabled(Context context, Intent intent) {
742            SecurityPolicy.getInstance(context).onAdminEnabled(false);
743        }
744
745        /**
746         * Called after the user has changed their password.
747         */
748        @Override
749        public void onPasswordChanged(Context context, Intent intent) {
750            Account.clearSecurityHoldOnAllAccounts(context);
751        }
752    }
753}
754