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 17 package com.android.email; 18 19 import com.android.email.activity.setup.AccountSecurity; 20 import com.android.email.provider.EmailContent; 21 import com.android.email.provider.EmailContent.Account; 22 import com.android.email.provider.EmailContent.AccountColumns; 23 import com.android.email.service.MailService; 24 25 import android.app.Notification; 26 import android.app.NotificationManager; 27 import android.app.PendingIntent; 28 import android.app.admin.DeviceAdminReceiver; 29 import android.app.admin.DevicePolicyManager; 30 import android.content.ComponentName; 31 import android.content.ContentResolver; 32 import android.content.ContentUris; 33 import android.content.ContentValues; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.database.Cursor; 37 import android.media.AudioManager; 38 import android.net.Uri; 39 import 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 */ 45 public 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); 55 56 /** 57 * This projection on Account is for scanning/reading 58 */ 59 private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] { 60 AccountColumns.ID, AccountColumns.SECURITY_FLAGS 61 }; 62 private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1; 63 // Note, this handles the NULL case to deal with older accounts where the column was added 64 private static final String WHERE_ACCOUNT_SECURITY_NONZERO = 65 Account.SECURITY_FLAGS + " IS NOT NULL AND " + Account.SECURITY_FLAGS + "!=0"; 66 67 /** 68 * This projection on Account is for clearing the "security hold" column. Also includes 69 * the security flags column, so we can use it for selecting. 70 */ 71 private static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] { 72 AccountColumns.ID, AccountColumns.FLAGS, AccountColumns.SECURITY_FLAGS 73 }; 74 private static final int ACCOUNT_FLAGS_COLUMN_ID = 0; 75 private static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1; 76 77 /** 78 * Get the security policy instance 79 */ getInstance(Context context)80 public synchronized static SecurityPolicy getInstance(Context context) { 81 if (sInstance == null) { 82 sInstance = new SecurityPolicy(context); 83 } 84 return sInstance; 85 } 86 87 /** 88 * Private constructor (one time only) 89 */ SecurityPolicy(Context context)90 private SecurityPolicy(Context context) { 91 mContext = context; 92 mDPM = null; 93 mAdminName = new ComponentName(context, PolicyAdmin.class); 94 mAggregatePolicy = null; 95 } 96 97 /** 98 * For testing only: Inject context into already-created instance 99 */ setContext(Context context)100 /* package */ void setContext(Context context) { 101 mContext = context; 102 } 103 104 /** 105 * Compute the aggregate policy for all accounts that require it, and record it. 106 * 107 * The business logic is as follows: 108 * min password length take the max 109 * password mode take the max (strongest mode) 110 * max password fails take the min 111 * max screen lock time take the min 112 * require remote wipe take the max (logical or) 113 * 114 * @return a policy representing the strongest aggregate. If no policy sets are defined, 115 * a lightweight "nothing required" policy will be returned. Never null. 116 */ computeAggregatePolicy()117 /* package */ PolicySet computeAggregatePolicy() { 118 boolean policiesFound = false; 119 120 int minPasswordLength = Integer.MIN_VALUE; 121 int passwordMode = Integer.MIN_VALUE; 122 int maxPasswordFails = Integer.MAX_VALUE; 123 int maxScreenLockTime = Integer.MAX_VALUE; 124 boolean requireRemoteWipe = false; 125 126 Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI, 127 ACCOUNT_SECURITY_PROJECTION, WHERE_ACCOUNT_SECURITY_NONZERO, null, null); 128 try { 129 while (c.moveToNext()) { 130 int flags = c.getInt(ACCOUNT_SECURITY_COLUMN_FLAGS); 131 if (flags != 0) { 132 PolicySet p = new PolicySet(flags); 133 minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength); 134 passwordMode = Math.max(p.mPasswordMode, passwordMode); 135 if (p.mMaxPasswordFails > 0) { 136 maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails); 137 } 138 if (p.mMaxScreenLockTime > 0) { 139 maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime); 140 } 141 requireRemoteWipe |= p.mRequireRemoteWipe; 142 policiesFound = true; 143 } 144 } 145 } finally { 146 c.close(); 147 } 148 if (policiesFound) { 149 // final cleanup pass converts any untouched min/max values to zero (not specified) 150 if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0; 151 if (passwordMode == Integer.MIN_VALUE) passwordMode = 0; 152 if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0; 153 if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0; 154 155 return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails, 156 maxScreenLockTime, requireRemoteWipe); 157 } else { 158 return NO_POLICY_SET; 159 } 160 } 161 162 /** 163 * Return updated aggregate policy, from cached value if possible 164 */ getAggregatePolicy()165 public synchronized PolicySet getAggregatePolicy() { 166 if (mAggregatePolicy == null) { 167 mAggregatePolicy = computeAggregatePolicy(); 168 } 169 return mAggregatePolicy; 170 } 171 172 /** 173 * Get the dpm. This mainly allows us to make some utility calls without it, for testing. 174 */ getDPM()175 private synchronized DevicePolicyManager getDPM() { 176 if (mDPM == null) { 177 mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 178 } 179 return mDPM; 180 } 181 182 /** 183 * API: Report that policies may have been updated due to rewriting values in an Account. 184 * @param accountId the account that has been updated, -1 if unknown/deleted 185 */ updatePolicies(long accountId)186 public synchronized void updatePolicies(long accountId) { 187 mAggregatePolicy = null; 188 } 189 190 /** 191 * API: Report that policies may have been updated *and* the caller vouches that the 192 * change is a reduction in policies. This forces an immediate change to device state. 193 * Typically used when deleting accounts, although we may use it for server-side policy 194 * rollbacks. 195 */ reducePolicies()196 public void reducePolicies() { 197 updatePolicies(-1); 198 setActivePolicies(); 199 } 200 201 /** 202 * API: Query used to determine if a given policy is "active" (the device is operating at 203 * the required security level). 204 * 205 * This can be used when syncing a specific account, by passing a specific set of policies 206 * for that account. Or, it can be used at any time to compare the device 207 * state against the aggregate set of device policies stored in all accounts. 208 * 209 * This method is for queries only, and does not trigger any change in device state. 210 * 211 * @param policies the policies requested, or null to check aggregate stored policies 212 * @return true if the policies are active, false if not active 213 */ isActive(PolicySet policies)214 public boolean isActive(PolicySet policies) { 215 // select aggregate set if needed 216 if (policies == null) { 217 policies = getAggregatePolicy(); 218 } 219 // quick check for the "empty set" of no policies 220 if (policies == NO_POLICY_SET) { 221 return true; 222 } 223 DevicePolicyManager dpm = getDPM(); 224 if (dpm.isAdminActive(mAdminName)) { 225 // check each policy explicitly 226 if (policies.mMinPasswordLength > 0) { 227 if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) { 228 return false; 229 } 230 } 231 if (policies.mPasswordMode > 0) { 232 if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) { 233 return false; 234 } 235 if (!dpm.isActivePasswordSufficient()) { 236 return false; 237 } 238 } 239 if (policies.mMaxScreenLockTime > 0) { 240 // Note, we use seconds, dpm uses milliseconds 241 if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) { 242 return false; 243 } 244 } 245 // password failures are counted locally - no test required here 246 // no check required for remote wipe (it's supported, if we're the admin) 247 248 // making it this far means we passed! 249 return true; 250 } 251 // return false, not active 252 return false; 253 } 254 255 /** 256 * Set the requested security level based on the aggregate set of requests. 257 * If the set is empty, we release our device administration. If the set is non-empty, 258 * we only proceed if we are already active as an admin. 259 */ setActivePolicies()260 public void setActivePolicies() { 261 DevicePolicyManager dpm = getDPM(); 262 // compute aggregate set of policies 263 PolicySet policies = getAggregatePolicy(); 264 // if empty set, detach from policy manager 265 if (policies == NO_POLICY_SET) { 266 dpm.removeActiveAdmin(mAdminName); 267 } else if (dpm.isAdminActive(mAdminName)) { 268 // set each policy in the policy manager 269 // password mode & length 270 dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality()); 271 dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength); 272 // screen lock time 273 dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000); 274 // local wipe (failed passwords limit) 275 dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails); 276 } 277 } 278 279 /** 280 * API: Set/Clear the "hold" flag in any account. This flag serves a dual purpose: 281 * Setting it gives us an indication that it was blocked, and clearing it gives EAS a 282 * signal to try syncing again. 283 */ setAccountHoldFlag(Account account, boolean newState)284 public void setAccountHoldFlag(Account account, boolean newState) { 285 if (newState) { 286 account.mFlags |= Account.FLAGS_SECURITY_HOLD; 287 } else { 288 account.mFlags &= ~Account.FLAGS_SECURITY_HOLD; 289 } 290 ContentValues cv = new ContentValues(); 291 cv.put(AccountColumns.FLAGS, account.mFlags); 292 account.update(mContext, cv); 293 } 294 295 /** 296 * Clear all account hold flags that are set. This will trigger watchers, and in particular 297 * will cause EAS to try and resync the account(s). 298 */ clearAccountHoldFlags()299 public void clearAccountHoldFlags() { 300 ContentResolver resolver = mContext.getContentResolver(); 301 Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION, 302 WHERE_ACCOUNT_SECURITY_NONZERO, null, null); 303 try { 304 while (c.moveToNext()) { 305 int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS); 306 if (0 != (flags & Account.FLAGS_SECURITY_HOLD)) { 307 ContentValues cv = new ContentValues(); 308 cv.put(AccountColumns.FLAGS, flags & ~Account.FLAGS_SECURITY_HOLD); 309 long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID); 310 Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId); 311 resolver.update(uri, cv, null, null); 312 } 313 } 314 } finally { 315 c.close(); 316 } 317 } 318 319 /** 320 * API: Sync service should call this any time a sync fails due to isActive() returning false. 321 * This will kick off the notify-acquire-admin-state process and/or increase the security level. 322 * The caller needs to write the required policies into this account before making this call. 323 * Should not be called from UI thread - uses DB lookups to prepare new notifications 324 * 325 * @param accountId the account for which sync cannot proceed 326 */ policiesRequired(long accountId)327 public void policiesRequired(long accountId) { 328 Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId); 329 330 // Mark the account as "on hold". 331 setAccountHoldFlag(account, true); 332 333 // 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 */ clearNotification(long accountId)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 */ remoteWipe()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 { 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 419 /*package*/ final int mMinPasswordLength; 420 /*package*/ final int mPasswordMode; 421 /*package*/ final int mMaxPasswordFails; 422 /*package*/ final int mMaxScreenLockTime; 423 /*package*/ final boolean mRequireRemoteWipe; 424 getMinPasswordLengthForTest()425 public int getMinPasswordLengthForTest() { 426 return mMinPasswordLength; 427 } 428 getPasswordModeForTest()429 public int getPasswordModeForTest() { 430 return mPasswordMode; 431 } 432 getMaxPasswordFailsForTest()433 public int getMaxPasswordFailsForTest() { 434 return mMaxPasswordFails; 435 } 436 getMaxScreenLockTimeForTest()437 public int getMaxScreenLockTimeForTest() { 438 return mMaxScreenLockTime; 439 } 440 isRequireRemoteWipeForTest()441 public boolean isRequireRemoteWipeForTest() { 442 return mRequireRemoteWipe; 443 } 444 445 /** 446 * Create from raw values. 447 * @param minPasswordLength (0=not enforced) 448 * @param passwordMode 449 * @param maxPasswordFails (0=not enforced) 450 * @param maxScreenLockTime in seconds (0=not enforced) 451 * @param requireRemoteWipe 452 * @throws IllegalArgumentException for illegal arguments. 453 */ PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails, int maxScreenLockTime, boolean requireRemoteWipe)454 public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails, 455 int maxScreenLockTime, boolean requireRemoteWipe) throws IllegalArgumentException { 456 // If we're not enforcing passwords, make sure we clean up related values, since EAS 457 // can send non-zero values for any or all of these 458 if (passwordMode == PASSWORD_MODE_NONE) { 459 maxPasswordFails = 0; 460 maxScreenLockTime = 0; 461 minPasswordLength = 0; 462 } else { 463 if ((passwordMode != PASSWORD_MODE_SIMPLE) && 464 (passwordMode != PASSWORD_MODE_STRONG)) { 465 throw new IllegalArgumentException("password mode"); 466 } 467 // The next value has a hard limit which cannot be supported if exceeded. 468 if (minPasswordLength > PASSWORD_LENGTH_MAX) { 469 throw new IllegalArgumentException("password length"); 470 } 471 // This value can be reduced (which actually increases security) if necessary 472 if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) { 473 maxPasswordFails = PASSWORD_MAX_FAILS_MAX; 474 } 475 // This value can be reduced (which actually increases security) if necessary 476 if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) { 477 maxScreenLockTime = SCREEN_LOCK_TIME_MAX; 478 } 479 } 480 mMinPasswordLength = minPasswordLength; 481 mPasswordMode = passwordMode; 482 mMaxPasswordFails = maxPasswordFails; 483 mMaxScreenLockTime = maxScreenLockTime; 484 mRequireRemoteWipe = requireRemoteWipe; 485 } 486 487 /** 488 * Create from values encoded in an account 489 * @param account 490 */ PolicySet(Account account)491 public PolicySet(Account account) { 492 this(account.mSecurityFlags); 493 } 494 495 /** 496 * Create from values encoded in an account flags int 497 */ PolicySet(int flags)498 public PolicySet(int flags) { 499 mMinPasswordLength = 500 (flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT; 501 mPasswordMode = 502 (flags & PASSWORD_MODE_MASK); 503 mMaxPasswordFails = 504 (flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT; 505 mMaxScreenLockTime = 506 (flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT; 507 mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE); 508 } 509 510 /** 511 * Helper to map our internal encoding to DevicePolicyManager password modes. 512 */ getDPManagerPasswordQuality()513 public int getDPManagerPasswordQuality() { 514 switch (mPasswordMode) { 515 case PASSWORD_MODE_SIMPLE: 516 return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; 517 case PASSWORD_MODE_STRONG: 518 return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; 519 default: 520 return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED; 521 } 522 } 523 524 /** 525 * Record flags (and a sync key for the flags) into an Account 526 * Note: the hash code is defined as the encoding used in Account 527 * 528 * @param account to write the values mSecurityFlags and mSecuritySyncKey 529 * @param syncKey the value to write into the account's mSecuritySyncKey 530 * @param update if true, also writes the account back to the provider (updating only 531 * the fields changed by this API) 532 * @param context a context for writing to the provider 533 * @return true if the actual policies changed, false if no change (note, sync key 534 * does not affect this) 535 */ writeAccount(Account account, String syncKey, boolean update, Context context)536 public boolean writeAccount(Account account, String syncKey, boolean update, 537 Context context) { 538 int newFlags = hashCode(); 539 boolean dirty = (newFlags != account.mSecurityFlags); 540 account.mSecurityFlags = newFlags; 541 account.mSecuritySyncKey = syncKey; 542 if (update) { 543 if (account.isSaved()) { 544 ContentValues cv = new ContentValues(); 545 cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags); 546 cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey); 547 account.update(context, cv); 548 } else { 549 account.save(context); 550 } 551 } 552 return dirty; 553 } 554 555 @Override equals(Object o)556 public boolean equals(Object o) { 557 if (o instanceof PolicySet) { 558 PolicySet other = (PolicySet)o; 559 return (this.mMinPasswordLength == other.mMinPasswordLength) 560 && (this.mPasswordMode == other.mPasswordMode) 561 && (this.mMaxPasswordFails == other.mMaxPasswordFails) 562 && (this.mMaxScreenLockTime == other.mMaxScreenLockTime) 563 && (this.mRequireRemoteWipe == other.mRequireRemoteWipe); 564 } 565 return false; 566 } 567 568 /** 569 * Note: the hash code is defined as the encoding used in Account 570 */ 571 @Override hashCode()572 public int hashCode() { 573 int flags = 0; 574 flags = mMinPasswordLength << PASSWORD_LENGTH_SHIFT; 575 flags |= mPasswordMode; 576 flags |= mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT; 577 flags |= mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT; 578 if (mRequireRemoteWipe) { 579 flags |= REQUIRE_REMOTE_WIPE; 580 } 581 return flags; 582 } 583 584 @Override toString()585 public String toString() { 586 return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode 587 + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max=" 588 + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe + "}"; 589 } 590 } 591 592 /** 593 * If we are not the active device admin, try to become so. 594 * 595 * @return true if we are already active, false if we are not 596 */ isActiveAdmin()597 public boolean isActiveAdmin() { 598 DevicePolicyManager dpm = getDPM(); 599 return dpm.isAdminActive(mAdminName); 600 } 601 602 /** 603 * Report admin component name - for making calls into device policy manager 604 */ getAdminComponent()605 public ComponentName getAdminComponent() { 606 return mAdminName; 607 } 608 609 /** 610 * Internal handler for enabled->disabled transitions. Resets all security keys 611 * forcing EAS to resync security state. 612 */ onAdminEnabled(boolean isEnabled)613 /* package */ void onAdminEnabled(boolean isEnabled) { 614 if (!isEnabled) { 615 // transition to disabled state 616 // Response: clear *all* security state information from the accounts, forcing 617 // them back to the initial configurations requiring policy administration 618 ContentValues cv = new ContentValues(); 619 cv.put(AccountColumns.SECURITY_FLAGS, 0); 620 cv.putNull(AccountColumns.SECURITY_SYNC_KEY); 621 mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null); 622 updatePolicies(-1); 623 } 624 } 625 626 /** 627 * Device Policy administrator. This is primarily a listener for device state changes. 628 * Note: This is instantiated by incoming messages. 629 * Note: We do not implement onPasswordFailed() because the default behavior of the 630 * DevicePolicyManager - complete local wipe after 'n' failures - is sufficient. 631 */ 632 public static class PolicyAdmin extends DeviceAdminReceiver { 633 634 /** 635 * Called after the administrator is first enabled. 636 */ 637 @Override onEnabled(Context context, Intent intent)638 public void onEnabled(Context context, Intent intent) { 639 SecurityPolicy.getInstance(context).onAdminEnabled(true); 640 } 641 642 /** 643 * Called prior to the administrator being disabled. 644 */ 645 @Override onDisabled(Context context, Intent intent)646 public void onDisabled(Context context, Intent intent) { 647 SecurityPolicy.getInstance(context).onAdminEnabled(false); 648 } 649 650 /** 651 * Called after the user has changed their password. 652 */ 653 @Override onPasswordChanged(Context context, Intent intent)654 public void onPasswordChanged(Context context, Intent intent) { 655 SecurityPolicy.getInstance(context).clearAccountHoldFlags(); 656 } 657 } 658 } 659