Merge "Rewrite of security policy handling and service code"

This commit is contained in:
Marc Blank 2011-10-25 14:24:14 -07:00 committed by Android (Google) Code Review
commit ad921d01ca
20 changed files with 536 additions and 355 deletions

View File

@ -671,10 +671,6 @@ public final class Account extends EmailContent implements AccountColumns, Parce
*/
@Override
public int update(Context context, ContentValues cv) {
if (mPolicy != null && mPolicyKey <= 0) {
// If a policy is set and there's no policy, link it to the account
Policy.setAccountPolicy(context, this, mPolicy, null);
}
if (cv.containsKey(AccountColumns.IS_DEFAULT) &&
cv.getAsBoolean(AccountColumns.IS_DEFAULT)) {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

View File

@ -1437,5 +1437,8 @@ public abstract class EmailContent {
public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback";
// Indicates that the server allows password recovery, not that we support it
public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled";
// Tokenized strings indicating protocol specific policies enforced/unsupported
public static final String PROTOCOL_POLICIES_ENFORCED = "protocolPoliciesEnforced";
public static final String PROTOCOL_POLICIES_UNSUPPORTED = "protocolPoliciesUnsupported";
}
}

View File

@ -16,21 +16,17 @@
package com.android.emailcommon.provider;
import android.app.admin.DevicePolicyManager;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import com.android.emailcommon.utility.TextUtilities;
import com.android.emailcommon.utility.Utility;
import com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
@ -56,6 +52,8 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
public static final int PASSWORD_MODE_SIMPLE = 1;
public static final int PASSWORD_MODE_STRONG = 2;
public static final char POLICY_STRING_DELIMITER = '\1';
public int mPasswordMode;
public int mPasswordMinLength;
public int mPasswordMaxFails;
@ -76,6 +74,8 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
public int mMaxEmailLookback;
public int mMaxCalendarLookback;
public boolean mPasswordRecoveryEnabled;
public String mProtocolPoliciesEnforced;
public String mProtocolPoliciesUnsupported;
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_PASSWORD_MODE_COLUMN = 1;
@ -98,6 +98,8 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
public static final int CONTENT_MAX_EMAIL_LOOKBACK_COLUMN = 18;
public static final int CONTENT_MAX_CALENDAR_LOOKBACK_COLUMN = 19;
public static final int CONTENT_PASSWORD_RECOVERY_ENABLED_COLUMN = 20;
public static final int CONTENT_PROTOCOL_POLICIES_ENFORCED_COLUMN = 21;
public static final int CONTENT_PROTOCOL_POLICIES_UNSUPPORTED_COLUMN = 22;
public static final String[] CONTENT_PROJECTION = new String[] {RECORD_ID,
PolicyColumns.PASSWORD_MODE, PolicyColumns.PASSWORD_MIN_LENGTH,
@ -109,7 +111,8 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
PolicyColumns.DONT_ALLOW_ATTACHMENTS, PolicyColumns.DONT_ALLOW_HTML,
PolicyColumns.MAX_ATTACHMENT_SIZE, PolicyColumns.MAX_TEXT_TRUNCATION_SIZE,
PolicyColumns.MAX_HTML_TRUNCATION_SIZE, PolicyColumns.MAX_EMAIL_LOOKBACK,
PolicyColumns.MAX_CALENDAR_LOOKBACK, PolicyColumns.PASSWORD_RECOVERY_ENABLED
PolicyColumns.MAX_CALENDAR_LOOKBACK, PolicyColumns.PASSWORD_RECOVERY_ENABLED,
PolicyColumns.PROTOCOL_POLICIES_ENFORCED, PolicyColumns.PROTOCOL_POLICIES_UNSUPPORTED
};
public static final Policy NO_POLICY = new Policy();
@ -139,6 +142,24 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
Account.ID_PROJECTION_COLUMN, Account.NO_ACCOUNT);
}
public static ArrayList<String> addPolicyStringToList(String policyString,
ArrayList<String> policyList) {
if (policyString != null) {
int start = 0;
int len = policyString.length();
while(start < len) {
int end = policyString.indexOf(POLICY_STRING_DELIMITER, start);
if (end > start) {
policyList.add(policyString.substring(start, end));
start = end + 1;
} else {
break;
}
}
}
return policyList;
}
// We override this method to insure that we never write invalid policy data to the provider
@Override
public Uri save(Context context) {
@ -146,76 +167,6 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
return super.save(context);
}
public static void clearAccountPolicy(Context context, Account account) {
setAccountPolicy(context, account, null, null);
}
/**
* Convenience method for {@link #setAccountPolicy(Context, Account, Policy, String)}.
*/
@VisibleForTesting
public static void setAccountPolicy(Context context, long accountId, Policy policy,
String securitySyncKey) {
setAccountPolicy(context, Account.restoreAccountWithId(context, accountId),
policy, securitySyncKey);
}
/**
* Set the policy for an account atomically; this also removes any other policy associated with
* the account and sets the policy key for the account. If policy is null, the policyKey is
* set to 0 and the securitySyncKey to null. Also, update the account object to reflect the
* current policyKey and securitySyncKey
* @param context the caller's context
* @param account the account whose policy is to be set
* @param policy the policy to set, or null if we're clearing the policy
* @param securitySyncKey the security sync key for this account (ignored if policy is null)
*/
public static void setAccountPolicy(Context context, Account account, Policy policy,
String securitySyncKey) {
if (DEBUG_POLICY) {
Log.d(TAG, "Set policy for account " + account.mDisplayName + ": " +
((policy == null) ? "none" : policy.toString()));
}
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
// Make sure this is a valid policy set
if (policy != null) {
policy.normalize();
// Add the new policy (no account will yet reference this)
ops.add(ContentProviderOperation.newInsert(
Policy.CONTENT_URI).withValues(policy.toContentValues()).build());
// Make the policyKey of the account our newly created policy, and set the sync key
ops.add(ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
.withValueBackReference(AccountColumns.POLICY_KEY, 0)
.withValue(AccountColumns.SECURITY_SYNC_KEY, securitySyncKey)
.build());
} else {
ops.add(ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
.withValue(AccountColumns.SECURITY_SYNC_KEY, null)
.withValue(AccountColumns.POLICY_KEY, 0)
.build());
}
// Delete the previous policy associated with this account, if any
if (account.mPolicyKey > 0) {
ops.add(ContentProviderOperation.newDelete(
ContentUris.withAppendedId(
Policy.CONTENT_URI, account.mPolicyKey)).build());
}
try {
context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
account.refresh(context);
} catch (RemoteException e) {
// This is fatal to a remote process
throw new IllegalStateException("Exception setting account policy.");
} catch (OperationApplicationException e) {
// Can't happen; our provider doesn't throw this exception
}
}
/**
* Review all attachment records for this account, and reset the "don't allow download" flag
* as required by the account's new security policies
@ -286,6 +237,7 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
public boolean equals(Object other) {
if (!(other instanceof Policy)) return false;
Policy otherPolicy = (Policy)other;
// Policies here are enforced by the DPM
if (mRequireEncryption != otherPolicy.mRequireEncryption) return false;
if (mRequireEncryptionExternal != otherPolicy.mRequireEncryptionExternal) return false;
if (mRequireRemoteWipe != otherPolicy.mRequireRemoteWipe) return false;
@ -296,10 +248,13 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
if (mPasswordMaxFails != otherPolicy.mPasswordMaxFails) return false;
if (mPasswordMinLength != otherPolicy.mPasswordMinLength) return false;
if (mPasswordMode != otherPolicy.mPasswordMode) return false;
if (mDontAllowCamera != otherPolicy.mDontAllowCamera) return false;
// Policies here are enforced by the Exchange sync manager
// They should eventually be removed from Policy and replaced with some opaque data
if (mRequireManualSyncWhenRoaming != otherPolicy.mRequireManualSyncWhenRoaming) {
return false;
}
if (mDontAllowCamera != otherPolicy.mDontAllowCamera) return false;
if (mDontAllowAttachments != otherPolicy.mDontAllowAttachments) return false;
if (mDontAllowHtml != otherPolicy.mDontAllowHtml) return false;
if (mMaxAttachmentSize != otherPolicy.mMaxAttachmentSize) return false;
@ -308,6 +263,15 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
if (mMaxEmailLookback != otherPolicy.mMaxEmailLookback) return false;
if (mMaxCalendarLookback != otherPolicy.mMaxCalendarLookback) return false;
if (mPasswordRecoveryEnabled != otherPolicy.mPasswordRecoveryEnabled) return false;
if (!TextUtilities.stringOrNullEquals(mProtocolPoliciesEnforced,
otherPolicy.mProtocolPoliciesEnforced)) {
return false;
}
if (!TextUtilities.stringOrNullEquals(mProtocolPoliciesUnsupported,
otherPolicy.mProtocolPoliciesUnsupported)) {
return false;
}
return true;
}
@ -353,6 +317,9 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
mMaxEmailLookback = cursor.getInt(CONTENT_MAX_EMAIL_LOOKBACK_COLUMN);
mMaxCalendarLookback = cursor.getInt(CONTENT_MAX_CALENDAR_LOOKBACK_COLUMN);
mPasswordRecoveryEnabled = cursor.getInt(CONTENT_PASSWORD_RECOVERY_ENABLED_COLUMN) == 1;
mProtocolPoliciesEnforced = cursor.getString(CONTENT_PROTOCOL_POLICIES_ENFORCED_COLUMN);
mProtocolPoliciesUnsupported =
cursor.getString(CONTENT_PROTOCOL_POLICIES_UNSUPPORTED_COLUMN);
}
@Override
@ -378,6 +345,8 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
values.put(PolicyColumns.MAX_EMAIL_LOOKBACK, mMaxEmailLookback);
values.put(PolicyColumns.MAX_CALENDAR_LOOKBACK, mMaxCalendarLookback);
values.put(PolicyColumns.PASSWORD_RECOVERY_ENABLED, mPasswordRecoveryEnabled);
values.put(PolicyColumns.PROTOCOL_POLICIES_ENFORCED, mProtocolPoliciesEnforced);
values.put(PolicyColumns.PROTOCOL_POLICIES_UNSUPPORTED, mProtocolPoliciesUnsupported);
return values;
}
@ -508,6 +477,8 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
dest.writeInt(mMaxEmailLookback);
dest.writeInt(mMaxCalendarLookback);
dest.writeInt(mPasswordRecoveryEnabled ? 1 : 0);
dest.writeString(mProtocolPoliciesEnforced);
dest.writeString(mProtocolPoliciesUnsupported);
}
/**
@ -536,5 +507,7 @@ public final class Policy extends EmailContent implements EmailContent.PolicyCol
mMaxEmailLookback = in.readInt();
mMaxCalendarLookback = in.readInt();
mPasswordRecoveryEnabled = in.readInt() == 1;
mProtocolPoliciesEnforced = in.readString();
mProtocolPoliciesUnsupported = in.readString();
}
}

View File

@ -19,12 +19,7 @@ import com.android.emailcommon.provider.Policy;
interface IPolicyService {
boolean isActive(in Policy policies);
void policiesRequired(long accountId);
void policiesUpdated(long accountId);
void setAccountHoldFlag(long accountId, boolean newState);
boolean isActiveAdmin();
// This is about as oneway as you can get
void setAccountPolicy(long accountId, in Policy policy, String securityKey);
oneway void remoteWipe();
boolean isSupported(in Policy policies);
Policy clearUnsupportedPolicies(in Policy policies);
}

View File

@ -48,24 +48,6 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
return null;
}
@Override
public Policy clearUnsupportedPolicies(final Policy arg0) throws RemoteException {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mReturn = mService.clearUnsupportedPolicies(arg0);
}
}, "clearUnsupportedPolicies");
waitForCompletion();
if (DEBUG_PROXY) {
Log.v(TAG, "clearUnsupportedPolicies: " + ((mReturn == null) ? "null" : mReturn));
}
if (mReturn == null) {
throw new ServiceUnavailableException("clearUnsupportedPolicies");
} else {
return (Policy)mReturn;
}
}
@Override
public boolean isActive(final Policy arg0) throws RemoteException {
setTask(new ProxyTask() {
@ -85,48 +67,14 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
}
@Override
public boolean isActiveAdmin() throws RemoteException {
public void setAccountPolicy(final long accountId, final Policy policy,
final String securityKey) throws RemoteException {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mReturn = mService.isActiveAdmin();
mService.setAccountPolicy(accountId, policy, securityKey);
}
}, "isActiveAdmin");
}, "setAccountPolicy");
waitForCompletion();
if (DEBUG_PROXY) {
Log.v(TAG, "isActiveAdmin: " + ((mReturn == null) ? "null" : mReturn));
}
if (mReturn == null) {
throw new ServiceUnavailableException("isActiveAdmin");
} else {
return (Boolean)mReturn;
}
}
@Override
public boolean isSupported(final Policy arg0) throws RemoteException {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mReturn = mService.isSupported(arg0);
}
}, "isSupported");
waitForCompletion();
if (DEBUG_PROXY) {
Log.v(TAG, "isSupported: " + ((mReturn == null) ? "null" : mReturn));
}
if (mReturn == null) {
throw new ServiceUnavailableException("isSupported");
} else {
return (Boolean)mReturn;
}
}
@Override
public void policiesRequired(final long arg0) throws RemoteException {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mService.policiesRequired(arg0);
}
}, "policiesRequired");
}
@Override
@ -147,15 +95,6 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
}, "setAccountHoldFlag");
}
@Override
public void policiesUpdated(final long arg0) throws RemoteException {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mService.policiesUpdated(arg0);
}
}, "policiesUpdated");
}
// Static methods that encapsulate the proxy calls above
public static boolean isActive(Context context, Policy policies) {
try {
@ -165,22 +104,6 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
return false;
}
public static void policiesRequired(Context context, long accountId) {
try {
new PolicyServiceProxy(context).policiesRequired(accountId);
} catch (RemoteException e) {
throw new IllegalStateException("PolicyService transaction failed");
}
}
public static void policiesUpdated(Context context, long accountId) {
try {
new PolicyServiceProxy(context).policiesUpdated(accountId);
} catch (RemoteException e) {
throw new IllegalStateException("PolicyService transaction failed");
}
}
public static void setAccountHoldFlag(Context context, Account account, boolean newState) {
try {
new PolicyServiceProxy(context).setAccountHoldFlag(account.mId, newState);
@ -189,14 +112,6 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
}
}
public static boolean isActiveAdmin(Context context) {
try {
return new PolicyServiceProxy(context).isActiveAdmin();
} catch (RemoteException e) {
}
return false;
}
public static void remoteWipe(Context context) {
try {
new PolicyServiceProxy(context).remoteWipe();
@ -205,17 +120,11 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
}
}
public static boolean isSupported(Context context, Policy policy) {
public static void setAccountPolicy(Context context, long accountId, Policy policy,
String securityKey) {
try {
return new PolicyServiceProxy(context).isSupported(policy);
} catch (RemoteException e) {
}
return false;
}
public static Policy clearUnsupportedPolicies(Context context, Policy policy) {
try {
return new PolicyServiceProxy(context).clearUnsupportedPolicies(policy);
new PolicyServiceProxy(context).setAccountPolicy(accountId, policy, securityKey);
return;
} catch (RemoteException e) {
}
throw new IllegalStateException("PolicyService transaction failed");

View File

@ -714,4 +714,15 @@ public class TextUtilities {
return (CharSequence)sb;
}
/**
* Determine whether two Strings (either of which might be null) are the same; this is true
* when both are null or both are Strings that are equal.
*/
public static boolean stringOrNullEquals(String a, String b) {
if (a == null && b == null) return true;
if (a != null && b != null && a.equals(b)) return true;
return false;
}
}

View File

@ -856,14 +856,30 @@ as <xliff:g id="filename">%s</xliff:g>.</string>
provisioning, just before jumping into system settings such as Device Policy grant,
PIN/password, or encryption setup. [CHAR LIMIT=none] -->
<string name="account_security_dialog_content_fmt">
<xliff:g id="account">%s</xliff:g> requires that you update your security settings.</string>
<xliff:g id="account">%s</xliff:g> requires that you update your security
settings.</string>
<!-- Notification ticker when device security required (note: unused in Holo XL) -->
<string name="security_notification_ticker_fmt">
<string name="security_unsupported_ticker_fmt">
Account \"<xliff:g id="account">%s</xliff:g>\" cannot be synced due to security
requirements.</string>
<!-- Notification ticker when device security required (note: unused in Holo XL) -->
<string name="security_needed_ticker_fmt">
Account \"<xliff:g id="account">%s</xliff:g>\" requires security settings update.
</string>
<!-- Notification ticker when device security required (note: unused in Holo XL) -->
<string name="security_changed_ticker_fmt">
Account \"<xliff:g id="account">%s</xliff:g>\" changed its security settings; no user
action is required.
</string>
<!-- Notification content title when device security required [CHAR_LIMIT=30] -->
<string name="security_notification_content_title">Security update required</string>
<string name="security_notification_content_update_title">Security update required</string>
<!-- Notification content title when device security policies have changed [CHAR_LIMIT=30] -->
<string name="security_notification_content_change_title">Security policies have
changed</string>
<!-- Notification content title when device security policies cannot be met [CHAR_LIMIT=30] -->
<string name="security_notification_content_unsupported_title">Security policies cannot be
met</string>
<!-- Title of the activity that dispatches changes to device security. Not normally seen. -->
<string name="account_security_title">Device security</string>
<!-- Additional diagnostic text when the email app asserts control of the phone.
@ -937,15 +953,27 @@ as <xliff:g id="filename">%s</xliff:g>.</string>
<string name="account_settings_mail_check_frequency_label">Inbox check frequency</string>
<!-- On Settings screen, setting option name -->
<string name="account_settings_incoming_label">Incoming settings</string>
<!-- On Settings screen, setting option summary [CHAR LIMIT=64] -->
<!-- On Settings screen, setting option summary [CHAR LIMIT=64] -->
<string name="account_settings_incoming_summary">
Username, password, and other incoming server settings</string>
<!-- On Settings screen, setting option name -->
<string name="account_settings_outgoing_label">Outgoing settings</string>
<!-- On Settings screen, setting option summary [CHAR LIMIT=64] -->
<!-- On Settings screen, setting option summary [CHAR LIMIT=64] -->
<string name="account_settings_outgoing_summary">
Username, password, and other outgoing server settings</string>
<!-- On Settings screen, setting option name -->
<string name="account_settings_enforced_label">Policies enforced</string>
<!-- On Settings screen, setting option summary [CHAR LIMIT=64] -->
<string name="account_settings_enforced_summary">None</string>
<!-- On Settings screen, setting option name -->
<string name="account_settings_unsupported_label">Unsupported policies</string>
<!-- On Settings screen, setting option summary [CHAR LIMIT=64] -->
<string name="account_settings_unsupported_summary">None</string>
<!-- On Settings screen, label for button that attempts to sync the account -->
<string name="account_settings_retry_label">Attempt sync</string>
<!-- On Settings screen, summmary for button that attempts to sync an account [CHAR LIMIT=64] -->
<string name="account_settings_retry_summary">Tap here to attempt to sync this account (i.e. if server settings have changed)</string>
<!-- On Settings screen, setting option name -->
<string name="account_settings_description_label">Account name</string>
<!-- On Settings screen, setting option name -->
<string name="account_settings_name_label">Your name</string>
@ -963,6 +991,8 @@ as <xliff:g id="filename">%s</xliff:g>.</string>
<string name="account_settings_notifications">Notification settings</string>
<!-- On Settings screen, section heading for data usage [CHAR LIMIT=70] -->
<string name="account_settings_data_usage">Data usage</string>
<!-- On Settings screen, section heading -->
<string name="account_settings_policies">Security policies</string>
<!-- On settings screen, dialog heading informing user to edit a quick response -->
<string name="edit_quick_response_dialog">Edit quick response</string>
@ -1215,4 +1245,17 @@ as <xliff:g id="filename">%s</xliff:g>.</string>
<!-- A long placeholder string to be used in template XML files message_list_item_*.xml.
Used only in layout computation, and never actually exposed to the user. -->
<string name="long_string" translatable="false">looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong</string>
<!-- A policy disallowing the user of the device's camera [CHAR LIMIT=40] -->
<string name="policy_dont_allow_camera">Disallow use of the device\'s camera</string>
<!-- A policy requiring a device lock screen password [CHAR LIMIT=40] -->
<string name="policy_require_password">Require device password</string>
<!-- A policy disallowing the reuse of recent passwords [CHAR LIMIT=40] -->
<string name="policy_password_history">Restrict the reuse of recent passwords</string>
<!-- A policy that forces a password to expire after a set period of time [CHAR LIMIT=40] -->
<string name="policy_password_expiration">Require passwords to expire</string>
<!-- A policy requiring a maximum amount of time the device can sit idle before the lock screen
is activated [CHAR LIMIT=40] -->
<string name="policy_screen_timeout">Require an idle device to lock its screen</string>
</resources>

20
res/xml/account_settings_preferences.xml Normal file → Executable file
View File

@ -135,6 +135,26 @@
android:summary="@string/account_settings_outgoing_summary" />
</PreferenceCategory>
<PreferenceCategory
android:key="account_policies"
android:title="@string/account_settings_policies">
<com.android.email.activity.setup.PolicyListPreference
android:key="policies_enforced"
android:title="@string/account_settings_enforced_label"
android:summary="@string/account_settings_enforced_summary" />
<com.android.email.activity.setup.PolicyListPreference
android:key="policies_unsupported"
android:title="@string/account_settings_unsupported_label"
android:summary="@string/account_settings_unsupported_summary" />
<Preference
android:key="policies_retry_account"
android:title="@string/account_settings_retry_label"
android:summary="@string/account_settings_retry_summary" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/account_settings_category_delete_account">

View File

@ -52,6 +52,7 @@ import com.android.emailcommon.provider.EmailContent.MailboxColumns;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.EmailContent.MessageColumns;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.utility.EmailAsyncTask;
import com.android.emailcommon.utility.Utility;
import com.google.common.annotations.VisibleForTesting;
@ -62,7 +63,6 @@ import java.util.HashSet;
* Class that manages notifications.
*/
public class NotificationController {
private static final int NOTIFICATION_ID_SECURITY_NEEDED = 1;
/** Reserved for {@link com.android.exchange.CalendarSyncEnabler} */
@SuppressWarnings("unused")
private static final int NOTIFICATION_ID_EXCHANGE_CALENDAR_ADDED = 2;
@ -70,8 +70,11 @@ public class NotificationController {
private static final int NOTIFICATION_ID_PASSWORD_EXPIRING = 4;
private static final int NOTIFICATION_ID_PASSWORD_EXPIRED = 5;
private static final int NOTIFICATION_ID_BASE_MASK = 0xF0000000;
private static final int NOTIFICATION_ID_BASE_NEW_MESSAGES = 0x10000000;
private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000;
private static final int NOTIFICATION_ID_BASE_SECURITY_NEEDED = 0x30000000;
private static final int NOTIFICATION_ID_BASE_SECURITY_CHANGED = 0x40000000;
/** Selection to retrieve accounts that should we notify user for changes */
private final static String NOTIFIED_ACCOUNT_SELECTION =
@ -144,7 +147,7 @@ public class NotificationController {
private boolean needsOngoingNotification(int notificationId) {
// "Security needed" must be ongoing so that the user doesn't close it; otherwise, sync will
// be prevented until a reboot. Consider also doing this for password expired.
return notificationId == NOTIFICATION_ID_SECURITY_NEEDED;
return (notificationId & NOTIFICATION_ID_BASE_MASK) == NOTIFICATION_ID_BASE_SECURITY_NEEDED;
}
/**
@ -591,24 +594,67 @@ public class NotificationController {
}
/**
* Show (or update) a security needed notification. The given account is used to update
* the display text, but, all accounts share the same notification ID.
* Show (or update) a security needed notification. If tapped, the user is taken to a
* dialog asking whether he wants to update his settings.
*/
public void showSecurityNeededNotification(Account account) {
Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, account.mId, true);
String accountName = account.getDisplayName();
String ticker =
mContext.getString(R.string.security_notification_ticker_fmt, accountName);
String title = mContext.getString(R.string.security_notification_content_title);
mContext.getString(R.string.security_needed_ticker_fmt, accountName);
String title = mContext.getString(R.string.security_notification_content_update_title);
showAccountNotification(account, ticker, title, accountName, intent,
NOTIFICATION_ID_SECURITY_NEEDED);
(int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
}
/**
* Cancels the security needed notification.
* Show (or update) a security changed notification. If tapped, the user is taken to the
* account settings screen where he can view the list of enforced policies
*/
public void showSecurityChangedNotification(Account account) {
Intent intent = AccountSettings.createAccountSettingsIntent(mContext, account.mId, null);
String accountName = account.getDisplayName();
String ticker =
mContext.getString(R.string.security_changed_ticker_fmt, accountName);
String title = mContext.getString(R.string.security_notification_content_change_title);
showAccountNotification(account, ticker, title, accountName, intent,
(int)(NOTIFICATION_ID_BASE_SECURITY_CHANGED + account.mId));
}
/**
* Show (or update) a security unsupported notification. If tapped, the user is taken to the
* account settings screen where he can view the list of unsupported policies
*/
public void showSecurityUnsupportedNotification(Account account) {
Intent intent = AccountSettings.createAccountSettingsIntent(mContext, account.mId, null);
String accountName = account.getDisplayName();
String ticker =
mContext.getString(R.string.security_unsupported_ticker_fmt, accountName);
String title = mContext.getString(R.string.security_notification_content_unsupported_title);
showAccountNotification(account, ticker, title, accountName, intent,
(int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
}
/**
* Cancels all security needed notifications.
*/
public void cancelSecurityNeededNotification() {
mNotificationManager.cancel(NOTIFICATION_ID_SECURITY_NEEDED);
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
Account.ID_PROJECTION, null, null, null);
try {
while (c.moveToNext()) {
long id = c.getLong(Account.ID_PROJECTION_COLUMN);
mNotificationManager.cancel(
(int)(NOTIFICATION_ID_BASE_SECURITY_NEEDED + id));
}
}
finally {
c.close();
}
}});
}
/**

View File

@ -20,11 +20,15 @@ import android.app.admin.DeviceAdminInfo;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import com.android.email.service.EmailBroadcastProcessorService;
@ -34,9 +38,12 @@ import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.PolicyColumns;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.utility.TextUtilities;
import com.android.emailcommon.utility.Utility;
import com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
/**
* Utility functions to support reading and writing security policies, and handshaking the device
* into and out of various security states.
@ -204,11 +211,12 @@ public class SecurityPolicy {
}
/**
* API: Report that policies may have been updated due to rewriting values in an Account.
* @param accountId the account that has been updated, -1 if unknown/deleted
* API: Report that policies may have been updated due to rewriting values in an Account; we
* clear the aggregate policy (so it can be recomputed) and set the policies in the DPM
*/
public synchronized void policiesUpdated(long accountId) {
public synchronized void policiesUpdated() {
mAggregatePolicy = null;
setActivePolicies();
}
/**
@ -221,58 +229,7 @@ public class SecurityPolicy {
if (Email.DEBUG) {
Log.d(TAG, "reducePolicies");
}
policiesUpdated(-1);
setActivePolicies();
}
/**
* API: Query if the proposed set of policies are supported on the device.
*
* @param policy the polices that were requested
* @return boolean if supported
*/
public boolean isSupported(Policy policy) {
// 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 (policy.mRequireEncryption) {
int encryptionStatus = getDPM().getStorageEncryptionStatus();
if (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) {
return false;
}
}
// If we ever support devices that can't disable cameras for any reason, we should
// indicate as such in the mDontAllowCamera policy
return true;
}
/**
* 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 policy the polices that were requested
* @return the same PolicySet if all are supported; A replacement PolicySet if any
* unsupported policies were removed
*/
public Policy clearUnsupportedPolicies(Policy policy) {
// 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 (policy.mRequireEncryption) {
int encryptionStatus = getDPM().getStorageEncryptionStatus();
if (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) {
policy.mRequireEncryption = false;
}
}
// If we ever support devices that can't disable cameras for any reason, we should
// clear the mDontAllowCamera policy
return policy;
policiesUpdated();
}
/**
@ -303,6 +260,9 @@ public class SecurityPolicy {
if ((reasons & INACTIVE_NEED_ENCRYPTION) != 0) {
sb.append("encryption ");
}
if ((reasons & INACTIVE_PROTOCOL_POLICIES) != 0) {
sb.append("protocol ");
}
Log.d(TAG, sb.toString());
}
return reasons == 0;
@ -328,6 +288,11 @@ public class SecurityPolicy {
*/
public final static int INACTIVE_NEED_ENCRYPTION = 8;
/**
* Return bits from isActive: Protocol-specific policies cannot be enforced
*/
public final static int INACTIVE_PROTOCOL_POLICIES = 16;
/**
* API: Query used to determine if a given policy is "active" (the device is operating at
* the required security level).
@ -418,6 +383,10 @@ public class SecurityPolicy {
// password failures are counted locally - no test required here
// no check required for remote wipe (it's supported, if we're the admin)
if (policy.mProtocolPoliciesUnsupported != null) {
reasons |= INACTIVE_PROTOCOL_POLICIES;
}
// If we made it all the way, reasons == 0 here. Otherwise it's a list of grievances.
return reasons;
}
@ -514,24 +483,122 @@ public class SecurityPolicy {
Account account = Account.restoreAccountWithId(mContext, accountId);
// In case the account has been deleted, just return
if (account == null) return;
if (account.mPolicyKey == 0) return;
Policy policy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
if (policy == null) return;
if (Email.DEBUG) {
if (account.mPolicyKey == 0) {
Log.d(TAG, "policiesRequired for " + account.mDisplayName + ": none");
} else {
Policy policy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
if (policy == null) {
Log.w(TAG, "No policy??");
} else {
Log.d(TAG, "policiesRequired for " + account.mDisplayName + ": " + policy);
}
}
Log.d(TAG, "policiesRequired for " + account.mDisplayName + ": " + policy);
}
// Mark the account as "on hold".
setAccountHoldFlag(mContext, account, true);
// Put up a notification
NotificationController.getInstance(mContext).showSecurityNeededNotification(account);
// Put up an appropriate notification
if (policy.mProtocolPoliciesUnsupported == null) {
NotificationController.getInstance(mContext).showSecurityNeededNotification(account);
} else {
NotificationController.getInstance(mContext).showSecurityUnsupportedNotification(
account);
}
}
public static void clearAccountPolicy(Context context, Account account) {
setAccountPolicy(context, account, null, null);
}
/**
* Set the policy for an account atomically; this also removes any other policy associated with
* the account and sets the policy key for the account. If policy is null, the policyKey is
* set to 0 and the securitySyncKey to null. Also, update the account object to reflect the
* current policyKey and securitySyncKey
* @param context the caller's context
* @param account the account whose policy is to be set
* @param policy the policy to set, or null if we're clearing the policy
* @param securitySyncKey the security sync key for this account (ignored if policy is null)
*/
public static void setAccountPolicy(Context context, Account account, Policy policy,
String securitySyncKey) {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
// Make sure this is a valid policy set
if (policy != null) {
policy.normalize();
// Add the new policy (no account will yet reference this)
ops.add(ContentProviderOperation.newInsert(
Policy.CONTENT_URI).withValues(policy.toContentValues()).build());
// Make the policyKey of the account our newly created policy, and set the sync key
ops.add(ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
.withValueBackReference(AccountColumns.POLICY_KEY, 0)
.withValue(AccountColumns.SECURITY_SYNC_KEY, securitySyncKey)
.build());
} else {
ops.add(ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
.withValue(AccountColumns.SECURITY_SYNC_KEY, null)
.withValue(AccountColumns.POLICY_KEY, 0)
.build());
}
// Delete the previous policy associated with this account, if any
if (account.mPolicyKey > 0) {
ops.add(ContentProviderOperation.newDelete(
ContentUris.withAppendedId(
Policy.CONTENT_URI, account.mPolicyKey)).build());
}
try {
context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
account.refresh(context);
} catch (RemoteException e) {
// This is fatal to a remote process
throw new IllegalStateException("Exception setting account policy.");
} catch (OperationApplicationException e) {
// Can't happen; our provider doesn't throw this exception
}
}
public void setAccountPolicy(long accountId, Policy policy, String securityKey) {
Account account = Account.restoreAccountWithId(mContext, accountId);
Policy oldPolicy = null;
if (account.mPolicyKey > 0) {
oldPolicy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
}
boolean policyChanged = !oldPolicy.equals(policy);
if (!policyChanged && (TextUtilities.stringOrNullEquals(securityKey,
account.mSecuritySyncKey))) {
Log.d(Logging.LOG_TAG, "setAccountPolicy; policy unchanged");
} else {
setAccountPolicy(mContext, account, policy, securityKey);
policiesUpdated();
}
boolean setHold = false;
if (policy.mProtocolPoliciesUnsupported != null) {
// We can't support this, reasons in unsupportedRemotePolicies
Log.d(Logging.LOG_TAG,
"Notify policies for " + account.mDisplayName + " not supported.");
setHold = true;
NotificationController.getInstance(mContext).showSecurityUnsupportedNotification(
account);
// Erase data
Controller.getInstance(mContext).deleteSyncedDataSync(accountId);
} else if (isActive(policy)) {
if (policyChanged) {
Log.d(Logging.LOG_TAG, "Notify policies for " + account.mDisplayName + " changed.");
// Notify that policies changed
NotificationController.getInstance(mContext).showSecurityChangedNotification(
account);
}
} else {
setHold = true;
Log.d(Logging.LOG_TAG, "Notify policies for " + account.mDisplayName +
" are not being enforced.");
// Put up a notification
NotificationController.getInstance(mContext).showSecurityNeededNotification(account);
}
// Set/clear the account hold.
setAccountHoldFlag(mContext, account, setHold);
}
/**
@ -600,7 +667,7 @@ public class SecurityPolicy {
} finally {
c.close();
}
policiesUpdated(-1);
policiesUpdated();
}
/**

View File

@ -41,6 +41,7 @@ import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.utility.EmailAsyncTask;
import com.android.emailcommon.utility.IntentUtilities;
import com.android.emailcommon.utility.Utility;
@ -414,7 +415,19 @@ public class Welcome extends Activity {
@Override
public void onAccountSecurityHold(long accountId) {
cleanUp();
// If we can't find the account, we know what to do
Account account = Account.restoreAccountWithId(Welcome.this, accountId);
if (account == null) {
onAccountNotFound();
return;
}
// If there's no policy or it's "unsupported", act like the account doesn't exist
Policy policy = Policy.restorePolicyWithId(Welcome.this, account.mPolicyKey);
if (policy == null || (policy.mProtocolPoliciesUnsupported != null)) {
onAccountNotFound();
return;
}
// Otherwise, try advancing security
ActivityHelper.showSecurityHoldDialog(Welcome.this, accountId);
finish();
}

View File

@ -469,8 +469,10 @@ public class AccountCheckSettingsFragment extends Fragment {
EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET));
return new MessagingException(resultCode, mStoreHost);
} else if (resultCode == MessagingException.SECURITY_POLICIES_UNSUPPORTED) {
String[] data = bundle.getStringArray(
EmailServiceProxy.VALIDATE_BUNDLE_UNSUPPORTED_POLICIES);
Policy policy = (Policy)bundle.getParcelable(
EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET);
String unsupported = policy.mProtocolPoliciesUnsupported;
String[] data = unsupported.split("" + Policy.POLICY_STRING_DELIMITER);
return new MessagingException(resultCode, mStoreHost, data);
} else if (resultCode != MessagingException.NO_ERROR) {
String errorMessage =

View File

@ -119,6 +119,7 @@ public class AccountSecurity extends Activity {
finish();
return;
}
// Special handling for password expiration events
if (passwordExpiring || passwordExpired) {
FragmentManager fm = getFragmentManager();

View File

@ -27,6 +27,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Vibrator;
@ -43,6 +44,7 @@ import android.util.Log;
import com.android.email.Email;
import com.android.email.R;
import com.android.email.SecurityPolicy;
import com.android.email.mail.Sender;
import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.CalendarProviderStub;
@ -51,8 +53,11 @@ import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.utility.Utility;
import java.util.ArrayList;
/**
* Fragment containing the main logic for account settings. This also calls out to other
* fragments for server settings.
@ -81,6 +86,10 @@ public class AccountSettingsFragment extends PreferenceFragment {
private static final String PREFERENCE_VIBRATE_WHEN = "account_settings_vibrate_when";
private static final String PREFERENCE_RINGTONE = "account_ringtone";
private static final String PREFERENCE_CATEGORY_SERVER = "account_servers";
private static final String PREFERENCE_CATEGORY_POLICIES = "account_policies";
private static final String PREFERENCE_POLICIES_ENFORCED = "policies_enforced";
private static final String PREFERENCE_POLICIES_UNSUPPORTED = "policies_unsupported";
private static final String PREFERENCE_POLICIES_RETRY_ACCOUNT = "policies_retry_account";
private static final String PREFERENCE_INCOMING = "incoming";
private static final String PREFERENCE_OUTGOING = "outgoing";
private static final String PREFERENCE_SYNC_CONTACTS = "account_sync_contacts";
@ -351,6 +360,46 @@ public class AccountSettingsFragment extends PreferenceFragment {
}
}
/**
* From a Policy, create and return an ArrayList of Strings that describe (simply) those
* policies that are supported by the OS. At the moment, the strings are simple (e.g.
* "password required"); we should probably add more information (# characters, etc.), though
*/
private ArrayList<String> getSystemPoliciesList(Policy policy) {
Resources res = mContext.getResources();
ArrayList<String> policies = new ArrayList<String>();
if (policy.mPasswordMode != Policy.PASSWORD_MODE_NONE) {
policies.add(res.getString(R.string.policy_require_password));
}
if (policy.mPasswordHistory > 0) {
policies.add(res.getString(R.string.policy_password_history));
}
if (policy.mPasswordExpirationDays > 0) {
policies.add(res.getString(R.string.policy_password_expiration));
}
if (policy.mMaxScreenLockTime > 0) {
policies.add(res.getString(R.string.policy_screen_timeout));
}
if (policy.mDontAllowCamera) {
policies.add(res.getString(R.string.policy_dont_allow_camera));
}
return policies;
}
private void setPolicyListSummary(ArrayList<String> policies, String policiesToAdd,
String preferenceName) {
Policy.addPolicyStringToList(policiesToAdd, policies);
if (policies.size() > 0) {
Preference p = findPreference(preferenceName);
StringBuilder sb = new StringBuilder();
for (String desc: policies) {
sb.append(desc);
sb.append('\n');
}
p.setSummary(sb.toString());
}
}
/**
* Load account data into preference UI
*/
@ -397,7 +446,6 @@ public class AccountSettingsFragment extends PreferenceFragment {
});
mAccountSignature = (EditTextPreference) findPreference(PREFERENCE_SIGNATURE);
String signature = mAccount.getSignature();
mAccountSignature.setText(mAccount.getSignature());
mAccountSignature.setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
@ -520,6 +568,45 @@ public class AccountSettingsFragment extends PreferenceFragment {
notificationsCategory.removePreference(mAccountVibrateWhen);
}
final Preference retryAccount = findPreference(PREFERENCE_POLICIES_RETRY_ACCOUNT);
final PreferenceCategory policiesCategory = (PreferenceCategory) findPreference(
PREFERENCE_CATEGORY_POLICIES);
if (mAccount.mPolicyKey > 0) {
// Make sure we have most recent data from account
mAccount.refresh(mContext);
Policy policy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey);
if (policy == null) {
// The account has been deleted? Crazy, but not impossible
return;
}
if (policy.mProtocolPoliciesEnforced != null) {
ArrayList<String> policies = getSystemPoliciesList(policy);
setPolicyListSummary(policies, policy.mProtocolPoliciesEnforced,
PREFERENCE_POLICIES_ENFORCED);
}
if (policy.mProtocolPoliciesUnsupported != null) {
ArrayList<String> policies = new ArrayList<String>();
setPolicyListSummary(policies, policy.mProtocolPoliciesUnsupported,
PREFERENCE_POLICIES_UNSUPPORTED);
} else {
// Don't show "retry" unless we have unsupported policies
policiesCategory.removePreference(retryAccount);
}
} else {
// Remove the category completely if there are no policies
getPreferenceScreen().removePreference(policiesCategory);
}
retryAccount.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
// Release the account
SecurityPolicy.setAccountHoldFlag(mContext, mAccount, false);
// Remove the preference
policiesCategory.removePreference(retryAccount);
return true;
}
});
findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2011 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.activity.setup;
import android.content.Context;
import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
/**
* Simple text preference allowing a large number of lines
*/
public class PolicyListPreference extends Preference {
// Arbitrary, but large number (we don't, and won't, have nearly this many)
public static final int MAX_POLICIES = 24;
public PolicyListPreference(Context ctx, AttributeSet attrs, int defStyle) {
super(ctx, attrs, defStyle);
}
public PolicyListPreference(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
((TextView)view.findViewById(android.R.id.summary)).setMaxLines(MAX_POLICIES);
}
}

View File

@ -160,8 +160,9 @@ public class EmailProvider extends ContentProvider {
// Version 26: Update IMAP accounts to add FLAG_SUPPORTS_SEARCH flag
// Version 27: Add protocolSearchInfo to Message table
// Version 28: Add notifiedMessageId and notifiedMessageCount to Account
// Version 29: Add protocolPoliciesEnforced and protocolPoliciesUnsupported to Policy
public static final int DATABASE_VERSION = 28;
public static final int DATABASE_VERSION = 29;
// Any changes to the database format *must* include update-in-place code.
// Original version: 2
@ -651,7 +652,9 @@ public class EmailProvider extends ContentProvider {
+ PolicyColumns.MAX_HTML_TRUNCATION_SIZE + " integer, "
+ PolicyColumns.MAX_EMAIL_LOOKBACK + " integer, "
+ PolicyColumns.MAX_CALENDAR_LOOKBACK + " integer, "
+ PolicyColumns.PASSWORD_RECOVERY_ENABLED + " integer"
+ PolicyColumns.PASSWORD_RECOVERY_ENABLED + " integer, "
+ PolicyColumns.PROTOCOL_POLICIES_ENFORCED + " text, "
+ PolicyColumns.PROTOCOL_POLICIES_UNSUPPORTED + " text"
+ ");";
db.execSQL("create table " + Policy.TABLE_NAME + s);
}
@ -1341,10 +1344,22 @@ public class EmailProvider extends ContentProvider {
+ " add column " + Account.NOTIFIED_MESSAGE_COUNT + " integer;");
} catch (SQLException e) {
// Shouldn't be needed unless we're debugging and interrupt the process
Log.w(TAG, "Exception upgrading EmailProvider.db from 27 to 27 " + e);
Log.w(TAG, "Exception upgrading EmailProvider.db from 27 to 28 " + e);
}
oldVersion = 28;
}
if (oldVersion == 28) {
try {
db.execSQL("alter table " + Policy.TABLE_NAME
+ " add column " + Policy.PROTOCOL_POLICIES_ENFORCED + " text;");
db.execSQL("alter table " + Policy.TABLE_NAME
+ " add column " + Policy.PROTOCOL_POLICIES_UNSUPPORTED + " text;");
} catch (SQLException e) {
// Shouldn't be needed unless we're debugging and interrupt the process
Log.w(TAG, "Exception upgrading EmailProvider.db from 28 to 29 " + e);
}
oldVersion = 29;
}
}
@Override

View File

@ -16,15 +16,15 @@
package com.android.email.service;
import com.android.email.SecurityPolicy;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.IPolicyService;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import com.android.email.SecurityPolicy;
import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.IPolicyService;
public class PolicyService extends Service {
private SecurityPolicy mSecurityPolicy;
@ -35,32 +35,16 @@ public class PolicyService extends Service {
return mSecurityPolicy.isActive(policy);
}
public void policiesRequired(long accountId) {
mSecurityPolicy.policiesRequired(accountId);
}
public void policiesUpdated(long accountId) {
mSecurityPolicy.policiesUpdated(accountId);
}
public void setAccountHoldFlag(long accountId, boolean newState) {
SecurityPolicy.setAccountHoldFlag(mContext, accountId, newState);
}
public boolean isActiveAdmin() {
return mSecurityPolicy.isActiveAdmin();
}
public void remoteWipe() {
mSecurityPolicy.remoteWipe();
}
public boolean isSupported(Policy policy) {
return mSecurityPolicy.isSupported(policy);
}
public Policy clearUnsupportedPolicies(Policy policy) {
return mSecurityPolicy.clearUnsupportedPolicies(policy);
public void setAccountPolicy(long accountId, Policy policy, String securityKey) {
mSecurityPolicy.setAccountPolicy(accountId, policy, securityKey);
}
};

62
tests/src/com/android/email/SecurityPolicyTests.java Normal file → Executable file
View File

@ -142,7 +142,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
Account a3 = ProviderTestUtils.setupAccount("sec-3", true, mMockContext);
Policy p3ain = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0,
false, false);
Policy.setAccountPolicy(mMockContext, a3, p3ain, null);
SecurityPolicy.setAccountPolicy(mMockContext, a3, p3ain, null);
Policy p3aout = mSecurityPolicy.computeAggregatePolicy();
assertNotNull(p3aout);
assertEquals(p3ain, p3aout);
@ -150,7 +150,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
// Repeat that test with fully-populated policies
Policy p3bin = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 15, 16, false, 6, 2, 3,
false, false);
Policy.setAccountPolicy(mMockContext, a3, p3bin, null);
SecurityPolicy.setAccountPolicy(mMockContext, a3, p3bin, null);
Policy p3bout = mSecurityPolicy.computeAggregatePolicy();
assertNotNull(p3bout);
assertEquals(p3bin, p3bout);
@ -166,7 +166,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
Policy p4in = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 0, 5, 7,
false, true);
Account a4 = ProviderTestUtils.setupAccount("sec-4", true, mMockContext);
Policy.setAccountPolicy(mMockContext, a4, p4in, null);
SecurityPolicy.setAccountPolicy(mMockContext, a4, p4in, null);
Policy p4out = mSecurityPolicy.computeAggregatePolicy();
assertNotNull(p4out);
assertEquals(20, p4out.mPasswordMinLength);
@ -192,7 +192,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
Policy p5in = setupPolicy(4, Policy.PASSWORD_MODE_SIMPLE, 5, 6, true, 1, 0, 0,
true, false);
Account a5 = ProviderTestUtils.setupAccount("sec-5", true, mMockContext);
Policy.setAccountPolicy(mMockContext, a5, p5in, null);
SecurityPolicy.setAccountPolicy(mMockContext, a5, p5in, null);
Policy p5out = mSecurityPolicy.computeAggregatePolicy();
assertNotNull(p5out);
assertEquals(20, p5out.mPasswordMinLength);
@ -236,17 +236,17 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
long accountId = account.mId;
Policy initial = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0,
false, false);
Policy.setAccountPolicy(mMockContext, accountId, initial, null);
SecurityPolicy.setAccountPolicy(mMockContext, account, initial, null);
long oldKey = assertAccountPolicyConsistent(account.mId, 0);
Policy updated = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0,
false, false);
Policy.setAccountPolicy(mMockContext, accountId, updated, null);
SecurityPolicy.setAccountPolicy(mMockContext, account, updated, null);
oldKey = assertAccountPolicyConsistent(account.mId, oldKey);
// Remove the policy
Policy.clearAccountPolicy(
SecurityPolicy.clearAccountPolicy(
mMockContext, Account.restoreAccountWithId(mMockContext, accountId));
assertNull("old policy not cleaned up",
Policy.restorePolicyWithId(mMockContext, oldKey));
@ -306,15 +306,15 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
Account a1 = ProviderTestUtils.setupAccount("disable-1", true, mMockContext);
Policy p1 = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0,
false, false);
Policy.setAccountPolicy(mMockContext, a1, p1, "security-sync-key-1");
SecurityPolicy.setAccountPolicy(mMockContext, a1, p1, "security-sync-key-1");
Account a2 = ProviderTestUtils.setupAccount("disable-2", true, mMockContext);
Policy p2 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 0, 0, 0,
false, false);
Policy.setAccountPolicy(mMockContext, a2, p2, "security-sync-key-2");
SecurityPolicy.setAccountPolicy(mMockContext, a2, p2, "security-sync-key-2");
Account a3 = ProviderTestUtils.setupAccount("disable-3", true, mMockContext);
Policy.clearAccountPolicy(mMockContext, a3);
SecurityPolicy.clearAccountPolicy(mMockContext, a3);
mSecurityPolicy = SecurityPolicy.getInstance(mMockContext);
@ -359,7 +359,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
ProviderTestUtils.setupAccount("expiring-2", true, mMockContext);
Policy p2 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 30, 0, 0,
false, true);
Policy.setAccountPolicy(mMockContext, a2, p2, null);
SecurityPolicy.setAccountPolicy(mMockContext, a2, p2, null);
// The expiring account should be returned
nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext);
@ -369,7 +369,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
Account a3 = ProviderTestUtils.setupAccount("expiring-3", true, mMockContext);
Policy p3 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 60, 0, 0,
false, true);
Policy.setAccountPolicy(mMockContext, a3, p3, null);
SecurityPolicy.setAccountPolicy(mMockContext, a3, p3, null);
// The original expiring account (a2) should be returned
nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext);
@ -379,7 +379,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
Account a4 = ProviderTestUtils.setupAccount("expiring-4", true, mMockContext);
Policy p4 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 15, 0, 0,
false, true);
Policy.setAccountPolicy(mMockContext, a4, p4, null);
SecurityPolicy.setAccountPolicy(mMockContext, a4, p4, null);
// The new expiring account (a4) should be returned
nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext);
@ -409,7 +409,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
Account a2 = ProviderTestUtils.setupAccount("expired-2", true, mMockContext);
Policy p2 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 0, 0, 0,
false, true);
Policy.setAccountPolicy(mMockContext, a2, p2, null);
SecurityPolicy.setAccountPolicy(mMockContext, a2, p2, null);
// Add a mailbox & messages to each account
long account1Id = a1.mId;
@ -435,7 +435,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
Account a3 = ProviderTestUtils.setupAccount("expired-3", true, mMockContext);
Policy p3 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 30, 0, 0,
false, true);
Policy.setAccountPolicy(mMockContext, a3, p3, null);
SecurityPolicy.setAccountPolicy(mMockContext, a3, p3, null);
// Add mailbox & messages to 3rd account
long account3Id = a3.mId;
@ -466,38 +466,6 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
assertEquals(Account.FLAGS_SECURITY_HOLD, account.mFlags & Account.FLAGS_SECURITY_HOLD);
}
/**
* Test the code that clears unsupported policies
* TODO inject a mock DPM so we can directly control & test all cases, no matter what device
*/
public void testClearUnsupportedPolicies() {
Policy p1 =
setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, false);
Policy p2 =
setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, true, false);
mSecurityPolicy = SecurityPolicy.getInstance(mMockContext);
DevicePolicyManager dpm = mSecurityPolicy.getDPM();
boolean hasEncryption =
dpm.getStorageEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
Policy p1Result = mSecurityPolicy.clearUnsupportedPolicies(p1);
Policy p2Result = mSecurityPolicy.clearUnsupportedPolicies(p2);
// No changes expected when encryptionRequested was false
assertEquals(p1, p1Result);
if (hasEncryption) {
// No changes expected
assertEquals(p2, p2Result);
} else {
// If encryption is unsupported, encryption policy bits are cleared
Policy policyExpect =
setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false,
false);
assertEquals(policyExpect, p2Result);
}
}
/**
* Test the code that converts from exchange-style quality to DPM/Lockscreen style quality.
*/

22
tests/src/com/android/email/provider/PolicyTests.java Normal file → Executable file
View File

@ -16,6 +16,12 @@
package com.android.email.provider;
import android.content.Context;
import android.os.Parcel;
import android.test.ProviderTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
import com.android.email.SecurityPolicy;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Attachment;
@ -24,11 +30,6 @@ import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.provider.Policy;
import android.content.Context;
import android.os.Parcel;
import android.test.ProviderTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
import java.util.ArrayList;
/**
@ -68,10 +69,10 @@ public class PolicyTests extends ProviderTestCase2<EmailProvider> {
// Setup two accounts with policies
Account account1 = ProviderTestUtils.setupAccount("acct1", true, mMockContext);
Policy policy1 = new Policy();
Policy.setAccountPolicy(mMockContext, account1, policy1, securitySyncKey);
SecurityPolicy.setAccountPolicy(mMockContext, account1, policy1, securitySyncKey);
Account account2 = ProviderTestUtils.setupAccount("acct2", true, mMockContext);
Policy policy2 = new Policy();
Policy.setAccountPolicy(mMockContext, account2, policy2, securitySyncKey);
SecurityPolicy.setAccountPolicy(mMockContext, account2, policy2, securitySyncKey);
// Get the accounts back from the database
account1.refresh(mMockContext);
account2.refresh(mMockContext);
@ -92,7 +93,7 @@ public class PolicyTests extends ProviderTestCase2<EmailProvider> {
assertEquals(0, account.mPolicyKey);
assertEquals(0, EmailContent.count(mMockContext, Policy.CONTENT_URI));
Policy policy = new Policy();
Policy.setAccountPolicy(mMockContext, account, policy, securitySyncKey);
SecurityPolicy.setAccountPolicy(mMockContext, account, policy, securitySyncKey);
account.refresh(mMockContext);
// We should have a policyKey now
assertTrue(account.mPolicyKey > 0);
@ -103,7 +104,7 @@ public class PolicyTests extends ProviderTestCase2<EmailProvider> {
assertEquals(policy, dbPolicy);
// The account should have the security sync key set
assertEquals(securitySyncKey, account.mSecuritySyncKey);
Policy.clearAccountPolicy(mMockContext, account);
SecurityPolicy.clearAccountPolicy(mMockContext, account);
account.refresh(mMockContext);
// Make sure policyKey is cleared and policy is deleted
assertEquals(0, account.mPolicyKey);
@ -118,11 +119,12 @@ public class PolicyTests extends ProviderTestCase2<EmailProvider> {
att.mAccountKey = acct.mId;
return att;
}
public void testSetAttachmentFlagsForNewPolicy() {
Account acct = ProviderTestUtils.setupAccount("acct1", true, mMockContext);
Policy policy1 = new Policy();
policy1.mDontAllowAttachments = true;
Policy.setAccountPolicy(mMockContext, acct, policy1, null);
SecurityPolicy.setAccountPolicy(mMockContext, acct, policy1, null);
Mailbox box = ProviderTestUtils.setupMailbox("box1", acct.mId, true, mMockContext);
Message msg1 = ProviderTestUtils.setupMessage("message1", acct.mId, box.mId, false, false,
mMockContext);

View File

@ -36,6 +36,7 @@ import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.email.SecurityPolicy;
import com.android.email.provider.EmailProvider.AttachmentService;
import com.android.emailcommon.AccountManagerTypes;
import com.android.emailcommon.provider.Account;
@ -2529,7 +2530,7 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
Policy p2 = new Policy();
p2.save(mMockContext);
Policy p3 = new Policy();
Policy.setAccountPolicy(mMockContext, a.mId, p3, "0");
SecurityPolicy.setAccountPolicy(mMockContext, a, p3, "0");
// We don't want anything cached or the tests below won't work. Note that
// deleteUnlinked is only called by EmailProvider when the caches are empty