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