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