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