SecurityPolicy.java revision 61911d4ff70132fa21c5ee7a987303479e8ef6ae
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(MailService.NOTIFICATION_ID_SECURITY_NEEDED, notification); 367 } 368 369 /** 370 * Called from the notification's intent receiver to register that the notification can be 371 * cleared now. 372 */ 373 public void clearNotification(long accountId) { 374 NotificationManager notificationManager = 375 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 376 notificationManager.cancel(MailService.NOTIFICATION_ID_SECURITY_NEEDED); 377 } 378 379 /** 380 * API: Remote wipe (from server). This is final, there is no confirmation. It will only 381 * return to the caller if there is an unexpected failure. 382 */ 383 public void remoteWipe() { 384 DevicePolicyManager dpm = getDPM(); 385 if (dpm.isAdminActive(mAdminName)) { 386 dpm.wipeData(0); 387 } else { 388 Log.d(Email.LOG_TAG, "Could not remote wipe because not device admin."); 389 } 390 } 391 392 /** 393 * Class for tracking policies and reading/writing into accounts 394 */ 395 public static class PolicySet implements Parcelable { 396 397 // Security (provisioning) flags 398 // bits 0..4: password length (0=no password required) 399 private static final int PASSWORD_LENGTH_MASK = 31; 400 private static final int PASSWORD_LENGTH_SHIFT = 0; 401 public static final int PASSWORD_LENGTH_MAX = 30; 402 // bits 5..8: password mode 403 private static final int PASSWORD_MODE_SHIFT = 5; 404 private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT; 405 public static final int PASSWORD_MODE_NONE = 0 << PASSWORD_MODE_SHIFT; 406 public static final int PASSWORD_MODE_SIMPLE = 1 << PASSWORD_MODE_SHIFT; 407 public static final int PASSWORD_MODE_STRONG = 2 << PASSWORD_MODE_SHIFT; 408 // bits 9..13: password failures -> wipe device (0=disabled) 409 private static final int PASSWORD_MAX_FAILS_SHIFT = 9; 410 private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT; 411 public static final int PASSWORD_MAX_FAILS_MAX = 31; 412 // bits 14..24: seconds to screen lock (0=not required) 413 private static final int SCREEN_LOCK_TIME_SHIFT = 14; 414 private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT; 415 public static final int SCREEN_LOCK_TIME_MAX = 2047; 416 // bit 25: remote wipe capability required 417 private static final int REQUIRE_REMOTE_WIPE = 1 << 25; 418 // bit 26..35: password expiration (days; 0=not required) 419 private static final int PASSWORD_EXPIRATION_SHIFT = 26; 420 private static final long PASSWORD_EXPIRATION_MASK = 1023L << PASSWORD_EXPIRATION_SHIFT; 421 public static final int PASSWORD_EXPIRATION_MAX = 1023; 422 // bit 35..42: password history (length; 0=not required) 423 private static final int PASSWORD_HISTORY_SHIFT = 36; 424 private static final long PASSWORD_HISTORY_MASK = 255L << PASSWORD_HISTORY_SHIFT; 425 public static final int PASSWORD_HISTORY_MAX = 255; 426 // bit 42..46: min complex characters (0=not required) 427 private static final int PASSWORD_COMPLEX_CHARS_SHIFT = 44; 428 private static final long PASSWORD_COMPLEX_CHARS_MASK = 31L << PASSWORD_COMPLEX_CHARS_SHIFT; 429 public static final int PASSWORD_COMPLEX_CHARS_MAX = 31; 430 431 /*package*/ final int mMinPasswordLength; 432 /*package*/ final int mPasswordMode; 433 /*package*/ final int mMaxPasswordFails; 434 /*package*/ final int mMaxScreenLockTime; 435 /*package*/ final boolean mRequireRemoteWipe; 436 /*package*/ final int mPasswordExpiration; 437 /*package*/ final int mPasswordHistory; 438 /*package*/ final int mPasswordComplexChars; 439 440 public int getMinPasswordLength() { 441 return mMinPasswordLength; 442 } 443 444 public int getPasswordMode() { 445 return mPasswordMode; 446 } 447 448 public int getMaxPasswordFails() { 449 return mMaxPasswordFails; 450 } 451 452 public int getMaxScreenLockTime() { 453 return mMaxScreenLockTime; 454 } 455 456 public boolean isRequireRemoteWipe() { 457 return mRequireRemoteWipe; 458 } 459 460 /** 461 * Create from raw values. 462 * @param minPasswordLength (0=not enforced) 463 * @param passwordMode 464 * @param maxPasswordFails (0=not enforced) 465 * @param maxScreenLockTime in seconds (0=not enforced) 466 * @param requireRemoteWipe 467 * @throws IllegalArgumentException for illegal arguments. 468 */ 469 public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails, 470 int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpiration, 471 int passwordHistory, int passwordComplexChars) throws IllegalArgumentException { 472 // If we're not enforcing passwords, make sure we clean up related values, since EAS 473 // can send non-zero values for any or all of these 474 if (passwordMode == PASSWORD_MODE_NONE) { 475 maxPasswordFails = 0; 476 maxScreenLockTime = 0; 477 minPasswordLength = 0; 478 passwordComplexChars = 0; 479 passwordHistory = 0; 480 passwordExpiration = 0; 481 } else { 482 if ((passwordMode != PASSWORD_MODE_SIMPLE) && 483 (passwordMode != PASSWORD_MODE_STRONG)) { 484 throw new IllegalArgumentException("password mode"); 485 } 486 // If we're only requiring a simple password, set complex chars to zero; note 487 // that EAS can erroneously send non-zero values in this case 488 if (passwordMode == PASSWORD_MODE_SIMPLE) { 489 passwordComplexChars = 0; 490 } 491 // The next four values have hard limits which cannot be supported if exceeded. 492 if (minPasswordLength > PASSWORD_LENGTH_MAX) { 493 throw new IllegalArgumentException("password length"); 494 } 495 if (passwordExpiration > PASSWORD_EXPIRATION_MAX) { 496 throw new IllegalArgumentException("password expiration"); 497 } 498 if (passwordHistory > PASSWORD_HISTORY_MAX) { 499 throw new IllegalArgumentException("password history"); 500 } 501 if (passwordComplexChars > PASSWORD_COMPLEX_CHARS_MAX) { 502 throw new IllegalArgumentException("complex chars"); 503 } 504 // This value can be reduced (which actually increases security) if necessary 505 if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) { 506 maxPasswordFails = PASSWORD_MAX_FAILS_MAX; 507 } 508 // This value can be reduced (which actually increases security) if necessary 509 if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) { 510 maxScreenLockTime = SCREEN_LOCK_TIME_MAX; 511 } 512 } 513 mMinPasswordLength = minPasswordLength; 514 mPasswordMode = passwordMode; 515 mMaxPasswordFails = maxPasswordFails; 516 mMaxScreenLockTime = maxScreenLockTime; 517 mRequireRemoteWipe = requireRemoteWipe; 518 mPasswordExpiration = passwordExpiration; 519 mPasswordHistory = passwordHistory; 520 mPasswordComplexChars = passwordComplexChars; 521 } 522 523 /** 524 * Create from values encoded in an account 525 * @param account 526 */ 527 public PolicySet(Account account) { 528 this(account.mSecurityFlags); 529 } 530 531 /** 532 * Create from values encoded in an account flags int 533 */ 534 public PolicySet(long flags) { 535 mMinPasswordLength = 536 (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT); 537 mPasswordMode = 538 (int) (flags & PASSWORD_MODE_MASK); 539 mMaxPasswordFails = 540 (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT); 541 mMaxScreenLockTime = 542 (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT); 543 mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE); 544 mPasswordExpiration = 545 (int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT); 546 mPasswordHistory = 547 (int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT); 548 mPasswordComplexChars = 549 (int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT); 550 } 551 552 /** 553 * Helper to map our internal encoding to DevicePolicyManager password modes. 554 */ 555 public int getDPManagerPasswordQuality() { 556 switch (mPasswordMode) { 557 case PASSWORD_MODE_SIMPLE: 558 return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 559 case PASSWORD_MODE_STRONG: 560 return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 561 default: 562 return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED; 563 } 564 } 565 566 /** 567 * Record flags (and a sync key for the flags) into an Account 568 * Note: the hash code is defined as the encoding used in Account 569 * 570 * @param account to write the values mSecurityFlags and mSecuritySyncKey 571 * @param syncKey the value to write into the account's mSecuritySyncKey 572 * @param update if true, also writes the account back to the provider (updating only 573 * the fields changed by this API) 574 * @param context a context for writing to the provider 575 * @return true if the actual policies changed, false if no change (note, sync key 576 * does not affect this) 577 */ 578 public boolean writeAccount(Account account, String syncKey, boolean update, 579 Context context) { 580 long newFlags = getSecurityCode(); 581 boolean dirty = (newFlags != account.mSecurityFlags); 582 account.mSecurityFlags = newFlags; 583 account.mSecuritySyncKey = syncKey; 584 if (update) { 585 if (account.isSaved()) { 586 ContentValues cv = new ContentValues(); 587 cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags); 588 cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey); 589 account.update(context, cv); 590 } else { 591 account.save(context); 592 } 593 } 594 return dirty; 595 } 596 597 @Override 598 public boolean equals(Object o) { 599 if (o instanceof PolicySet) { 600 PolicySet other = (PolicySet)o; 601 return (this.getSecurityCode() == other.getSecurityCode()); 602 } 603 return false; 604 } 605 606 /** 607 * Supports Parcelable 608 */ 609 public int describeContents() { 610 return 0; 611 } 612 613 /** 614 * Supports Parcelable 615 */ 616 public static final Parcelable.Creator<PolicySet> CREATOR 617 = new Parcelable.Creator<PolicySet>() { 618 public PolicySet createFromParcel(Parcel in) { 619 return new PolicySet(in); 620 } 621 622 public PolicySet[] newArray(int size) { 623 return new PolicySet[size]; 624 } 625 }; 626 627 /** 628 * Supports Parcelable 629 */ 630 public void writeToParcel(Parcel dest, int flags) { 631 dest.writeInt(mMinPasswordLength); 632 dest.writeInt(mPasswordMode); 633 dest.writeInt(mMaxPasswordFails); 634 dest.writeInt(mMaxScreenLockTime); 635 dest.writeInt(mRequireRemoteWipe ? 1 : 0); 636 dest.writeInt(mPasswordExpiration); 637 dest.writeInt(mPasswordHistory); 638 dest.writeInt(mPasswordComplexChars); 639 } 640 641 /** 642 * Supports Parcelable 643 */ 644 public PolicySet(Parcel in) { 645 mMinPasswordLength = in.readInt(); 646 mPasswordMode = in.readInt(); 647 mMaxPasswordFails = in.readInt(); 648 mMaxScreenLockTime = in.readInt(); 649 mRequireRemoteWipe = in.readInt() == 1; 650 mPasswordExpiration = in.readInt(); 651 mPasswordHistory = in.readInt(); 652 mPasswordComplexChars = in.readInt(); 653 } 654 655 @Override 656 public int hashCode() { 657 long code = getSecurityCode(); 658 return (int) code; 659 } 660 661 public long getSecurityCode() { 662 long flags = 0; 663 flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT; 664 flags |= mPasswordMode; 665 flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT; 666 flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT; 667 if (mRequireRemoteWipe) { 668 flags |= REQUIRE_REMOTE_WIPE; 669 } 670 flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT; 671 flags |= (long)mPasswordExpiration << PASSWORD_EXPIRATION_SHIFT; 672 flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT; 673 return flags; 674 } 675 676 @Override 677 public String toString() { 678 return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode 679 + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max=" 680 + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe 681 + " pw-expiration=" + mPasswordExpiration 682 + " pw-history=" + mPasswordHistory 683 + " pw-complex-chars=" + mPasswordComplexChars + "}"; 684 } 685 } 686 687 /** 688 * If we are not the active device admin, try to become so. 689 * 690 * @return true if we are already active, false if we are not 691 */ 692 public boolean isActiveAdmin() { 693 DevicePolicyManager dpm = getDPM(); 694 return dpm.isAdminActive(mAdminName); 695 } 696 697 /** 698 * Report admin component name - for making calls into device policy manager 699 */ 700 public ComponentName getAdminComponent() { 701 return mAdminName; 702 } 703 704 /** 705 * Internal handler for enabled->disabled transitions. Resets all security keys 706 * forcing EAS to resync security state. 707 */ 708 /* package */ void onAdminEnabled(boolean isEnabled) { 709 if (!isEnabled) { 710 // transition to disabled state 711 // Response: clear *all* security state information from the accounts, forcing 712 // them back to the initial configurations requiring policy administration 713 ContentValues cv = new ContentValues(); 714 cv.put(AccountColumns.SECURITY_FLAGS, 0); 715 cv.putNull(AccountColumns.SECURITY_SYNC_KEY); 716 mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null); 717 updatePolicies(-1); 718 } 719 } 720 721 /** 722 * Device Policy administrator. This is primarily a listener for device state changes. 723 * Note: This is instantiated by incoming messages. 724 * Note: We do not implement onPasswordFailed() because the default behavior of the 725 * DevicePolicyManager - complete local wipe after 'n' failures - is sufficient. 726 */ 727 public static class PolicyAdmin extends DeviceAdminReceiver { 728 729 /** 730 * Called after the administrator is first enabled. 731 */ 732 @Override 733 public void onEnabled(Context context, Intent intent) { 734 SecurityPolicy.getInstance(context).onAdminEnabled(true); 735 } 736 737 /** 738 * Called prior to the administrator being disabled. 739 */ 740 @Override 741 public void onDisabled(Context context, Intent intent) { 742 SecurityPolicy.getInstance(context).onAdminEnabled(false); 743 } 744 745 /** 746 * Called after the user has changed their password. 747 */ 748 @Override 749 public void onPasswordChanged(Context context, Intent intent) { 750 Account.clearSecurityHoldOnAllAccounts(context); 751 } 752 } 753} 754