2010-01-27 01:24:15 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2010 The Android Open Source Project
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package com.android.email;
|
|
|
|
|
2010-02-05 19:10:39 +00:00
|
|
|
import com.android.email.activity.setup.AccountSecurity;
|
|
|
|
import com.android.email.provider.EmailContent;
|
2010-01-27 01:24:15 +00:00
|
|
|
import com.android.email.provider.EmailContent.Account;
|
2010-02-05 19:10:39 +00:00
|
|
|
import com.android.email.provider.EmailContent.AccountColumns;
|
2010-12-30 08:16:55 +00:00
|
|
|
import com.android.email.service.EmailBroadcastProcessorService;
|
2010-01-27 01:24:15 +00:00
|
|
|
|
2010-12-09 00:06:16 +00:00
|
|
|
import android.app.admin.DeviceAdminInfo;
|
2010-02-27 01:26:45 +00:00
|
|
|
import android.app.admin.DeviceAdminReceiver;
|
|
|
|
import android.app.admin.DevicePolicyManager;
|
2010-02-02 00:48:16 +00:00
|
|
|
import android.content.ComponentName;
|
2010-10-25 18:49:29 +00:00
|
|
|
import android.content.ContentResolver;
|
2010-02-05 19:10:39 +00:00
|
|
|
import android.content.ContentValues;
|
2010-01-27 01:24:15 +00:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.database.Cursor;
|
2010-06-02 20:25:03 +00:00
|
|
|
import android.os.Parcel;
|
|
|
|
import android.os.Parcelable;
|
2010-02-09 19:01:01 +00:00
|
|
|
import android.util.Log;
|
2010-01-27 01:24:15 +00:00
|
|
|
|
|
|
|
/**
|
2010-02-10 01:24:55 +00:00
|
|
|
* Utility functions to support reading and writing security policies, and handshaking the device
|
|
|
|
* into and out of various security states.
|
2010-01-27 01:24:15 +00:00
|
|
|
*/
|
|
|
|
public class SecurityPolicy {
|
2010-10-25 18:49:29 +00:00
|
|
|
private static final String TAG = "SecurityPolicy";
|
2010-01-27 01:24:15 +00:00
|
|
|
private static SecurityPolicy sInstance = null;
|
|
|
|
private Context mContext;
|
2010-02-02 00:48:16 +00:00
|
|
|
private DevicePolicyManager mDPM;
|
|
|
|
private ComponentName mAdminName;
|
|
|
|
private PolicySet mAggregatePolicy;
|
|
|
|
|
2010-02-16 22:33:08 +00:00
|
|
|
/* package */ static final PolicySet NO_POLICY_SET =
|
2011-01-13 21:12:55 +00:00
|
|
|
new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0, false);
|
2010-01-27 01:24:15 +00:00
|
|
|
|
|
|
|
/**
|
2010-05-20 23:11:26 +00:00
|
|
|
* This projection on Account is for scanning/reading
|
2010-01-27 01:24:15 +00:00
|
|
|
*/
|
|
|
|
private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] {
|
2010-02-09 01:42:42 +00:00
|
|
|
AccountColumns.ID, AccountColumns.SECURITY_FLAGS
|
2010-01-27 01:24:15 +00:00
|
|
|
};
|
2010-12-01 20:58:36 +00:00
|
|
|
private static final int ACCOUNT_SECURITY_COLUMN_ID = 0;
|
2010-01-27 01:24:15 +00:00
|
|
|
private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1;
|
2010-02-09 01:42:42 +00:00
|
|
|
|
2010-12-30 08:16:55 +00:00
|
|
|
// Messages used for DevicePolicyManager callbacks
|
|
|
|
private static final int DEVICE_ADMIN_MESSAGE_ENABLED = 1;
|
|
|
|
private static final int DEVICE_ADMIN_MESSAGE_DISABLED = 2;
|
|
|
|
private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED = 3;
|
|
|
|
private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING = 4;
|
|
|
|
|
2010-01-27 01:24:15 +00:00
|
|
|
/**
|
|
|
|
* Get the security policy instance
|
|
|
|
*/
|
|
|
|
public synchronized static SecurityPolicy getInstance(Context context) {
|
|
|
|
if (sInstance == null) {
|
2010-12-30 08:16:55 +00:00
|
|
|
sInstance = new SecurityPolicy(context.getApplicationContext());
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
return sInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Private constructor (one time only)
|
|
|
|
*/
|
|
|
|
private SecurityPolicy(Context context) {
|
2010-05-20 23:11:26 +00:00
|
|
|
mContext = context.getApplicationContext();
|
2010-02-02 00:48:16 +00:00
|
|
|
mDPM = null;
|
|
|
|
mAdminName = new ComponentName(context, PolicyAdmin.class);
|
|
|
|
mAggregatePolicy = null;
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For testing only: Inject context into already-created instance
|
|
|
|
*/
|
|
|
|
/* package */ void setContext(Context context) {
|
|
|
|
mContext = context;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compute the aggregate policy for all accounts that require it, and record it.
|
|
|
|
*
|
|
|
|
* The business logic is as follows:
|
|
|
|
* min password length take the max
|
|
|
|
* password mode take the max (strongest mode)
|
|
|
|
* max password fails take the min
|
|
|
|
* max screen lock time take the min
|
|
|
|
* require remote wipe take the max (logical or)
|
2010-06-09 23:18:57 +00:00
|
|
|
* password history take the max (strongest mode)
|
2010-12-01 20:58:36 +00:00
|
|
|
* password expiration take the min (strongest mode)
|
2010-06-09 23:18:57 +00:00
|
|
|
* password complex chars take the max (strongest mode)
|
2011-01-13 21:12:55 +00:00
|
|
|
* encryption take the max (logical or)
|
2010-05-20 23:11:26 +00:00
|
|
|
*
|
2010-02-02 00:48:16 +00:00
|
|
|
* @return a policy representing the strongest aggregate. If no policy sets are defined,
|
|
|
|
* a lightweight "nothing required" policy will be returned. Never null.
|
2010-01-27 01:24:15 +00:00
|
|
|
*/
|
2010-06-09 23:18:57 +00:00
|
|
|
/*package*/ PolicySet computeAggregatePolicy() {
|
2010-01-27 01:24:15 +00:00
|
|
|
boolean policiesFound = false;
|
|
|
|
|
|
|
|
int minPasswordLength = Integer.MIN_VALUE;
|
|
|
|
int passwordMode = Integer.MIN_VALUE;
|
|
|
|
int maxPasswordFails = Integer.MAX_VALUE;
|
|
|
|
int maxScreenLockTime = Integer.MAX_VALUE;
|
|
|
|
boolean requireRemoteWipe = false;
|
2010-06-09 23:18:57 +00:00
|
|
|
int passwordHistory = Integer.MIN_VALUE;
|
2010-12-01 20:58:36 +00:00
|
|
|
int passwordExpirationDays = Integer.MAX_VALUE;
|
2010-06-09 23:18:57 +00:00
|
|
|
int passwordComplexChars = Integer.MIN_VALUE;
|
2011-01-13 21:12:55 +00:00
|
|
|
boolean requireEncryption = false;
|
2010-01-27 01:24:15 +00:00
|
|
|
|
|
|
|
Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
|
2010-07-27 19:52:46 +00:00
|
|
|
ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null);
|
2010-01-27 01:24:15 +00:00
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
2010-06-09 23:18:57 +00:00
|
|
|
long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS);
|
2010-01-27 01:24:15 +00:00
|
|
|
if (flags != 0) {
|
|
|
|
PolicySet p = new PolicySet(flags);
|
2010-02-05 19:10:39 +00:00
|
|
|
minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
|
|
|
|
passwordMode = Math.max(p.mPasswordMode, passwordMode);
|
2010-01-27 01:24:15 +00:00
|
|
|
if (p.mMaxPasswordFails > 0) {
|
|
|
|
maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
|
|
|
|
}
|
|
|
|
if (p.mMaxScreenLockTime > 0) {
|
|
|
|
maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime);
|
|
|
|
}
|
2010-06-09 23:18:57 +00:00
|
|
|
if (p.mPasswordHistory > 0) {
|
|
|
|
passwordHistory = Math.max(p.mPasswordHistory, passwordHistory);
|
|
|
|
}
|
2010-12-01 20:58:36 +00:00
|
|
|
if (p.mPasswordExpirationDays > 0) {
|
|
|
|
passwordExpirationDays =
|
|
|
|
Math.min(p.mPasswordExpirationDays, passwordExpirationDays);
|
2010-06-09 23:18:57 +00:00
|
|
|
}
|
|
|
|
if (p.mPasswordComplexChars > 0) {
|
|
|
|
passwordComplexChars = Math.max(p.mPasswordComplexChars,
|
|
|
|
passwordComplexChars);
|
|
|
|
}
|
2010-01-27 01:24:15 +00:00
|
|
|
requireRemoteWipe |= p.mRequireRemoteWipe;
|
2011-01-13 21:12:55 +00:00
|
|
|
requireEncryption |= p.mRequireEncryption;
|
2010-01-27 01:24:15 +00:00
|
|
|
policiesFound = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
if (policiesFound) {
|
2010-02-05 19:10:39 +00:00
|
|
|
// final cleanup pass converts any untouched min/max values to zero (not specified)
|
|
|
|
if (minPasswordLength == Integer.MIN_VALUE) minPasswordLength = 0;
|
|
|
|
if (passwordMode == Integer.MIN_VALUE) passwordMode = 0;
|
|
|
|
if (maxPasswordFails == Integer.MAX_VALUE) maxPasswordFails = 0;
|
|
|
|
if (maxScreenLockTime == Integer.MAX_VALUE) maxScreenLockTime = 0;
|
2010-06-09 23:18:57 +00:00
|
|
|
if (passwordHistory == Integer.MIN_VALUE) passwordHistory = 0;
|
2010-12-01 20:58:36 +00:00
|
|
|
if (passwordExpirationDays == Integer.MAX_VALUE) passwordExpirationDays = 0;
|
2010-06-09 23:18:57 +00:00
|
|
|
if (passwordComplexChars == Integer.MIN_VALUE) passwordComplexChars = 0;
|
2010-02-05 19:10:39 +00:00
|
|
|
|
2010-01-27 01:24:15 +00:00
|
|
|
return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
|
2010-12-01 20:58:36 +00:00
|
|
|
maxScreenLockTime, requireRemoteWipe, passwordExpirationDays, passwordHistory,
|
2011-01-13 21:12:55 +00:00
|
|
|
passwordComplexChars, requireEncryption);
|
2010-01-27 01:24:15 +00:00
|
|
|
} else {
|
2010-02-02 00:48:16 +00:00
|
|
|
return NO_POLICY_SET;
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-09 19:01:01 +00:00
|
|
|
/**
|
|
|
|
* Return updated aggregate policy, from cached value if possible
|
|
|
|
*/
|
|
|
|
public synchronized PolicySet getAggregatePolicy() {
|
|
|
|
if (mAggregatePolicy == null) {
|
|
|
|
mAggregatePolicy = computeAggregatePolicy();
|
|
|
|
}
|
|
|
|
return mAggregatePolicy;
|
|
|
|
}
|
|
|
|
|
2010-01-27 01:24:15 +00:00
|
|
|
/**
|
2010-02-02 00:48:16 +00:00
|
|
|
* Get the dpm. This mainly allows us to make some utility calls without it, for testing.
|
|
|
|
*/
|
2011-01-19 19:40:48 +00:00
|
|
|
/* package */ synchronized DevicePolicyManager getDPM() {
|
2010-02-02 00:48:16 +00:00
|
|
|
if (mDPM == null) {
|
|
|
|
mDPM = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
|
|
|
}
|
|
|
|
return mDPM;
|
|
|
|
}
|
|
|
|
|
2010-01-27 01:24:15 +00:00
|
|
|
/**
|
2010-02-02 00:48:16 +00:00
|
|
|
* API: Report that policies may have been updated due to rewriting values in an Account.
|
2010-02-09 19:01:01 +00:00
|
|
|
* @param accountId the account that has been updated, -1 if unknown/deleted
|
2010-02-02 00:48:16 +00:00
|
|
|
*/
|
2010-02-05 19:10:39 +00:00
|
|
|
public synchronized void updatePolicies(long accountId) {
|
2010-02-02 00:48:16 +00:00
|
|
|
mAggregatePolicy = null;
|
|
|
|
}
|
|
|
|
|
2010-02-09 19:01:01 +00:00
|
|
|
/**
|
|
|
|
* API: Report that policies may have been updated *and* the caller vouches that the
|
|
|
|
* change is a reduction in policies. This forces an immediate change to device state.
|
|
|
|
* Typically used when deleting accounts, although we may use it for server-side policy
|
|
|
|
* rollbacks.
|
|
|
|
*/
|
|
|
|
public void reducePolicies() {
|
|
|
|
updatePolicies(-1);
|
|
|
|
setActivePolicies();
|
|
|
|
}
|
|
|
|
|
2011-01-13 21:12:55 +00:00
|
|
|
/**
|
|
|
|
* API: Query if the proposed set of policies are supported on the device.
|
|
|
|
*
|
2011-01-19 19:40:48 +00:00
|
|
|
* @param policies the polices that were requested
|
2011-01-13 21:12:55 +00:00
|
|
|
* @return boolean if supported
|
|
|
|
*/
|
|
|
|
public boolean isSupported(PolicySet policies) {
|
|
|
|
// IMPLEMENTATION: At this time, the only policy which might not be supported is
|
|
|
|
// encryption (which requires low-level systems support). Other policies are fully
|
|
|
|
// supported by the framework and do not need to be checked.
|
|
|
|
if (policies.mRequireEncryption) {
|
2011-01-17 20:54:40 +00:00
|
|
|
int encryptionStatus = getDPM().getStorageEncryptionStatus();
|
2011-01-13 21:12:55 +00:00
|
|
|
if (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-01-19 19:40:48 +00:00
|
|
|
/**
|
|
|
|
* API: Remove any unsupported policies
|
|
|
|
*
|
|
|
|
* This is used when we have a set of polices that have been requested, but the server
|
|
|
|
* is willing to allow unsupported policies to be considered optional.
|
|
|
|
*
|
|
|
|
* @param policies the polices that were requested
|
|
|
|
* @return the same PolicySet if all are supported; A replacement PolicySet if any
|
|
|
|
* unsupported policies were removed
|
|
|
|
*/
|
|
|
|
public PolicySet clearUnsupportedPolicies(PolicySet policies) {
|
|
|
|
PolicySet result = policies;
|
|
|
|
// IMPLEMENTATION: At this time, the only policy which might not be supported is
|
|
|
|
// encryption (which requires low-level systems support). Other policies are fully
|
|
|
|
// supported by the framework and do not need to be checked.
|
|
|
|
if (policies.mRequireEncryption) {
|
|
|
|
int encryptionStatus = getDPM().getStorageEncryptionStatus();
|
|
|
|
if (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) {
|
|
|
|
// Make new PolicySet w/o encryption
|
|
|
|
result = new PolicySet(policies.mMinPasswordLength, policies.mPasswordMode,
|
|
|
|
policies.mMaxPasswordFails, policies.mMaxScreenLockTime,
|
|
|
|
policies.mRequireRemoteWipe, policies.mPasswordExpirationDays,
|
|
|
|
policies.mPasswordHistory, policies.mPasswordComplexChars, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2011-01-13 21:12:55 +00:00
|
|
|
/**
|
|
|
|
* API: Query used to determine if a given policy is "active" (the device is operating at
|
|
|
|
* the required security level).
|
|
|
|
*
|
|
|
|
* @param policies the policies requested, or null to check aggregate stored policies
|
|
|
|
* @return true if the requested policies are active, false if not.
|
|
|
|
*/
|
|
|
|
public boolean isActive(PolicySet policies) {
|
|
|
|
int reasons = getInactiveReasons(policies);
|
|
|
|
return reasons == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return bits from isActive: Device Policy Manager has not been activated
|
|
|
|
*/
|
|
|
|
public final static int INACTIVE_NEED_ACTIVATION = 1;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return bits from isActive: Some required configuration is not correct (no user action).
|
|
|
|
*/
|
|
|
|
public final static int INACTIVE_NEED_CONFIGURATION = 2;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return bits from isActive: Password needs to be set or updated
|
|
|
|
*/
|
|
|
|
public final static int INACTIVE_NEED_PASSWORD = 4;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return bits from isActive: Encryption has not be enabled
|
|
|
|
*/
|
|
|
|
public final static int INACTIVE_NEED_ENCRYPTION = 8;
|
|
|
|
|
2010-02-02 00:48:16 +00:00
|
|
|
/**
|
|
|
|
* API: Query used to determine if a given policy is "active" (the device is operating at
|
2010-02-05 19:10:39 +00:00
|
|
|
* the required security level).
|
2010-01-27 01:24:15 +00:00
|
|
|
*
|
2010-02-05 19:10:39 +00:00
|
|
|
* This can be used when syncing a specific account, by passing a specific set of policies
|
|
|
|
* for that account. Or, it can be used at any time to compare the device
|
|
|
|
* state against the aggregate set of device policies stored in all accounts.
|
|
|
|
*
|
|
|
|
* This method is for queries only, and does not trigger any change in device state.
|
|
|
|
*
|
2010-12-01 20:58:36 +00:00
|
|
|
* NOTE: If there are multiple accounts with password expiration policies, the device
|
|
|
|
* password will be set to expire in the shortest required interval (most secure). This method
|
|
|
|
* will return 'false' as soon as the password expires - irrespective of which account caused
|
|
|
|
* the expiration. In other words, all accounts (that require expiration) will run/stop
|
|
|
|
* based on the requirements of the account with the shortest interval.
|
|
|
|
*
|
2010-02-05 19:10:39 +00:00
|
|
|
* @param policies the policies requested, or null to check aggregate stored policies
|
2011-01-13 21:12:55 +00:00
|
|
|
* @return zero if the requested policies are active, non-zero bits indicates that more work
|
|
|
|
* is needed (typically, by the user) before the required security polices are fully active.
|
2010-01-27 01:24:15 +00:00
|
|
|
*/
|
2011-01-13 21:12:55 +00:00
|
|
|
public int getInactiveReasons(PolicySet policies) {
|
2010-02-09 19:01:01 +00:00
|
|
|
// select aggregate set if needed
|
|
|
|
if (policies == null) {
|
|
|
|
policies = getAggregatePolicy();
|
|
|
|
}
|
|
|
|
// quick check for the "empty set" of no policies
|
|
|
|
if (policies == NO_POLICY_SET) {
|
2011-01-13 21:12:55 +00:00
|
|
|
return 0;
|
2010-02-09 19:01:01 +00:00
|
|
|
}
|
2011-01-13 21:12:55 +00:00
|
|
|
int reasons = 0;
|
2010-02-02 00:48:16 +00:00
|
|
|
DevicePolicyManager dpm = getDPM();
|
2010-12-09 00:06:16 +00:00
|
|
|
if (isActiveAdmin()) {
|
2010-02-02 00:48:16 +00:00
|
|
|
// check each policy explicitly
|
2010-02-05 19:10:39 +00:00
|
|
|
if (policies.mMinPasswordLength > 0) {
|
|
|
|
if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) {
|
2011-01-13 21:12:55 +00:00
|
|
|
reasons |= INACTIVE_NEED_PASSWORD;
|
2010-02-02 00:48:16 +00:00
|
|
|
}
|
|
|
|
}
|
2010-02-05 19:10:39 +00:00
|
|
|
if (policies.mPasswordMode > 0) {
|
|
|
|
if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
|
2011-01-13 21:12:55 +00:00
|
|
|
reasons |= INACTIVE_NEED_PASSWORD;
|
2010-02-02 00:48:16 +00:00
|
|
|
}
|
|
|
|
if (!dpm.isActivePasswordSufficient()) {
|
2011-01-13 21:12:55 +00:00
|
|
|
reasons |= INACTIVE_NEED_PASSWORD;
|
2010-02-02 00:48:16 +00:00
|
|
|
}
|
|
|
|
}
|
2010-02-05 19:10:39 +00:00
|
|
|
if (policies.mMaxScreenLockTime > 0) {
|
2010-02-02 00:48:16 +00:00
|
|
|
// Note, we use seconds, dpm uses milliseconds
|
2010-02-05 19:10:39 +00:00
|
|
|
if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) {
|
2011-01-13 21:12:55 +00:00
|
|
|
reasons |= INACTIVE_NEED_CONFIGURATION;
|
2010-02-02 00:48:16 +00:00
|
|
|
}
|
|
|
|
}
|
2010-12-01 20:58:36 +00:00
|
|
|
if (policies.mPasswordExpirationDays > 0) {
|
|
|
|
// confirm that expirations are currently set
|
|
|
|
long currentTimeout = dpm.getPasswordExpirationTimeout(mAdminName);
|
|
|
|
if (currentTimeout == 0
|
|
|
|
|| currentTimeout > policies.getDPManagerPasswordExpirationTimeout()) {
|
2011-01-13 21:12:55 +00:00
|
|
|
reasons |= INACTIVE_NEED_PASSWORD;
|
2010-12-01 20:58:36 +00:00
|
|
|
}
|
|
|
|
// confirm that the current password hasn't expired
|
|
|
|
long expirationDate = dpm.getPasswordExpiration(mAdminName);
|
|
|
|
long timeUntilExpiration = expirationDate - System.currentTimeMillis();
|
|
|
|
boolean expired = timeUntilExpiration < 0;
|
|
|
|
if (expired) {
|
2011-01-13 21:12:55 +00:00
|
|
|
reasons |= INACTIVE_NEED_PASSWORD;
|
2010-12-01 20:58:36 +00:00
|
|
|
}
|
2010-06-09 23:18:57 +00:00
|
|
|
}
|
|
|
|
if (policies.mPasswordHistory > 0) {
|
|
|
|
if (dpm.getPasswordHistoryLength(mAdminName) < policies.mPasswordHistory) {
|
2011-01-13 21:12:55 +00:00
|
|
|
reasons |= INACTIVE_NEED_PASSWORD;
|
2010-06-09 23:18:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (policies.mPasswordComplexChars > 0) {
|
|
|
|
if (dpm.getPasswordMinimumNonLetter(mAdminName) < policies.mPasswordComplexChars) {
|
2011-01-13 21:12:55 +00:00
|
|
|
reasons |= INACTIVE_NEED_PASSWORD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (policies.mRequireEncryption) {
|
2011-01-17 20:54:40 +00:00
|
|
|
int encryptionStatus = getDPM().getStorageEncryptionStatus();
|
2011-01-13 21:12:55 +00:00
|
|
|
if (encryptionStatus != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) {
|
|
|
|
reasons |= INACTIVE_NEED_ENCRYPTION;
|
2010-06-09 23:18:57 +00:00
|
|
|
}
|
|
|
|
}
|
2010-02-02 00:48:16 +00:00
|
|
|
// password failures are counted locally - no test required here
|
|
|
|
// no check required for remote wipe (it's supported, if we're the admin)
|
2010-02-09 01:42:42 +00:00
|
|
|
|
2011-01-13 21:12:55 +00:00
|
|
|
// If we made it all the way, reasons == 0 here. Otherwise it's a list of grievances.
|
|
|
|
return reasons;
|
2010-02-02 00:48:16 +00:00
|
|
|
}
|
2010-02-10 01:24:55 +00:00
|
|
|
// return false, not active
|
2011-01-13 21:12:55 +00:00
|
|
|
return INACTIVE_NEED_ACTIVATION;
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-02-09 19:01:01 +00:00
|
|
|
* Set the requested security level based on the aggregate set of requests.
|
|
|
|
* If the set is empty, we release our device administration. If the set is non-empty,
|
|
|
|
* we only proceed if we are already active as an admin.
|
2010-02-05 19:10:39 +00:00
|
|
|
*/
|
|
|
|
public void setActivePolicies() {
|
|
|
|
DevicePolicyManager dpm = getDPM();
|
2010-02-09 19:01:01 +00:00
|
|
|
// compute aggregate set of policies
|
|
|
|
PolicySet policies = getAggregatePolicy();
|
|
|
|
// if empty set, detach from policy manager
|
|
|
|
if (policies == NO_POLICY_SET) {
|
|
|
|
dpm.removeActiveAdmin(mAdminName);
|
2010-12-09 00:06:16 +00:00
|
|
|
} else if (isActiveAdmin()) {
|
2010-02-05 19:10:39 +00:00
|
|
|
// set each policy in the policy manager
|
|
|
|
// password mode & length
|
|
|
|
dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality());
|
|
|
|
dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength);
|
|
|
|
// screen lock time
|
|
|
|
dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000);
|
|
|
|
// local wipe (failed passwords limit)
|
|
|
|
dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails);
|
2010-12-01 20:58:36 +00:00
|
|
|
// password expiration (days until a password expires). API takes mSec.
|
|
|
|
dpm.setPasswordExpirationTimeout(mAdminName,
|
|
|
|
policies.getDPManagerPasswordExpirationTimeout());
|
2010-06-09 23:18:57 +00:00
|
|
|
// password history length (number of previous passwords that may not be reused)
|
|
|
|
dpm.setPasswordHistoryLength(mAdminName, policies.mPasswordHistory);
|
|
|
|
// password minimum complex characters
|
|
|
|
dpm.setPasswordMinimumNonLetter(mAdminName, policies.mPasswordComplexChars);
|
2011-01-13 21:12:55 +00:00
|
|
|
// encryption required
|
|
|
|
dpm.setStorageEncryption(mAdminName, policies.mRequireEncryption);
|
2010-02-05 19:10:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-09 01:42:42 +00:00
|
|
|
/**
|
|
|
|
* API: Set/Clear the "hold" flag in any account. This flag serves a dual purpose:
|
|
|
|
* Setting it gives us an indication that it was blocked, and clearing it gives EAS a
|
|
|
|
* signal to try syncing again.
|
2010-12-01 20:58:36 +00:00
|
|
|
* @param context
|
|
|
|
* @param account The account to update
|
|
|
|
* @param newState true = security hold, false = free to sync
|
2010-02-09 01:42:42 +00:00
|
|
|
*/
|
2010-12-01 20:58:36 +00:00
|
|
|
public static void setAccountHoldFlag(Context context, Account account, boolean newState) {
|
2010-02-09 01:42:42 +00:00
|
|
|
if (newState) {
|
|
|
|
account.mFlags |= Account.FLAGS_SECURITY_HOLD;
|
|
|
|
} else {
|
|
|
|
account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
|
|
|
|
}
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(AccountColumns.FLAGS, account.mFlags);
|
2010-12-01 20:58:36 +00:00
|
|
|
account.update(context, cv);
|
2010-02-09 01:42:42 +00:00
|
|
|
}
|
|
|
|
|
2010-02-05 19:10:39 +00:00
|
|
|
/**
|
|
|
|
* API: Sync service should call this any time a sync fails due to isActive() returning false.
|
2010-02-02 00:48:16 +00:00
|
|
|
* This will kick off the notify-acquire-admin-state process and/or increase the security level.
|
2010-01-27 01:24:15 +00:00
|
|
|
* The caller needs to write the required policies into this account before making this call.
|
2010-02-05 19:10:39 +00:00
|
|
|
* Should not be called from UI thread - uses DB lookups to prepare new notifications
|
2010-01-27 01:24:15 +00:00
|
|
|
*
|
|
|
|
* @param accountId the account for which sync cannot proceed
|
|
|
|
*/
|
|
|
|
public void policiesRequired(long accountId) {
|
2010-02-09 01:42:42 +00:00
|
|
|
Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
|
2010-12-01 20:58:36 +00:00
|
|
|
|
2010-02-09 01:42:42 +00:00
|
|
|
// Mark the account as "on hold".
|
2010-12-01 20:58:36 +00:00
|
|
|
setAccountHoldFlag(mContext, account, true);
|
|
|
|
|
|
|
|
// Put up a notification
|
2010-02-05 19:10:39 +00:00
|
|
|
String tickerText = mContext.getString(R.string.security_notification_ticker_fmt,
|
|
|
|
account.getDisplayName());
|
|
|
|
String contentTitle = mContext.getString(R.string.security_notification_content_title);
|
|
|
|
String contentText = account.getDisplayName();
|
|
|
|
Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, accountId);
|
2010-12-01 20:58:36 +00:00
|
|
|
NotificationController.getInstance(mContext).postAccountNotification(
|
|
|
|
account, tickerText, contentTitle, contentText, intent,
|
|
|
|
NotificationController.NOTIFICATION_ID_SECURITY_NEEDED);
|
2010-02-05 19:10:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called from the notification's intent receiver to register that the notification can be
|
|
|
|
* cleared now.
|
|
|
|
*/
|
2010-04-13 09:15:37 +00:00
|
|
|
public void clearNotification(long accountId) {
|
2010-12-01 20:58:36 +00:00
|
|
|
NotificationController.getInstance(mContext).cancelNotification(
|
|
|
|
NotificationController.NOTIFICATION_ID_SECURITY_NEEDED);
|
2010-02-05 19:10:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-02-09 19:01:01 +00:00
|
|
|
* API: Remote wipe (from server). This is final, there is no confirmation. It will only
|
|
|
|
* return to the caller if there is an unexpected failure.
|
2010-02-05 19:10:39 +00:00
|
|
|
*/
|
2010-02-09 19:01:01 +00:00
|
|
|
public void remoteWipe() {
|
2010-02-05 19:10:39 +00:00
|
|
|
DevicePolicyManager dpm = getDPM();
|
|
|
|
if (dpm.isAdminActive(mAdminName)) {
|
|
|
|
dpm.wipeData(0);
|
2010-02-09 19:01:01 +00:00
|
|
|
} else {
|
|
|
|
Log.d(Email.LOG_TAG, "Could not remote wipe because not device admin.");
|
2010-02-05 19:10:39 +00:00
|
|
|
}
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class for tracking policies and reading/writing into accounts
|
|
|
|
*/
|
2010-06-02 20:25:03 +00:00
|
|
|
public static class PolicySet implements Parcelable {
|
2010-01-27 01:24:15 +00:00
|
|
|
|
|
|
|
// Security (provisioning) flags
|
|
|
|
// bits 0..4: password length (0=no password required)
|
|
|
|
private static final int PASSWORD_LENGTH_MASK = 31;
|
|
|
|
private static final int PASSWORD_LENGTH_SHIFT = 0;
|
2010-04-03 05:05:12 +00:00
|
|
|
public static final int PASSWORD_LENGTH_MAX = 30;
|
2010-01-27 01:24:15 +00:00
|
|
|
// bits 5..8: password mode
|
|
|
|
private static final int PASSWORD_MODE_SHIFT = 5;
|
|
|
|
private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT;
|
|
|
|
public static final int PASSWORD_MODE_NONE = 0 << PASSWORD_MODE_SHIFT;
|
|
|
|
public static final int PASSWORD_MODE_SIMPLE = 1 << PASSWORD_MODE_SHIFT;
|
|
|
|
public static final int PASSWORD_MODE_STRONG = 2 << PASSWORD_MODE_SHIFT;
|
|
|
|
// bits 9..13: password failures -> wipe device (0=disabled)
|
|
|
|
private static final int PASSWORD_MAX_FAILS_SHIFT = 9;
|
|
|
|
private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT;
|
|
|
|
public static final int PASSWORD_MAX_FAILS_MAX = 31;
|
|
|
|
// bits 14..24: seconds to screen lock (0=not required)
|
|
|
|
private static final int SCREEN_LOCK_TIME_SHIFT = 14;
|
|
|
|
private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT;
|
|
|
|
public static final int SCREEN_LOCK_TIME_MAX = 2047;
|
|
|
|
// bit 25: remote wipe capability required
|
|
|
|
private static final int REQUIRE_REMOTE_WIPE = 1 << 25;
|
2010-06-09 23:18:57 +00:00
|
|
|
// bit 26..35: password expiration (days; 0=not required)
|
|
|
|
private static final int PASSWORD_EXPIRATION_SHIFT = 26;
|
|
|
|
private static final long PASSWORD_EXPIRATION_MASK = 1023L << PASSWORD_EXPIRATION_SHIFT;
|
|
|
|
public static final int PASSWORD_EXPIRATION_MAX = 1023;
|
2011-01-13 21:12:55 +00:00
|
|
|
// bit 36..43: password history (length; 0=not required)
|
2010-06-09 23:18:57 +00:00
|
|
|
private static final int PASSWORD_HISTORY_SHIFT = 36;
|
|
|
|
private static final long PASSWORD_HISTORY_MASK = 255L << PASSWORD_HISTORY_SHIFT;
|
|
|
|
public static final int PASSWORD_HISTORY_MAX = 255;
|
2011-01-13 21:12:55 +00:00
|
|
|
// bit 44..48: min complex characters (0=not required)
|
2010-06-09 23:18:57 +00:00
|
|
|
private static final int PASSWORD_COMPLEX_CHARS_SHIFT = 44;
|
|
|
|
private static final long PASSWORD_COMPLEX_CHARS_MASK = 31L << PASSWORD_COMPLEX_CHARS_SHIFT;
|
|
|
|
public static final int PASSWORD_COMPLEX_CHARS_MAX = 31;
|
2011-01-13 21:12:55 +00:00
|
|
|
// bit 49: requires device encryption
|
|
|
|
private static final long REQUIRE_ENCRYPTION = 1L << 49;
|
2010-01-27 01:24:15 +00:00
|
|
|
|
2010-12-01 20:58:36 +00:00
|
|
|
/* Convert days to mSec (used for password expiration) */
|
|
|
|
private static final long DAYS_TO_MSEC = 24 * 60 * 60 * 1000;
|
|
|
|
/* Small offset (2 minutes) added to policy expiration to make user testing easier. */
|
|
|
|
private static final long EXPIRATION_OFFSET_MSEC = 2 * 60 * 1000;
|
|
|
|
|
2010-06-14 19:24:02 +00:00
|
|
|
/*package*/ final int mMinPasswordLength;
|
|
|
|
/*package*/ final int mPasswordMode;
|
|
|
|
/*package*/ final int mMaxPasswordFails;
|
|
|
|
/*package*/ final int mMaxScreenLockTime;
|
|
|
|
/*package*/ final boolean mRequireRemoteWipe;
|
2010-12-01 20:58:36 +00:00
|
|
|
/*package*/ final int mPasswordExpirationDays;
|
2010-06-09 23:18:57 +00:00
|
|
|
/*package*/ final int mPasswordHistory;
|
|
|
|
/*package*/ final int mPasswordComplexChars;
|
2011-01-13 21:12:55 +00:00
|
|
|
/*package*/ final boolean mRequireEncryption;
|
2010-06-14 19:24:02 +00:00
|
|
|
|
2010-09-22 19:06:41 +00:00
|
|
|
public int getMinPasswordLengthForTest() {
|
2010-06-14 19:24:02 +00:00
|
|
|
return mMinPasswordLength;
|
|
|
|
}
|
|
|
|
|
2010-09-22 19:06:41 +00:00
|
|
|
public int getPasswordModeForTest() {
|
2010-06-14 19:24:02 +00:00
|
|
|
return mPasswordMode;
|
|
|
|
}
|
|
|
|
|
2010-09-22 19:06:41 +00:00
|
|
|
public int getMaxPasswordFailsForTest() {
|
2010-06-14 19:24:02 +00:00
|
|
|
return mMaxPasswordFails;
|
|
|
|
}
|
|
|
|
|
2010-09-22 19:06:41 +00:00
|
|
|
public int getMaxScreenLockTimeForTest() {
|
2010-06-14 19:24:02 +00:00
|
|
|
return mMaxScreenLockTime;
|
|
|
|
}
|
|
|
|
|
2010-09-22 19:06:41 +00:00
|
|
|
public boolean isRequireRemoteWipeForTest() {
|
2010-06-14 19:24:02 +00:00
|
|
|
return mRequireRemoteWipe;
|
|
|
|
}
|
2010-01-27 01:24:15 +00:00
|
|
|
|
2011-01-13 21:12:55 +00:00
|
|
|
public boolean isRequireEncryptionForTest() {
|
|
|
|
return mRequireEncryption;
|
|
|
|
}
|
|
|
|
|
2010-01-27 01:24:15 +00:00
|
|
|
/**
|
|
|
|
* Create from raw values.
|
|
|
|
* @param minPasswordLength (0=not enforced)
|
|
|
|
* @param passwordMode
|
|
|
|
* @param maxPasswordFails (0=not enforced)
|
2010-02-02 00:48:16 +00:00
|
|
|
* @param maxScreenLockTime in seconds (0=not enforced)
|
2010-01-27 01:24:15 +00:00
|
|
|
* @param requireRemoteWipe
|
2010-12-01 20:58:36 +00:00
|
|
|
* @param passwordExpirationDays in days (0=not enforced)
|
|
|
|
* @param passwordHistory (0=not enforced)
|
|
|
|
* @param passwordComplexChars (0=not enforced)
|
2010-04-03 05:05:12 +00:00
|
|
|
* @throws IllegalArgumentException for illegal arguments.
|
2010-01-27 01:24:15 +00:00
|
|
|
*/
|
|
|
|
public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
|
2010-12-01 20:58:36 +00:00
|
|
|
int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpirationDays,
|
2011-01-13 21:12:55 +00:00
|
|
|
int passwordHistory, int passwordComplexChars, boolean requireEncryption)
|
|
|
|
throws IllegalArgumentException {
|
2010-08-07 02:57:05 +00:00
|
|
|
// If we're not enforcing passwords, make sure we clean up related values, since EAS
|
|
|
|
// can send non-zero values for any or all of these
|
|
|
|
if (passwordMode == PASSWORD_MODE_NONE) {
|
|
|
|
maxPasswordFails = 0;
|
|
|
|
maxScreenLockTime = 0;
|
|
|
|
minPasswordLength = 0;
|
|
|
|
passwordComplexChars = 0;
|
|
|
|
passwordHistory = 0;
|
2010-12-01 20:58:36 +00:00
|
|
|
passwordExpirationDays = 0;
|
2010-08-07 02:57:05 +00:00
|
|
|
} else {
|
|
|
|
if ((passwordMode != PASSWORD_MODE_SIMPLE) &&
|
|
|
|
(passwordMode != PASSWORD_MODE_STRONG)) {
|
|
|
|
throw new IllegalArgumentException("password mode");
|
|
|
|
}
|
2010-08-28 01:01:21 +00:00
|
|
|
// If we're only requiring a simple password, set complex chars to zero; note
|
|
|
|
// that EAS can erroneously send non-zero values in this case
|
|
|
|
if (passwordMode == PASSWORD_MODE_SIMPLE) {
|
|
|
|
passwordComplexChars = 0;
|
|
|
|
}
|
2010-08-07 02:57:05 +00:00
|
|
|
// The next four values have hard limits which cannot be supported if exceeded.
|
|
|
|
if (minPasswordLength > PASSWORD_LENGTH_MAX) {
|
|
|
|
throw new IllegalArgumentException("password length");
|
|
|
|
}
|
2010-12-01 20:58:36 +00:00
|
|
|
if (passwordExpirationDays > PASSWORD_EXPIRATION_MAX) {
|
2010-08-07 02:57:05 +00:00
|
|
|
throw new IllegalArgumentException("password expiration");
|
|
|
|
}
|
|
|
|
if (passwordHistory > PASSWORD_HISTORY_MAX) {
|
|
|
|
throw new IllegalArgumentException("password history");
|
|
|
|
}
|
|
|
|
if (passwordComplexChars > PASSWORD_COMPLEX_CHARS_MAX) {
|
|
|
|
throw new IllegalArgumentException("complex chars");
|
|
|
|
}
|
|
|
|
// This value can be reduced (which actually increases security) if necessary
|
|
|
|
if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
|
|
|
|
maxPasswordFails = PASSWORD_MAX_FAILS_MAX;
|
|
|
|
}
|
|
|
|
// This value can be reduced (which actually increases security) if necessary
|
|
|
|
if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
|
|
|
|
maxScreenLockTime = SCREEN_LOCK_TIME_MAX;
|
|
|
|
}
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
mMinPasswordLength = minPasswordLength;
|
|
|
|
mPasswordMode = passwordMode;
|
|
|
|
mMaxPasswordFails = maxPasswordFails;
|
|
|
|
mMaxScreenLockTime = maxScreenLockTime;
|
|
|
|
mRequireRemoteWipe = requireRemoteWipe;
|
2010-12-01 20:58:36 +00:00
|
|
|
mPasswordExpirationDays = passwordExpirationDays;
|
2010-06-09 23:18:57 +00:00
|
|
|
mPasswordHistory = passwordHistory;
|
|
|
|
mPasswordComplexChars = passwordComplexChars;
|
2011-01-13 21:12:55 +00:00
|
|
|
mRequireEncryption = requireEncryption;
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create from values encoded in an account
|
|
|
|
* @param account
|
|
|
|
*/
|
|
|
|
public PolicySet(Account account) {
|
|
|
|
this(account.mSecurityFlags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create from values encoded in an account flags int
|
|
|
|
*/
|
2010-06-09 23:18:57 +00:00
|
|
|
public PolicySet(long flags) {
|
2010-01-27 01:24:15 +00:00
|
|
|
mMinPasswordLength =
|
2010-06-09 23:18:57 +00:00
|
|
|
(int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT);
|
2010-01-27 01:24:15 +00:00
|
|
|
mPasswordMode =
|
2010-06-09 23:18:57 +00:00
|
|
|
(int) (flags & PASSWORD_MODE_MASK);
|
2010-01-27 01:24:15 +00:00
|
|
|
mMaxPasswordFails =
|
2010-06-09 23:18:57 +00:00
|
|
|
(int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT);
|
2010-01-27 01:24:15 +00:00
|
|
|
mMaxScreenLockTime =
|
2010-06-09 23:18:57 +00:00
|
|
|
(int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT);
|
2010-01-27 01:24:15 +00:00
|
|
|
mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
|
2010-12-01 20:58:36 +00:00
|
|
|
mPasswordExpirationDays =
|
2010-06-09 23:18:57 +00:00
|
|
|
(int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT);
|
|
|
|
mPasswordHistory =
|
|
|
|
(int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT);
|
|
|
|
mPasswordComplexChars =
|
|
|
|
(int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT);
|
2011-01-13 21:12:55 +00:00
|
|
|
mRequireEncryption = 0 != (flags & REQUIRE_ENCRYPTION);
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-02-05 19:10:39 +00:00
|
|
|
* Helper to map our internal encoding to DevicePolicyManager password modes.
|
2010-02-02 00:48:16 +00:00
|
|
|
*/
|
2010-02-05 19:10:39 +00:00
|
|
|
public int getDPManagerPasswordQuality() {
|
2010-02-02 00:48:16 +00:00
|
|
|
switch (mPasswordMode) {
|
|
|
|
case PASSWORD_MODE_SIMPLE:
|
|
|
|
return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
|
|
|
|
case PASSWORD_MODE_STRONG:
|
|
|
|
return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
|
|
|
|
default:
|
|
|
|
return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-01 20:58:36 +00:00
|
|
|
/**
|
|
|
|
* Helper to map expiration times to the millisecond values used by DevicePolicyManager.
|
|
|
|
*/
|
|
|
|
public long getDPManagerPasswordExpirationTimeout() {
|
|
|
|
long result = mPasswordExpirationDays * DAYS_TO_MSEC;
|
|
|
|
// Add a small offset to the password expiration. This makes it easier to test
|
|
|
|
// by changing (for example) 1 day to 1 day + 5 minutes. If you set an expiration
|
|
|
|
// that is within the warning period, you should get a warning fairly quickly.
|
|
|
|
if (result > 0) {
|
|
|
|
result += EXPIRATION_OFFSET_MSEC;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2010-02-02 00:48:16 +00:00
|
|
|
/**
|
2010-01-27 01:24:15 +00:00
|
|
|
* Record flags (and a sync key for the flags) into an Account
|
|
|
|
* Note: the hash code is defined as the encoding used in Account
|
2010-02-05 19:10:39 +00:00
|
|
|
*
|
2010-01-27 01:24:15 +00:00
|
|
|
* @param account to write the values mSecurityFlags and mSecuritySyncKey
|
|
|
|
* @param syncKey the value to write into the account's mSecuritySyncKey
|
2010-02-05 19:10:39 +00:00
|
|
|
* @param update if true, also writes the account back to the provider (updating only
|
|
|
|
* the fields changed by this API)
|
|
|
|
* @param context a context for writing to the provider
|
|
|
|
* @return true if the actual policies changed, false if no change (note, sync key
|
|
|
|
* does not affect this)
|
2010-01-27 01:24:15 +00:00
|
|
|
*/
|
2010-02-05 19:10:39 +00:00
|
|
|
public boolean writeAccount(Account account, String syncKey, boolean update,
|
|
|
|
Context context) {
|
2010-06-09 23:18:57 +00:00
|
|
|
long newFlags = getSecurityCode();
|
2010-02-05 19:10:39 +00:00
|
|
|
boolean dirty = (newFlags != account.mSecurityFlags);
|
|
|
|
account.mSecurityFlags = newFlags;
|
|
|
|
account.mSecuritySyncKey = syncKey;
|
|
|
|
if (update) {
|
|
|
|
if (account.isSaved()) {
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
|
|
|
|
cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
|
|
|
|
account.update(context, cv);
|
|
|
|
} else {
|
|
|
|
account.save(context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dirty;
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
if (o instanceof PolicySet) {
|
|
|
|
PolicySet other = (PolicySet)o;
|
2010-06-09 23:18:57 +00:00
|
|
|
return (this.getSecurityCode() == other.getSecurityCode());
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2010-06-02 20:25:03 +00:00
|
|
|
/**
|
|
|
|
* Supports Parcelable
|
|
|
|
*/
|
|
|
|
public int describeContents() {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Supports Parcelable
|
|
|
|
*/
|
|
|
|
public static final Parcelable.Creator<PolicySet> CREATOR
|
|
|
|
= new Parcelable.Creator<PolicySet>() {
|
|
|
|
public PolicySet createFromParcel(Parcel in) {
|
|
|
|
return new PolicySet(in);
|
|
|
|
}
|
|
|
|
|
|
|
|
public PolicySet[] newArray(int size) {
|
|
|
|
return new PolicySet[size];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Supports Parcelable
|
|
|
|
*/
|
|
|
|
public void writeToParcel(Parcel dest, int flags) {
|
|
|
|
dest.writeInt(mMinPasswordLength);
|
|
|
|
dest.writeInt(mPasswordMode);
|
|
|
|
dest.writeInt(mMaxPasswordFails);
|
|
|
|
dest.writeInt(mMaxScreenLockTime);
|
|
|
|
dest.writeInt(mRequireRemoteWipe ? 1 : 0);
|
2010-12-01 20:58:36 +00:00
|
|
|
dest.writeInt(mPasswordExpirationDays);
|
2010-06-02 20:25:03 +00:00
|
|
|
dest.writeInt(mPasswordHistory);
|
|
|
|
dest.writeInt(mPasswordComplexChars);
|
2011-01-13 21:12:55 +00:00
|
|
|
dest.writeInt(mRequireEncryption ? 1 : 0);
|
2010-06-02 20:25:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Supports Parcelable
|
|
|
|
*/
|
|
|
|
public PolicySet(Parcel in) {
|
|
|
|
mMinPasswordLength = in.readInt();
|
|
|
|
mPasswordMode = in.readInt();
|
|
|
|
mMaxPasswordFails = in.readInt();
|
|
|
|
mMaxScreenLockTime = in.readInt();
|
|
|
|
mRequireRemoteWipe = in.readInt() == 1;
|
2010-12-01 20:58:36 +00:00
|
|
|
mPasswordExpirationDays = in.readInt();
|
2010-06-02 20:25:03 +00:00
|
|
|
mPasswordHistory = in.readInt();
|
|
|
|
mPasswordComplexChars = in.readInt();
|
2011-01-13 21:12:55 +00:00
|
|
|
mRequireEncryption = in.readInt() == 1;
|
2010-06-02 20:25:03 +00:00
|
|
|
}
|
|
|
|
|
2010-01-27 01:24:15 +00:00
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
2010-06-09 23:18:57 +00:00
|
|
|
long code = getSecurityCode();
|
|
|
|
return (int) code;
|
|
|
|
}
|
|
|
|
|
|
|
|
public long getSecurityCode() {
|
|
|
|
long flags = 0;
|
|
|
|
flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
|
2010-01-27 01:24:15 +00:00
|
|
|
flags |= mPasswordMode;
|
2010-06-09 23:18:57 +00:00
|
|
|
flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
|
|
|
|
flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
|
2011-01-13 21:12:55 +00:00
|
|
|
if (mRequireRemoteWipe) flags |= REQUIRE_REMOTE_WIPE;
|
2010-06-09 23:18:57 +00:00
|
|
|
flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT;
|
2010-12-01 20:58:36 +00:00
|
|
|
flags |= (long)mPasswordExpirationDays << PASSWORD_EXPIRATION_SHIFT;
|
2010-06-09 23:18:57 +00:00
|
|
|
flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT;
|
2011-01-13 21:12:55 +00:00
|
|
|
if (mRequireEncryption) flags |= REQUIRE_ENCRYPTION;
|
2010-01-27 01:24:15 +00:00
|
|
|
return flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
|
|
|
|
+ " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
|
2010-06-09 23:18:57 +00:00
|
|
|
+ mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe
|
2010-12-01 20:58:36 +00:00
|
|
|
+ " pw-expiration=" + mPasswordExpirationDays
|
2010-06-09 23:18:57 +00:00
|
|
|
+ " pw-history=" + mPasswordHistory
|
2011-01-13 21:12:55 +00:00
|
|
|
+ " pw-complex-chars=" + mPasswordComplexChars
|
|
|
|
+ " require-encryption=" + mRequireEncryption + "}";
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-02-05 19:10:39 +00:00
|
|
|
* If we are not the active device admin, try to become so.
|
|
|
|
*
|
2010-12-09 00:06:16 +00:00
|
|
|
* Also checks for any policies that we have added during the lifetime of this app.
|
|
|
|
* This catches the case where the user granted an earlier (smaller) set of policies
|
|
|
|
* but an app upgrade requires that new policies be granted.
|
|
|
|
*
|
2010-02-05 19:10:39 +00:00
|
|
|
* @return true if we are already active, false if we are not
|
2010-01-27 01:24:15 +00:00
|
|
|
*/
|
2010-02-05 19:10:39 +00:00
|
|
|
public boolean isActiveAdmin() {
|
|
|
|
DevicePolicyManager dpm = getDPM();
|
2011-01-17 20:54:40 +00:00
|
|
|
return dpm.isAdminActive(mAdminName)
|
|
|
|
&& dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)
|
|
|
|
&& dpm.hasGrantedPolicy(mAdminName, DeviceAdminInfo.USES_ENCRYPTED_STORAGE);
|
2010-02-05 19:10:39 +00:00
|
|
|
}
|
2010-01-27 01:24:15 +00:00
|
|
|
|
2010-02-05 19:10:39 +00:00
|
|
|
/**
|
|
|
|
* Report admin component name - for making calls into device policy manager
|
|
|
|
*/
|
|
|
|
public ComponentName getAdminComponent() {
|
|
|
|
return mAdminName;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2010-10-25 18:49:29 +00:00
|
|
|
* Delete all accounts whose security flags aren't zero (i.e. they have security enabled).
|
|
|
|
* This method is synchronous, so it should normally be called within a worker thread (the
|
|
|
|
* exception being for unit tests)
|
|
|
|
*
|
|
|
|
* @param context the caller's context
|
|
|
|
*/
|
|
|
|
/*package*/ void deleteSecuredAccounts(Context context) {
|
|
|
|
ContentResolver cr = context.getContentResolver();
|
|
|
|
// Find all accounts with security and delete them
|
|
|
|
Cursor c = cr.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
|
|
|
|
AccountColumns.SECURITY_FLAGS + "!=0", null, null);
|
|
|
|
try {
|
|
|
|
Log.w(TAG, "Email administration disabled; deleting " + c.getCount() +
|
|
|
|
" secured account(s)");
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
Controller.getInstance(context).deleteAccountSync(
|
|
|
|
c.getLong(EmailContent.ID_PROJECTION_COLUMN), context);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
updatePolicies(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal handler for enabled->disabled transitions. Deletes all secured accounts.
|
2010-12-30 08:16:55 +00:00
|
|
|
* Must call from worker thread, not on UI thread.
|
2010-02-05 19:10:39 +00:00
|
|
|
*/
|
2010-10-25 18:49:29 +00:00
|
|
|
/*package*/ void onAdminEnabled(boolean isEnabled) {
|
2010-04-07 05:17:21 +00:00
|
|
|
if (!isEnabled) {
|
2010-12-30 08:16:55 +00:00
|
|
|
deleteSecuredAccounts(mContext);
|
2010-02-05 19:10:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-01 20:58:36 +00:00
|
|
|
/**
|
|
|
|
* Handle password expiration - if any accounts appear to have triggered this, put up
|
|
|
|
* warnings, or even shut them down.
|
|
|
|
*
|
|
|
|
* NOTE: If there are multiple accounts with password expiration policies, the device
|
|
|
|
* password will be set to expire in the shortest required interval (most secure). The logic
|
|
|
|
* in this method operates based on the aggregate setting - irrespective of which account caused
|
|
|
|
* the expiration. In other words, all accounts (that require expiration) will run/stop
|
|
|
|
* based on the requirements of the account with the shortest interval.
|
|
|
|
*/
|
2010-12-30 08:16:55 +00:00
|
|
|
private void onPasswordExpiring(Context context) {
|
2010-12-01 20:58:36 +00:00
|
|
|
// 1. Do we have any accounts that matter here?
|
|
|
|
long nextExpiringAccountId = findShortestExpiration(context);
|
|
|
|
|
|
|
|
// 2. If not, exit immediately
|
|
|
|
if (nextExpiringAccountId == -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. If yes, are we warning or expired?
|
|
|
|
long expirationDate = getDPM().getPasswordExpiration(mAdminName);
|
|
|
|
long timeUntilExpiration = expirationDate - System.currentTimeMillis();
|
|
|
|
boolean expired = timeUntilExpiration < 0;
|
|
|
|
if (!expired) {
|
|
|
|
// 4. If warning, simply put up a generic notification and report that it came from
|
|
|
|
// the shortest-expiring account.
|
|
|
|
Account account = Account.restoreAccountWithId(context, nextExpiringAccountId);
|
|
|
|
if (account == null) return;
|
|
|
|
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
|
|
|
|
String ticker = context.getString(
|
|
|
|
R.string.password_expire_warning_ticker_fmt, account.getDisplayName());
|
|
|
|
String contentTitle = context.getString(
|
|
|
|
R.string.password_expire_warning_content_title);
|
|
|
|
String contentText = context.getString(
|
|
|
|
R.string.password_expire_warning_content_text_fmt, account.getDisplayName());
|
|
|
|
NotificationController nc = NotificationController.getInstance(mContext);
|
|
|
|
nc.postAccountNotification(account, ticker, contentTitle, contentText, intent,
|
|
|
|
NotificationController.NOTIFICATION_ID_PASSWORD_EXPIRING);
|
|
|
|
} else {
|
|
|
|
// 5. Actually expired - find all accounts that expire passwords, and wipe them
|
|
|
|
boolean wiped = wipeExpiredAccounts(context, Controller.getInstance(context));
|
|
|
|
if (wiped) {
|
|
|
|
// Post notification
|
|
|
|
Account account = Account.restoreAccountWithId(context, nextExpiringAccountId);
|
|
|
|
if (account == null) return;
|
|
|
|
Intent intent =
|
|
|
|
new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
|
|
|
|
String ticker = context.getString(R.string.password_expired_ticker);
|
|
|
|
String contentTitle = context.getString(R.string.password_expired_content_title);
|
|
|
|
String contentText = context.getString(R.string.password_expired_content_text);
|
|
|
|
NotificationController nc = NotificationController.getInstance(mContext);
|
|
|
|
nc.postAccountNotification(account, ticker, contentTitle,
|
|
|
|
contentText, intent,
|
|
|
|
NotificationController.NOTIFICATION_ID_PASSWORD_EXPIRED);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the account with the shortest expiration time. This is always assumed to be
|
|
|
|
* the account that forces the password to be refreshed.
|
|
|
|
* @return -1 if no expirations, or accountId if one is found
|
|
|
|
*/
|
|
|
|
/* package */ static long findShortestExpiration(Context context) {
|
|
|
|
long nextExpiringAccountId = -1;
|
|
|
|
long shortestExpiration = Long.MAX_VALUE;
|
|
|
|
Cursor c = context.getContentResolver().query(Account.CONTENT_URI,
|
|
|
|
ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null);
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS);
|
|
|
|
if (flags != 0) {
|
|
|
|
PolicySet p = new PolicySet(flags);
|
|
|
|
if (p.mPasswordExpirationDays > 0 &&
|
|
|
|
p.mPasswordExpirationDays < shortestExpiration) {
|
|
|
|
nextExpiringAccountId = c.getLong(ACCOUNT_SECURITY_COLUMN_ID);
|
|
|
|
shortestExpiration = p.mPasswordExpirationDays;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
return nextExpiringAccountId;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* For all accounts that require password expiration, put them in security hold and wipe
|
|
|
|
* their data.
|
|
|
|
* @param context
|
|
|
|
* @param controller
|
|
|
|
* @return true if one or more accounts were wiped
|
|
|
|
*/
|
|
|
|
/* package */ static boolean wipeExpiredAccounts(Context context, Controller controller) {
|
|
|
|
boolean result = false;
|
|
|
|
Cursor c = context.getContentResolver().query(Account.CONTENT_URI,
|
|
|
|
ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null);
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS);
|
|
|
|
if (flags != 0) {
|
|
|
|
PolicySet p = new PolicySet(flags);
|
|
|
|
if (p.mPasswordExpirationDays > 0) {
|
|
|
|
long accountId = c.getLong(ACCOUNT_SECURITY_COLUMN_ID);
|
|
|
|
Account account = Account.restoreAccountWithId(context, accountId);
|
|
|
|
if (account != null) {
|
|
|
|
// Mark the account as "on hold".
|
|
|
|
setAccountHoldFlag(context, account, true);
|
|
|
|
// Erase data
|
|
|
|
controller.deleteSyncedDataSync(accountId);
|
|
|
|
// Report one or more were found
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2010-12-30 08:16:55 +00:00
|
|
|
/**
|
|
|
|
* Callback from EmailBroadcastProcessorService. This provides the workers for the
|
|
|
|
* DeviceAdminReceiver calls. These should perform the work directly and not use async
|
|
|
|
* threads for completion.
|
|
|
|
*/
|
|
|
|
public static void onDeviceAdminReceiverMessage(Context context, int message) {
|
|
|
|
SecurityPolicy instance = SecurityPolicy.getInstance(context);
|
|
|
|
switch (message) {
|
|
|
|
case DEVICE_ADMIN_MESSAGE_ENABLED:
|
|
|
|
instance.onAdminEnabled(true);
|
|
|
|
break;
|
|
|
|
case DEVICE_ADMIN_MESSAGE_DISABLED:
|
|
|
|
instance.onAdminEnabled(false);
|
|
|
|
break;
|
|
|
|
case DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED:
|
|
|
|
// TODO make a small helper for this
|
|
|
|
// Clear security holds (if any)
|
|
|
|
Account.clearSecurityHoldOnAllAccounts(context);
|
|
|
|
// Cancel any active notifications (if any are posted)
|
|
|
|
NotificationController nc = NotificationController.getInstance(context);
|
|
|
|
nc.cancelNotification(NotificationController.NOTIFICATION_ID_PASSWORD_EXPIRING);
|
|
|
|
nc.cancelNotification(NotificationController.NOTIFICATION_ID_PASSWORD_EXPIRED);
|
|
|
|
break;
|
|
|
|
case DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING:
|
|
|
|
instance.onPasswordExpiring(instance.mContext);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-02-05 19:10:39 +00:00
|
|
|
/**
|
|
|
|
* Device Policy administrator. This is primarily a listener for device state changes.
|
|
|
|
* Note: This is instantiated by incoming messages.
|
2010-12-30 08:16:55 +00:00
|
|
|
* Note: This is actually a BroadcastReceiver and must remain within the guidelines required
|
|
|
|
* for proper behavior, including avoidance of ANRs.
|
2010-02-09 07:09:05 +00:00
|
|
|
* Note: We do not implement onPasswordFailed() because the default behavior of the
|
|
|
|
* DevicePolicyManager - complete local wipe after 'n' failures - is sufficient.
|
2010-02-05 19:10:39 +00:00
|
|
|
*/
|
2010-02-17 04:40:32 +00:00
|
|
|
public static class PolicyAdmin extends DeviceAdminReceiver {
|
2010-01-27 01:24:15 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Called after the administrator is first enabled.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void onEnabled(Context context, Intent intent) {
|
2010-12-30 08:16:55 +00:00
|
|
|
EmailBroadcastProcessorService.processDevicePolicyMessage(context,
|
|
|
|
DEVICE_ADMIN_MESSAGE_ENABLED);
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
2010-04-07 05:17:21 +00:00
|
|
|
|
2010-01-27 01:24:15 +00:00
|
|
|
/**
|
|
|
|
* Called prior to the administrator being disabled.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void onDisabled(Context context, Intent intent) {
|
2010-12-30 08:16:55 +00:00
|
|
|
EmailBroadcastProcessorService.processDevicePolicyMessage(context,
|
|
|
|
DEVICE_ADMIN_MESSAGE_DISABLED);
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
2010-04-07 05:17:21 +00:00
|
|
|
|
2010-10-25 18:49:29 +00:00
|
|
|
/**
|
|
|
|
* Called when the user asks to disable administration; we return a warning string that
|
|
|
|
* will be presented to the user
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public CharSequence onDisableRequested(Context context, Intent intent) {
|
|
|
|
return context.getString(R.string.disable_admin_warning);
|
|
|
|
}
|
|
|
|
|
2010-01-27 01:24:15 +00:00
|
|
|
/**
|
|
|
|
* Called after the user has changed their password.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void onPasswordChanged(Context context, Intent intent) {
|
2010-12-30 08:16:55 +00:00
|
|
|
EmailBroadcastProcessorService.processDevicePolicyMessage(context,
|
|
|
|
DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED);
|
2010-12-01 20:58:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when device password is expiring
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void onPasswordExpiring(Context context, Intent intent) {
|
2010-12-30 08:16:55 +00:00
|
|
|
EmailBroadcastProcessorService.processDevicePolicyMessage(context,
|
|
|
|
DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING);
|
2010-01-27 01:24:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|