Home | History | Annotate | Download | only in email
      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 
     17 package com.android.email;
     18 
     19 import com.android.email.activity.setup.AccountSecurity;
     20 import com.android.email.provider.EmailContent;
     21 import com.android.email.provider.EmailContent.Account;
     22 import com.android.email.provider.EmailContent.AccountColumns;
     23 import com.android.email.service.MailService;
     24 
     25 import android.app.Notification;
     26 import android.app.NotificationManager;
     27 import android.app.PendingIntent;
     28 import android.app.admin.DeviceAdminReceiver;
     29 import android.app.admin.DevicePolicyManager;
     30 import android.content.ComponentName;
     31 import android.content.ContentResolver;
     32 import android.content.ContentUris;
     33 import android.content.ContentValues;
     34 import android.content.Context;
     35 import android.content.Intent;
     36 import android.database.Cursor;
     37 import android.media.AudioManager;
     38 import android.net.Uri;
     39 import 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  */
     45 public 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);
     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;
     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      *
    114      * @return a policy representing the strongest aggregate.  If no policy sets are defined,
    115      * a lightweight "nothing required" policy will be returned.  Never null.
    116      */
    117     /* package */ PolicySet computeAggregatePolicy() {
    118         boolean policiesFound = false;
    119 
    120         int minPasswordLength = Integer.MIN_VALUE;
    121         int passwordMode = Integer.MIN_VALUE;
    122         int maxPasswordFails = Integer.MAX_VALUE;
    123         int maxScreenLockTime = Integer.MAX_VALUE;
    124         boolean requireRemoteWipe = false;
    125 
    126         Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
    127                 ACCOUNT_SECURITY_PROJECTION, WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
    128         try {
    129             while (c.moveToNext()) {
    130                 int flags = c.getInt(ACCOUNT_SECURITY_COLUMN_FLAGS);
    131                 if (flags != 0) {
    132                     PolicySet p = new PolicySet(flags);
    133                     minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
    134                     passwordMode  = Math.max(p.mPasswordMode, passwordMode);
    135                     if (p.mMaxPasswordFails > 0) {
    136                         maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
    137                     }
    138                     if (p.mMaxScreenLockTime > 0) {
    139                         maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime);
    140                     }
    141                     requireRemoteWipe |= p.mRequireRemoteWipe;
    142                     policiesFound = true;
    143                 }
    144             }
    145         } finally {
    146             c.close();
    147         }
    148         if (policiesFound) {
    149             // final cleanup pass converts any untouched min/max values to zero (not specified)
    150             if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0;
    151             if (passwordMode == Integer.MIN_VALUE) passwordMode = 0;
    152             if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0;
    153             if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0;
    154 
    155             return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
    156                     maxScreenLockTime, requireRemoteWipe);
    157         } else {
    158             return NO_POLICY_SET;
    159         }
    160     }
    161 
    162     /**
    163      * Return updated aggregate policy, from cached value if possible
    164      */
    165     public synchronized PolicySet getAggregatePolicy() {
    166         if (mAggregatePolicy == null) {
    167             mAggregatePolicy = computeAggregatePolicy();
    168         }
    169         return mAggregatePolicy;
    170     }
    171 
    172     /**
    173      * Get the dpm.  This mainly allows us to make some utility calls without it, for testing.
    174      */
    175     private synchronized DevicePolicyManager getDPM() {
    176         if (mDPM == null) {
    177             mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
    178         }
    179         return mDPM;
    180     }
    181 
    182     /**
    183      * API: Report that policies may have been updated due to rewriting values in an Account.
    184      * @param accountId the account that has been updated, -1 if unknown/deleted
    185      */
    186     public synchronized void updatePolicies(long accountId) {
    187         mAggregatePolicy = null;
    188     }
    189 
    190     /**
    191      * API: Report that policies may have been updated *and* the caller vouches that the
    192      * change is a reduction in policies.  This forces an immediate change to device state.
    193      * Typically used when deleting accounts, although we may use it for server-side policy
    194      * rollbacks.
    195      */
    196     public void reducePolicies() {
    197         updatePolicies(-1);
    198         setActivePolicies();
    199     }
    200 
    201     /**
    202      * API: Query used to determine if a given policy is "active" (the device is operating at
    203      * the required security level).
    204      *
    205      * This can be used when syncing a specific account, by passing a specific set of policies
    206      * for that account.  Or, it can be used at any time to compare the device
    207      * state against the aggregate set of device policies stored in all accounts.
    208      *
    209      * This method is for queries only, and does not trigger any change in device state.
    210      *
    211      * @param policies the policies requested, or null to check aggregate stored policies
    212      * @return true if the policies are active, false if not active
    213      */
    214     public boolean isActive(PolicySet policies) {
    215         // select aggregate set if needed
    216         if (policies == null) {
    217             policies = getAggregatePolicy();
    218         }
    219         // quick check for the "empty set" of no policies
    220         if (policies == NO_POLICY_SET) {
    221             return true;
    222         }
    223         DevicePolicyManager dpm = getDPM();
    224         if (dpm.isAdminActive(mAdminName)) {
    225             // check each policy explicitly
    226             if (policies.mMinPasswordLength > 0) {
    227                 if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) {
    228                     return false;
    229                 }
    230             }
    231             if (policies.mPasswordMode > 0) {
    232                 if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
    233                     return false;
    234                 }
    235                 if (!dpm.isActivePasswordSufficient()) {
    236                     return false;
    237                 }
    238             }
    239             if (policies.mMaxScreenLockTime > 0) {
    240                 // Note, we use seconds, dpm uses milliseconds
    241                 if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) {
    242                     return false;
    243                 }
    244             }
    245             // password failures are counted locally - no test required here
    246             // no check required for remote wipe (it's supported, if we're the admin)
    247 
    248             // making it this far means we passed!
    249             return true;
    250         }
    251         // return false, not active
    252         return false;
    253     }
    254 
    255     /**
    256      * Set the requested security level based on the aggregate set of requests.
    257      * If the set is empty, we release our device administration.  If the set is non-empty,
    258      * we only proceed if we are already active as an admin.
    259      */
    260     public void setActivePolicies() {
    261         DevicePolicyManager dpm = getDPM();
    262         // compute aggregate set of policies
    263         PolicySet policies = getAggregatePolicy();
    264         // if empty set, detach from policy manager
    265         if (policies == NO_POLICY_SET) {
    266             dpm.removeActiveAdmin(mAdminName);
    267         } else if (dpm.isAdminActive(mAdminName)) {
    268             // set each policy in the policy manager
    269             // password mode & length
    270             dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality());
    271             dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength);
    272             // screen lock time
    273             dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000);
    274             // local wipe (failed passwords limit)
    275             dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails);
    276         }
    277     }
    278 
    279     /**
    280      * API: Set/Clear the "hold" flag in any account.  This flag serves a dual purpose:
    281      * Setting it gives us an indication that it was blocked, and clearing it gives EAS a
    282      * signal to try syncing again.
    283      */
    284     public void setAccountHoldFlag(Account account, boolean newState) {
    285         if (newState) {
    286             account.mFlags |= Account.FLAGS_SECURITY_HOLD;
    287         } else {
    288             account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
    289         }
    290         ContentValues cv = new ContentValues();
    291         cv.put(AccountColumns.FLAGS, account.mFlags);
    292         account.update(mContext, cv);
    293     }
    294 
    295     /**
    296      * Clear all account hold flags that are set.  This will trigger watchers, and in particular
    297      * will cause EAS to try and resync the account(s).
    298      */
    299     public void clearAccountHoldFlags() {
    300         ContentResolver resolver = mContext.getContentResolver();
    301         Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
    302                 WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
    303         try {
    304             while (c.moveToNext()) {
    305                 int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
    306                 if (0 != (flags & Account.FLAGS_SECURITY_HOLD)) {
    307                     ContentValues cv = new ContentValues();
    308                     cv.put(AccountColumns.FLAGS, flags & ~Account.FLAGS_SECURITY_HOLD);
    309                     long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
    310                     Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
    311                     resolver.update(uri, cv, null, null);
    312                 }
    313             }
    314         } finally {
    315             c.close();
    316         }
    317     }
    318 
    319     /**
    320      * API: Sync service should call this any time a sync fails due to isActive() returning false.
    321      * This will kick off the notify-acquire-admin-state process and/or increase the security level.
    322      * The caller needs to write the required policies into this account before making this call.
    323      * Should not be called from UI thread - uses DB lookups to prepare new notifications
    324      *
    325      * @param accountId the account for which sync cannot proceed
    326      */
    327     public void policiesRequired(long accountId) {
    328         Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
    329 
    330         // Mark the account as "on hold".
    331         setAccountHoldFlag(account, true);
    332 
    333         // 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 {
    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 
    419         /*package*/ final int mMinPasswordLength;
    420         /*package*/ final int mPasswordMode;
    421         /*package*/ final int mMaxPasswordFails;
    422         /*package*/ final int mMaxScreenLockTime;
    423         /*package*/ final boolean mRequireRemoteWipe;
    424 
    425         public int getMinPasswordLengthForTest() {
    426             return mMinPasswordLength;
    427         }
    428 
    429         public int getPasswordModeForTest() {
    430             return mPasswordMode;
    431         }
    432 
    433         public int getMaxPasswordFailsForTest() {
    434             return mMaxPasswordFails;
    435         }
    436 
    437         public int getMaxScreenLockTimeForTest() {
    438             return mMaxScreenLockTime;
    439         }
    440 
    441         public boolean isRequireRemoteWipeForTest() {
    442             return mRequireRemoteWipe;
    443         }
    444 
    445         /**
    446          * Create from raw values.
    447          * @param minPasswordLength (0=not enforced)
    448          * @param passwordMode
    449          * @param maxPasswordFails (0=not enforced)
    450          * @param maxScreenLockTime in seconds (0=not enforced)
    451          * @param requireRemoteWipe
    452          * @throws IllegalArgumentException for illegal arguments.
    453          */
    454         public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
    455                 int maxScreenLockTime, boolean requireRemoteWipe) throws IllegalArgumentException {
    456             // If we're not enforcing passwords, make sure we clean up related values, since EAS
    457             // can send non-zero values for any or all of these
    458             if (passwordMode == PASSWORD_MODE_NONE) {
    459                 maxPasswordFails = 0;
    460                 maxScreenLockTime = 0;
    461                 minPasswordLength = 0;
    462             } else {
    463                 if ((passwordMode != PASSWORD_MODE_SIMPLE) &&
    464                         (passwordMode != PASSWORD_MODE_STRONG)) {
    465                     throw new IllegalArgumentException("password mode");
    466                 }
    467                 // The next value has a hard limit which cannot be supported if exceeded.
    468                 if (minPasswordLength > PASSWORD_LENGTH_MAX) {
    469                     throw new IllegalArgumentException("password length");
    470                 }
    471                 // This value can be reduced (which actually increases security) if necessary
    472                 if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
    473                     maxPasswordFails = PASSWORD_MAX_FAILS_MAX;
    474                 }
    475                 // This value can be reduced (which actually increases security) if necessary
    476                 if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
    477                     maxScreenLockTime = SCREEN_LOCK_TIME_MAX;
    478                 }
    479             }
    480             mMinPasswordLength = minPasswordLength;
    481             mPasswordMode = passwordMode;
    482             mMaxPasswordFails = maxPasswordFails;
    483             mMaxScreenLockTime = maxScreenLockTime;
    484             mRequireRemoteWipe = requireRemoteWipe;
    485         }
    486 
    487         /**
    488          * Create from values encoded in an account
    489          * @param account
    490          */
    491         public PolicySet(Account account) {
    492             this(account.mSecurityFlags);
    493         }
    494 
    495         /**
    496          * Create from values encoded in an account flags int
    497          */
    498         public PolicySet(int flags) {
    499             mMinPasswordLength =
    500                 (flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT;
    501             mPasswordMode =
    502                 (flags & PASSWORD_MODE_MASK);
    503             mMaxPasswordFails =
    504                 (flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT;
    505             mMaxScreenLockTime =
    506                 (flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT;
    507             mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
    508         }
    509 
    510         /**
    511          * Helper to map our internal encoding to DevicePolicyManager password modes.
    512          */
    513         public int getDPManagerPasswordQuality() {
    514             switch (mPasswordMode) {
    515                 case PASSWORD_MODE_SIMPLE:
    516                     return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
    517                 case PASSWORD_MODE_STRONG:
    518                     return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
    519                 default:
    520                     return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
    521             }
    522         }
    523 
    524         /**
    525          * Record flags (and a sync key for the flags) into an Account
    526          * Note: the hash code is defined as the encoding used in Account
    527          *
    528          * @param account to write the values mSecurityFlags and mSecuritySyncKey
    529          * @param syncKey the value to write into the account's mSecuritySyncKey
    530          * @param update if true, also writes the account back to the provider (updating only
    531          *  the fields changed by this API)
    532          * @param context a context for writing to the provider
    533          * @return true if the actual policies changed, false if no change (note, sync key
    534          *  does not affect this)
    535          */
    536         public boolean writeAccount(Account account, String syncKey, boolean update,
    537                 Context context) {
    538             int newFlags = hashCode();
    539             boolean dirty = (newFlags != account.mSecurityFlags);
    540             account.mSecurityFlags = newFlags;
    541             account.mSecuritySyncKey = syncKey;
    542             if (update) {
    543                 if (account.isSaved()) {
    544                     ContentValues cv = new ContentValues();
    545                     cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
    546                     cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
    547                     account.update(context, cv);
    548                 } else {
    549                     account.save(context);
    550                 }
    551             }
    552             return dirty;
    553         }
    554 
    555         @Override
    556         public boolean equals(Object o) {
    557             if (o instanceof PolicySet) {
    558                 PolicySet other = (PolicySet)o;
    559                 return (this.mMinPasswordLength == other.mMinPasswordLength)
    560                         && (this.mPasswordMode == other.mPasswordMode)
    561                         && (this.mMaxPasswordFails == other.mMaxPasswordFails)
    562                         && (this.mMaxScreenLockTime == other.mMaxScreenLockTime)
    563                         && (this.mRequireRemoteWipe == other.mRequireRemoteWipe);
    564             }
    565             return false;
    566         }
    567 
    568         /**
    569          * Note: the hash code is defined as the encoding used in Account
    570          */
    571         @Override
    572         public int hashCode() {
    573             int flags = 0;
    574             flags = mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
    575             flags |= mPasswordMode;
    576             flags |= mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
    577             flags |= mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
    578             if (mRequireRemoteWipe) {
    579                 flags |= REQUIRE_REMOTE_WIPE;
    580             }
    581             return flags;
    582         }
    583 
    584         @Override
    585         public String toString() {
    586             return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
    587                     + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
    588                     + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe + "}";
    589         }
    590     }
    591 
    592     /**
    593      * If we are not the active device admin, try to become so.
    594      *
    595      * @return true if we are already active, false if we are not
    596      */
    597     public boolean isActiveAdmin() {
    598         DevicePolicyManager dpm = getDPM();
    599         return dpm.isAdminActive(mAdminName);
    600     }
    601 
    602     /**
    603      * Report admin component name - for making calls into device policy manager
    604      */
    605     public ComponentName getAdminComponent() {
    606         return mAdminName;
    607     }
    608 
    609     /**
    610      * Internal handler for enabled->disabled transitions.  Resets all security keys
    611      * forcing EAS to resync security state.
    612      */
    613     /* package */ void onAdminEnabled(boolean isEnabled) {
    614         if (!isEnabled) {
    615             // transition to disabled state
    616             // Response:  clear *all* security state information from the accounts, forcing
    617             // them back to the initial configurations requiring policy administration
    618             ContentValues cv = new ContentValues();
    619             cv.put(AccountColumns.SECURITY_FLAGS, 0);
    620             cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
    621             mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null);
    622             updatePolicies(-1);
    623         }
    624     }
    625 
    626     /**
    627      * Device Policy administrator.  This is primarily a listener for device state changes.
    628      * Note:  This is instantiated by incoming messages.
    629      * Note:  We do not implement onPasswordFailed() because the default behavior of the
    630      *        DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
    631      */
    632     public static class PolicyAdmin extends DeviceAdminReceiver {
    633 
    634         /**
    635          * Called after the administrator is first enabled.
    636          */
    637         @Override
    638         public void onEnabled(Context context, Intent intent) {
    639             SecurityPolicy.getInstance(context).onAdminEnabled(true);
    640         }
    641 
    642         /**
    643          * Called prior to the administrator being disabled.
    644          */
    645         @Override
    646         public void onDisabled(Context context, Intent intent) {
    647             SecurityPolicy.getInstance(context).onAdminEnabled(false);
    648         }
    649 
    650         /**
    651          * Called after the user has changed their password.
    652          */
    653         @Override
    654         public void onPasswordChanged(Context context, Intent intent) {
    655             SecurityPolicy.getInstance(context).clearAccountHoldFlags();
    656         }
    657     }
    658 }
    659