SecurityPolicy.java revision 308ce9284793b597797994dfb1fb25155cbe0b20
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.service.EmailBroadcastProcessorService; 21import com.android.emailcommon.Logging; 22import com.android.emailcommon.provider.EmailContent; 23import com.android.emailcommon.provider.EmailContent.Account; 24import com.android.emailcommon.provider.EmailContent.AccountColumns; 25import com.android.emailcommon.service.PolicySet; 26 27import android.app.admin.DeviceAdminInfo; 28import android.app.admin.DeviceAdminReceiver; 29import android.app.admin.DevicePolicyManager; 30import android.content.ComponentName; 31import android.content.ContentResolver; 32import android.content.ContentValues; 33import android.content.Context; 34import android.content.Intent; 35import android.database.Cursor; 36import android.os.Environment; 37import android.util.Log; 38 39/** 40 * Utility functions to support reading and writing security policies, and handshaking the device 41 * into and out of various security states. 42 */ 43public class SecurityPolicy { 44 private static final String TAG = "SecurityPolicy"; 45 private static SecurityPolicy sInstance = null; 46 private Context mContext; 47 private DevicePolicyManager mDPM; 48 private ComponentName mAdminName; 49 private PolicySet mAggregatePolicy; 50 51 /* package */ static final PolicySet NO_POLICY_SET = 52 new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0, false, false); 53 54 /** 55 * This projection on Account is for scanning/reading 56 */ 57 private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] { 58 AccountColumns.ID, AccountColumns.SECURITY_FLAGS 59 }; 60 private static final int ACCOUNT_SECURITY_COLUMN_ID = 0; 61 private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1; 62 63 // Messages used for DevicePolicyManager callbacks 64 private static final int DEVICE_ADMIN_MESSAGE_ENABLED = 1; 65 private static final int DEVICE_ADMIN_MESSAGE_DISABLED = 2; 66 private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED = 3; 67 private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING = 4; 68 69 /** 70 * Get the security policy instance 71 */ 72 public synchronized static SecurityPolicy getInstance(Context context) { 73 if (sInstance == null) { 74 sInstance = new SecurityPolicy(context.getApplicationContext()); 75 } 76 return sInstance; 77 } 78 79 /** 80 * Private constructor (one time only) 81 */ 82 private SecurityPolicy(Context context) { 83 mContext = context.getApplicationContext(); 84 mDPM = null; 85 mAdminName = new ComponentName(context, PolicyAdmin.class); 86 mAggregatePolicy = null; 87 } 88 89 /** 90 * For testing only: Inject context into already-created instance 91 */ 92 /* package */ void setContext(Context context) { 93 mContext = context; 94 } 95 96 /** 97 * Compute the aggregate policy for all accounts that require it, and record it. 98 * 99 * The business logic is as follows: 100 * min password length take the max 101 * password mode take the max (strongest mode) 102 * max password fails take the min 103 * max screen lock time take the min 104 * require remote wipe take the max (logical or) 105 * password history take the max (strongest mode) 106 * password expiration take the min (strongest mode) 107 * password complex chars take the max (strongest mode) 108 * encryption take the max (logical or) 109 * encryption (external) take the max (logical or) 110 * 111 * @return a policy representing the strongest aggregate. If no policy sets are defined, 112 * a lightweight "nothing required" policy will be returned. Never null. 113 */ 114 /*package*/ PolicySet computeAggregatePolicy() { 115 boolean policiesFound = false; 116 117 int minPasswordLength = Integer.MIN_VALUE; 118 int passwordMode = Integer.MIN_VALUE; 119 int maxPasswordFails = Integer.MAX_VALUE; 120 int maxScreenLockTime = Integer.MAX_VALUE; 121 boolean requireRemoteWipe = false; 122 int passwordHistory = Integer.MIN_VALUE; 123 int passwordExpirationDays = Integer.MAX_VALUE; 124 int passwordComplexChars = Integer.MIN_VALUE; 125 boolean requireEncryption = false; 126 boolean requireEncryptionExternal = false; 127 128 Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI, 129 ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null); 130 try { 131 while (c.moveToNext()) { 132 long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS); 133 if (flags != 0) { 134 PolicySet p = new PolicySet(flags); 135 minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength); 136 passwordMode = Math.max(p.mPasswordMode, passwordMode); 137 if (p.mMaxPasswordFails > 0) { 138 maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails); 139 } 140 if (p.mMaxScreenLockTime > 0) { 141 maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime); 142 } 143 if (p.mPasswordHistory > 0) { 144 passwordHistory = Math.max(p.mPasswordHistory, passwordHistory); 145 } 146 if (p.mPasswordExpirationDays > 0) { 147 passwordExpirationDays = 148 Math.min(p.mPasswordExpirationDays, passwordExpirationDays); 149 } 150 if (p.mPasswordComplexChars > 0) { 151 passwordComplexChars = Math.max(p.mPasswordComplexChars, 152 passwordComplexChars); 153 } 154 requireRemoteWipe |= p.mRequireRemoteWipe; 155 requireEncryption |= p.mRequireEncryption; 156 requireEncryptionExternal |= p.mRequireEncryptionExternal; 157 policiesFound = true; 158 } 159 } 160 } finally { 161 c.close(); 162 } 163 if (policiesFound) { 164 // final cleanup pass converts any untouched min/max values to zero (not specified) 165 if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0; 166 if (passwordMode == Integer.MIN_VALUE) passwordMode = 0; 167 if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0; 168 if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0; 169 if (passwordHistory == Integer.MIN_VALUE) passwordHistory = 0; 170 if (passwordExpirationDays == Integer.MAX_VALUE) passwordExpirationDays = 0; 171 if (passwordComplexChars == Integer.MIN_VALUE) passwordComplexChars = 0; 172 173 return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails, 174 maxScreenLockTime, requireRemoteWipe, passwordExpirationDays, passwordHistory, 175 passwordComplexChars, requireEncryption, requireEncryptionExternal); 176 } else { 177 return NO_POLICY_SET; 178 } 179 } 180 181 /** 182 * Return updated aggregate policy, from cached value if possible 183 */ 184 public synchronized PolicySet getAggregatePolicy() { 185 if (mAggregatePolicy == null) { 186 mAggregatePolicy = computeAggregatePolicy(); 187 } 188 return mAggregatePolicy; 189 } 190 191 /** 192 * Get the dpm. This mainly allows us to make some utility calls without it, for testing. 193 */ 194 /* package */ synchronized DevicePolicyManager getDPM() { 195 if (mDPM == null) { 196 mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 197 } 198 return mDPM; 199 } 200 201 /** 202 * API: Report that policies may have been updated due to rewriting values in an Account. 203 * @param accountId the account that has been updated, -1 if unknown/deleted 204 */ 205 public synchronized void updatePolicies(long accountId) { 206 mAggregatePolicy = null; 207 } 208 209 /** 210 * API: Report that policies may have been updated *and* the caller vouches that the 211 * change is a reduction in policies. This forces an immediate change to device state. 212 * Typically used when deleting accounts, although we may use it for server-side policy 213 * rollbacks. 214 */ 215 public void reducePolicies() { 216 updatePolicies(-1); 217 setActivePolicies(); 218 } 219 220 /** 221 * API: Query if the proposed set of policies are supported on the device. 222 * 223 * @param policies the polices that were requested 224 * @return boolean if supported 225 */ 226 public boolean isSupported(PolicySet policies) { 227 // IMPLEMENTATION: At this time, the only policy which might not be supported is 228 // encryption (which requires low-level systems support). Other policies are fully 229 // supported by the framework and do not need to be checked. 230 if (policies.mRequireEncryption) { 231 int encryptionStatus = getDPM().getStorageEncryptionStatus(); 232 if (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) { 233 return false; 234 } 235 } 236 if (policies.mRequireEncryptionExternal) { 237 // At this time, we only support "external encryption" when it is provided by virtue 238 // of emulating the external storage inside an encrypted device. 239 if (!policies.mRequireEncryption) return false; 240 if (Environment.isExternalStorageRemovable()) return false; 241 if (!Environment.isExternalStorageEmulated()) return false; 242 } 243 return true; 244 } 245 246 /** 247 * API: Remove any unsupported policies 248 * 249 * This is used when we have a set of polices that have been requested, but the server 250 * is willing to allow unsupported policies to be considered optional. 251 * 252 * @param policies the polices that were requested 253 * @return the same PolicySet if all are supported; A replacement PolicySet if any 254 * unsupported policies were removed 255 */ 256 public PolicySet clearUnsupportedPolicies(PolicySet policies) { 257 PolicySet result = policies; 258 // IMPLEMENTATION: At this time, the only policy which might not be supported is 259 // encryption (which requires low-level systems support). Other policies are fully 260 // supported by the framework and do not need to be checked. 261 if (policies.mRequireEncryption) { 262 int encryptionStatus = getDPM().getStorageEncryptionStatus(); 263 if (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) { 264 // Make new PolicySet w/o encryption 265 result = new PolicySet(policies.mMinPasswordLength, policies.mPasswordMode, 266 policies.mMaxPasswordFails, policies.mMaxScreenLockTime, 267 policies.mRequireRemoteWipe, policies.mPasswordExpirationDays, 268 policies.mPasswordHistory, policies.mPasswordComplexChars, false, false); 269 } 270 } 271 // At this time, we only support "external encryption" when it is provided by virtue 272 // of emulating the external storage inside an encrypted device. 273 if (policies.mRequireEncryptionExternal) { 274 if (Environment.isExternalStorageRemovable() 275 || !Environment.isExternalStorageEmulated()) { 276 // Make new PolicySet w/o encryption 277 result = new PolicySet(policies.mMinPasswordLength, policies.mPasswordMode, 278 policies.mMaxPasswordFails, policies.mMaxScreenLockTime, 279 policies.mRequireRemoteWipe, policies.mPasswordExpirationDays, 280 policies.mPasswordHistory, policies.mPasswordComplexChars, false, false); 281 } 282 } 283 return result; 284 } 285 286 /** 287 * API: Query used to determine if a given policy is "active" (the device is operating at 288 * the required security level). 289 * 290 * @param policies the policies requested, or null to check aggregate stored policies 291 * @return true if the requested policies are active, false if not. 292 */ 293 public boolean isActive(PolicySet policies) { 294 int reasons = getInactiveReasons(policies); 295 return reasons == 0; 296 } 297 298 /** 299 * Return bits from isActive: Device Policy Manager has not been activated 300 */ 301 public final static int INACTIVE_NEED_ACTIVATION = 1; 302 303 /** 304 * Return bits from isActive: Some required configuration is not correct (no user action). 305 */ 306 public final static int INACTIVE_NEED_CONFIGURATION = 2; 307 308 /** 309 * Return bits from isActive: Password needs to be set or updated 310 */ 311 public final static int INACTIVE_NEED_PASSWORD = 4; 312 313 /** 314 * Return bits from isActive: Encryption has not be enabled 315 */ 316 public final static int INACTIVE_NEED_ENCRYPTION = 8; 317 318 /** 319 * API: Query used to determine if a given policy is "active" (the device is operating at 320 * the required security level). 321 * 322 * This can be used when syncing a specific account, by passing a specific set of policies 323 * for that account. Or, it can be used at any time to compare the device 324 * state against the aggregate set of device policies stored in all accounts. 325 * 326 * This method is for queries only, and does not trigger any change in device state. 327 * 328 * NOTE: If there are multiple accounts with password expiration policies, the device 329 * password will be set to expire in the shortest required interval (most secure). This method 330 * will return 'false' as soon as the password expires - irrespective of which account caused 331 * the expiration. In other words, all accounts (that require expiration) will run/stop 332 * based on the requirements of the account with the shortest interval. 333 * 334 * @param policies the policies requested, or null to check aggregate stored policies 335 * @return zero if the requested policies are active, non-zero bits indicates that more work 336 * is needed (typically, by the user) before the required security polices are fully active. 337 */ 338 public int getInactiveReasons(PolicySet policies) { 339 // select aggregate set if needed 340 if (policies == null) { 341 policies = getAggregatePolicy(); 342 } 343 // quick check for the "empty set" of no policies 344 if (policies == NO_POLICY_SET) { 345 return 0; 346 } 347 int reasons = 0; 348 DevicePolicyManager dpm = getDPM(); 349 if (isActiveAdmin()) { 350 // check each policy explicitly 351 if (policies.mMinPasswordLength > 0) { 352 if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) { 353 reasons |= INACTIVE_NEED_PASSWORD; 354 } 355 } 356 if (policies.mPasswordMode > 0) { 357 if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) { 358 reasons |= INACTIVE_NEED_PASSWORD; 359 } 360 if (!dpm.isActivePasswordSufficient()) { 361 reasons |= INACTIVE_NEED_PASSWORD; 362 } 363 } 364 if (policies.mMaxScreenLockTime > 0) { 365 // Note, we use seconds, dpm uses milliseconds 366 if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) { 367 reasons |= INACTIVE_NEED_CONFIGURATION; 368 } 369 } 370 if (policies.mPasswordExpirationDays > 0) { 371 // confirm that expirations are currently set 372 long currentTimeout = dpm.getPasswordExpirationTimeout(mAdminName); 373 if (currentTimeout == 0 374 || currentTimeout > policies.getDPManagerPasswordExpirationTimeout()) { 375 reasons |= INACTIVE_NEED_PASSWORD; 376 } 377 // confirm that the current password hasn't expired 378 long expirationDate = dpm.getPasswordExpiration(mAdminName); 379 long timeUntilExpiration = expirationDate - System.currentTimeMillis(); 380 boolean expired = timeUntilExpiration < 0; 381 if (expired) { 382 reasons |= INACTIVE_NEED_PASSWORD; 383 } 384 } 385 if (policies.mPasswordHistory > 0) { 386 if (dpm.getPasswordHistoryLength(mAdminName) < policies.mPasswordHistory) { 387 reasons |= INACTIVE_NEED_PASSWORD; 388 } 389 } 390 if (policies.mPasswordComplexChars > 0) { 391 if (dpm.getPasswordMinimumNonLetter(mAdminName) < policies.mPasswordComplexChars) { 392 reasons |= INACTIVE_NEED_PASSWORD; 393 } 394 } 395 if (policies.mRequireEncryption) { 396 int encryptionStatus = getDPM().getStorageEncryptionStatus(); 397 if (encryptionStatus != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) { 398 reasons |= INACTIVE_NEED_ENCRYPTION; 399 } 400 } 401 // TODO: If we ever support external storage encryption as a first-class feature, 402 // it will need to be checked here. For now, if there is a policy request for 403 // external storage encryption, it's sufficient that we've activated internal 404 // storage encryption. 405 406 // password failures are counted locally - no test required here 407 // no check required for remote wipe (it's supported, if we're the admin) 408 409 // If we made it all the way, reasons == 0 here. Otherwise it's a list of grievances. 410 return reasons; 411 } 412 // return false, not active 413 return INACTIVE_NEED_ACTIVATION; 414 } 415 416 /** 417 * Set the requested security level based on the aggregate set of requests. 418 * If the set is empty, we release our device administration. If the set is non-empty, 419 * we only proceed if we are already active as an admin. 420 */ 421 public void setActivePolicies() { 422 DevicePolicyManager dpm = getDPM(); 423 // compute aggregate set of policies 424 PolicySet policies = getAggregatePolicy(); 425 // if empty set, detach from policy manager 426 if (policies == NO_POLICY_SET) { 427 dpm.removeActiveAdmin(mAdminName); 428 } else if (isActiveAdmin()) { 429 // set each policy in the policy manager 430 // password mode & length 431 dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality()); 432 dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength); 433 // screen lock time 434 dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000); 435 // local wipe (failed passwords limit) 436 dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails); 437 // password expiration (days until a password expires). API takes mSec. 438 dpm.setPasswordExpirationTimeout(mAdminName, 439 policies.getDPManagerPasswordExpirationTimeout()); 440 // password history length (number of previous passwords that may not be reused) 441 dpm.setPasswordHistoryLength(mAdminName, policies.mPasswordHistory); 442 // password minimum complex characters. 443 // Note, in Exchange, "complex chars" simply means "non alpha", but in the DPM, 444 // setting the quality to complex also defaults min symbols=1 and min numeric=1. 445 // We always / safely clear minSymbols & minNumeric to zero (there is no policy 446 // configuration in which we explicitly require a minimum number of digits or symbols.) 447 dpm.setPasswordMinimumSymbols(mAdminName, 0); 448 dpm.setPasswordMinimumNumeric(mAdminName, 0); 449 dpm.setPasswordMinimumNonLetter(mAdminName, policies.mPasswordComplexChars); 450 // encryption required 451 dpm.setStorageEncryption(mAdminName, policies.mRequireEncryption); 452 // TODO: If we ever support external storage encryption as a first-class feature, 453 // it will need to be set here. For now, if there is a policy request for 454 // external storage encryption, it's sufficient that we've activated internal 455 // storage encryption. 456 } 457 } 458 459 /** 460 * Convenience method; see javadoc below 461 */ 462 public static void setAccountHoldFlag(Context context, long accountId, boolean newState) { 463 Account account = Account.restoreAccountWithId(context, accountId); 464 if (account != null) { 465 setAccountHoldFlag(context, account, newState); 466 } 467 } 468 469 /** 470 * API: Set/Clear the "hold" flag in any account. This flag serves a dual purpose: 471 * Setting it gives us an indication that it was blocked, and clearing it gives EAS a 472 * signal to try syncing again. 473 * @param context 474 * @param account the account whose hold flag is to be set/cleared 475 * @param newState true = security hold, false = free to sync 476 */ 477 public static void setAccountHoldFlag(Context context, Account account, boolean newState) { 478 if (newState) { 479 account.mFlags |= Account.FLAGS_SECURITY_HOLD; 480 } else { 481 account.mFlags &= ~Account.FLAGS_SECURITY_HOLD; 482 } 483 ContentValues cv = new ContentValues(); 484 cv.put(AccountColumns.FLAGS, account.mFlags); 485 account.update(context, cv); 486 } 487 488 /** 489 * API: Sync service should call this any time a sync fails due to isActive() returning false. 490 * This will kick off the notify-acquire-admin-state process and/or increase the security level. 491 * The caller needs to write the required policies into this account before making this call. 492 * Should not be called from UI thread - uses DB lookups to prepare new notifications 493 * 494 * @param accountId the account for which sync cannot proceed 495 */ 496 public void policiesRequired(long accountId) { 497 Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId); 498 // In case the account has been deleted, just return 499 if (account == null) return; 500 501 // Mark the account as "on hold". 502 setAccountHoldFlag(mContext, account, true); 503 504 // Put up a notification 505 NotificationController.getInstance(mContext).showSecurityNeededNotification(account); 506 } 507 508 /** 509 * Called from the notification's intent receiver to register that the notification can be 510 * cleared now. 511 */ 512 public void clearNotification(long accountId) { 513 NotificationController.getInstance(mContext).cancelSecurityNeededNotification(); 514 } 515 516 /** 517 * API: Remote wipe (from server). This is final, there is no confirmation. It will only 518 * return to the caller if there is an unexpected failure. 519 */ 520 public void remoteWipe() { 521 DevicePolicyManager dpm = getDPM(); 522 if (dpm.isAdminActive(mAdminName)) { 523 dpm.wipeData(0); 524 } else { 525 Log.d(Logging.LOG_TAG, "Could not remote wipe because not device admin."); 526 } 527 } 528 /** 529 * If we are not the active device admin, try to become so. 530 * 531 * Also checks for any policies that we have added during the lifetime of this app. 532 * This catches the case where the user granted an earlier (smaller) set of policies 533 * but an app upgrade requires that new policies be granted. 534 * 535 * @return true if we are already active, false if we are not 536 */ 537 public boolean isActiveAdmin() { 538 DevicePolicyManager dpm = getDPM(); 539 return dpm.isAdminActive(mAdminName) 540 && dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD) 541 && dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_ENCRYPTED_STORAGE); 542 } 543 544 /** 545 * Report admin component name - for making calls into device policy manager 546 */ 547 public ComponentName getAdminComponent() { 548 return mAdminName; 549 } 550 551 /** 552 * Delete all accounts whose security flags aren't zero (i.e. they have security enabled). 553 * This method is synchronous, so it should normally be called within a worker thread (the 554 * exception being for unit tests) 555 * 556 * @param context the caller's context 557 */ 558 /*package*/ void deleteSecuredAccounts(Context context) { 559 ContentResolver cr = context.getContentResolver(); 560 // Find all accounts with security and delete them 561 Cursor c = cr.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION, 562 AccountColumns.SECURITY_FLAGS + "!=0", null, null); 563 try { 564 Log.w(TAG, "Email administration disabled; deleting " + c.getCount() + 565 " secured account(s)"); 566 while (c.moveToNext()) { 567 Controller.getInstance(context).deleteAccountSync( 568 c.getLong(EmailContent.ID_PROJECTION_COLUMN), context); 569 } 570 } finally { 571 c.close(); 572 } 573 updatePolicies(-1); 574 } 575 576 /** 577 * Internal handler for enabled->disabled transitions. Deletes all secured accounts. 578 * Must call from worker thread, not on UI thread. 579 */ 580 /*package*/ void onAdminEnabled(boolean isEnabled) { 581 if (!isEnabled) { 582 deleteSecuredAccounts(mContext); 583 } 584 } 585 586 /** 587 * Handle password expiration - if any accounts appear to have triggered this, put up 588 * warnings, or even shut them down. 589 * 590 * NOTE: If there are multiple accounts with password expiration policies, the device 591 * password will be set to expire in the shortest required interval (most secure). The logic 592 * in this method operates based on the aggregate setting - irrespective of which account caused 593 * the expiration. In other words, all accounts (that require expiration) will run/stop 594 * based on the requirements of the account with the shortest interval. 595 */ 596 private void onPasswordExpiring(Context context) { 597 // 1. Do we have any accounts that matter here? 598 long nextExpiringAccountId = findShortestExpiration(context); 599 600 // 2. If not, exit immediately 601 if (nextExpiringAccountId == -1) { 602 return; 603 } 604 605 // 3. If yes, are we warning or expired? 606 long expirationDate = getDPM().getPasswordExpiration(mAdminName); 607 long timeUntilExpiration = expirationDate - System.currentTimeMillis(); 608 boolean expired = timeUntilExpiration < 0; 609 if (!expired) { 610 // 4. If warning, simply put up a generic notification and report that it came from 611 // the shortest-expiring account. 612 NotificationController.getInstance(mContext).showPasswordExpiringNotification( 613 nextExpiringAccountId); 614 } else { 615 // 5. Actually expired - find all accounts that expire passwords, and wipe them 616 boolean wiped = wipeExpiredAccounts(context, Controller.getInstance(context)); 617 if (wiped) { 618 NotificationController.getInstance(mContext).showPasswordExpiredNotification( 619 nextExpiringAccountId); 620 } 621 } 622 } 623 624 /** 625 * Find the account with the shortest expiration time. This is always assumed to be 626 * the account that forces the password to be refreshed. 627 * @return -1 if no expirations, or accountId if one is found 628 */ 629 /* package */ static long findShortestExpiration(Context context) { 630 long nextExpiringAccountId = -1; 631 long shortestExpiration = Long.MAX_VALUE; 632 Cursor c = context.getContentResolver().query(Account.CONTENT_URI, 633 ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null); 634 try { 635 while (c.moveToNext()) { 636 long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS); 637 if (flags != 0) { 638 PolicySet p = new PolicySet(flags); 639 if (p.mPasswordExpirationDays > 0 && 640 p.mPasswordExpirationDays < shortestExpiration) { 641 nextExpiringAccountId = c.getLong(ACCOUNT_SECURITY_COLUMN_ID); 642 shortestExpiration = p.mPasswordExpirationDays; 643 } 644 } 645 } 646 } finally { 647 c.close(); 648 } 649 return nextExpiringAccountId; 650 } 651 652 /** 653 * For all accounts that require password expiration, put them in security hold and wipe 654 * their data. 655 * @param context 656 * @param controller 657 * @return true if one or more accounts were wiped 658 */ 659 /* package */ static boolean wipeExpiredAccounts(Context context, Controller controller) { 660 boolean result = false; 661 Cursor c = context.getContentResolver().query(Account.CONTENT_URI, 662 ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null); 663 try { 664 while (c.moveToNext()) { 665 long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS); 666 if (flags != 0) { 667 PolicySet p = new PolicySet(flags); 668 if (p.mPasswordExpirationDays > 0) { 669 long accountId = c.getLong(ACCOUNT_SECURITY_COLUMN_ID); 670 Account account = Account.restoreAccountWithId(context, accountId); 671 if (account != null) { 672 // Mark the account as "on hold". 673 setAccountHoldFlag(context, account, true); 674 // Erase data 675 controller.deleteSyncedDataSync(accountId); 676 // Report one or more were found 677 result = true; 678 } 679 } 680 } 681 } 682 } finally { 683 c.close(); 684 } 685 return result; 686 } 687 688 /** 689 * Callback from EmailBroadcastProcessorService. This provides the workers for the 690 * DeviceAdminReceiver calls. These should perform the work directly and not use async 691 * threads for completion. 692 */ 693 public static void onDeviceAdminReceiverMessage(Context context, int message) { 694 SecurityPolicy instance = SecurityPolicy.getInstance(context); 695 switch (message) { 696 case DEVICE_ADMIN_MESSAGE_ENABLED: 697 instance.onAdminEnabled(true); 698 break; 699 case DEVICE_ADMIN_MESSAGE_DISABLED: 700 instance.onAdminEnabled(false); 701 break; 702 case DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED: 703 // TODO make a small helper for this 704 // Clear security holds (if any) 705 Account.clearSecurityHoldOnAllAccounts(context); 706 // Cancel any active notifications (if any are posted) 707 NotificationController.getInstance(context).cancelPasswordExpirationNotifications(); 708 break; 709 case DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING: 710 instance.onPasswordExpiring(instance.mContext); 711 break; 712 } 713 } 714 715 /** 716 * Device Policy administrator. This is primarily a listener for device state changes. 717 * Note: This is instantiated by incoming messages. 718 * Note: This is actually a BroadcastReceiver and must remain within the guidelines required 719 * for proper behavior, including avoidance of ANRs. 720 * Note: We do not implement onPasswordFailed() because the default behavior of the 721 * DevicePolicyManager - complete local wipe after 'n' failures - is sufficient. 722 */ 723 public static class PolicyAdmin extends DeviceAdminReceiver { 724 725 /** 726 * Called after the administrator is first enabled. 727 */ 728 @Override 729 public void onEnabled(Context context, Intent intent) { 730 EmailBroadcastProcessorService.processDevicePolicyMessage(context, 731 DEVICE_ADMIN_MESSAGE_ENABLED); 732 } 733 734 /** 735 * Called prior to the administrator being disabled. 736 */ 737 @Override 738 public void onDisabled(Context context, Intent intent) { 739 EmailBroadcastProcessorService.processDevicePolicyMessage(context, 740 DEVICE_ADMIN_MESSAGE_DISABLED); 741 } 742 743 /** 744 * Called when the user asks to disable administration; we return a warning string that 745 * will be presented to the user 746 */ 747 @Override 748 public CharSequence onDisableRequested(Context context, Intent intent) { 749 return context.getString(R.string.disable_admin_warning); 750 } 751 752 /** 753 * Called after the user has changed their password. 754 */ 755 @Override 756 public void onPasswordChanged(Context context, Intent intent) { 757 EmailBroadcastProcessorService.processDevicePolicyMessage(context, 758 DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED); 759 } 760 761 /** 762 * Called when device password is expiring 763 */ 764 @Override 765 public void onPasswordExpiring(Context context, Intent intent) { 766 EmailBroadcastProcessorService.processDevicePolicyMessage(context, 767 DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING); 768 } 769 } 770} 771