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