summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CleanSpec.mk1
-rw-r--r--emailcommon/src/com/android/emailcommon/provider/EmailContent.aidl21
-rw-r--r--emailcommon/src/com/android/emailcommon/provider/EmailContent.java177
-rw-r--r--emailcommon/src/com/android/emailcommon/provider/Policy.aidl (renamed from emailcommon/src/com/android/emailcommon/service/PolicySet.aidl)4
-rw-r--r--emailcommon/src/com/android/emailcommon/provider/Policy.java395
-rw-r--r--emailcommon/src/com/android/emailcommon/service/EmailServiceProxy.java3
-rw-r--r--emailcommon/src/com/android/emailcommon/service/IPolicyService.aidl10
-rw-r--r--emailcommon/src/com/android/emailcommon/service/LegacyPolicySet.java87
-rw-r--r--emailcommon/src/com/android/emailcommon/service/PolicyServiceProxy.java29
-rw-r--r--emailcommon/src/com/android/emailcommon/service/PolicySet.java375
-rw-r--r--src/com/android/email/AccountBackupRestore.java2
-rw-r--r--src/com/android/email/Controller.java13
-rw-r--r--src/com/android/email/LegacyConversions.java6
-rw-r--r--src/com/android/email/SecurityPolicy.java342
-rw-r--r--src/com/android/email/activity/setup/AccountCheckSettingsFragment.java4
-rw-r--r--src/com/android/email/activity/setup/AccountSecurity.java44
-rw-r--r--src/com/android/email/activity/setup/AccountSettingsUtils.java5
-rw-r--r--src/com/android/email/activity/setup/AccountSetupOptions.java7
-rw-r--r--src/com/android/email/activity/setup/SetupData.java30
-rw-r--r--src/com/android/email/provider/EmailProvider.java113
-rw-r--r--src/com/android/email/service/PolicyService.java18
-rw-r--r--tests/src/com/android/email/LegacyConversionsTests.java2
-rw-r--r--tests/src/com/android/email/SecurityPolicyTests.java566
-rw-r--r--tests/src/com/android/email/provider/PolicyTests.java125
-rw-r--r--tests/src/com/android/email/provider/ProviderTestUtils.java4
25 files changed, 1303 insertions, 1080 deletions
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 81f5bfd5c..ebb255650 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -53,6 +53,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/EmailGoogle_inte
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Email_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/com.android.emailcommon_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/com.android.emailcommon_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/com.android.emailcommon_intermediates)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/emailcommon/src/com/android/emailcommon/provider/EmailContent.aidl b/emailcommon/src/com/android/emailcommon/provider/EmailContent.aidl
deleted file mode 100644
index 7505fa6f9..000000000
--- a/emailcommon/src/com/android/emailcommon/provider/EmailContent.aidl
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2008-2009 Marc Blank
- * Licensed to 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.emailcommon.provider;
-
-parcelable EmailContent.HostAuth;
-
diff --git a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
index e42bf0138..f20a00d6e 100644
--- a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
+++ b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java
@@ -124,6 +124,33 @@ public abstract class EmailContent {
return mId != NOT_SAVED;
}
+
+ /**
+ * Restore a subclass of EmailContent from the database
+ * @param context the caller's context
+ * @param klass the class to restore
+ * @param contentUri the content uri of the EmailContent subclass
+ * @param contentProjection the content projection for the EmailContent subclass
+ * @param id the unique id of the object
+ * @return the instantiated object
+ */
+ public static <T extends EmailContent> T restoreContentWithId(Context context,
+ Class<T> klass, Uri contentUri, String[] contentProjection, long id) {
+ Uri u = ContentUris.withAppendedId(contentUri, id);
+ Cursor c = context.getContentResolver().query(u, contentProjection, null, null, null);
+
+ try {
+ if (c.moveToFirst()) {
+ return (T)getContent(c, klass);
+ } else {
+ return null;
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+
// The Content sub class must have a no-arg constructor
static public <T extends EmailContent> T getContent(Cursor cursor, Class<T> klass) {
try {
@@ -194,7 +221,7 @@ public abstract class EmailContent {
/**
* no public constructor since this is a utility class
*/
- private EmailContent() {
+ protected EmailContent() {
}
public interface SyncColumns {
@@ -724,25 +751,8 @@ public abstract class EmailContent {
}
public static Message restoreMessageWithId(Context context, long id) {
- Uri u = ContentUris.withAppendedId(Message.CONTENT_URI, id);
- if (context == null) {
- throw new NullPointerException("context");
- }
- ContentResolver resolver = context.getContentResolver();
- if (resolver == null) {
- throw new NullPointerException("resolver");
- }
- Cursor c = resolver.query(u, Message.CONTENT_PROJECTION, null, null, null);
-
- try {
- if (c.moveToFirst()) {
- return getContent(c, Message.class);
- } else {
- return null;
- }
- } finally {
- c.close();
- }
+ return EmailContent.restoreContentWithId(context, Message.class,
+ Message.CONTENT_URI, Message.CONTENT_PROJECTION, id);
}
@Override
@@ -961,12 +971,16 @@ public abstract class EmailContent {
public static final String PROTOCOL_VERSION = "protocolVersion";
// The number of new messages (reported by the sync/download engines
public static final String NEW_MESSAGE_COUNT = "newMessageCount";
- // Flags defining security (provisioning) requirements of this account
+ // Legacy flags defining security (provisioning) requirements of this account; this
+ // information is now found in the Policy table; POLICY_KEY (below) is the foreign key
+ @Deprecated
public static final String SECURITY_FLAGS = "securityFlags";
// Server-based sync key for the security policies currently enforced
public static final String SECURITY_SYNC_KEY = "securitySyncKey";
// Signature to use with this account
public static final String SIGNATURE = "signature";
+ // A foreign key into the Policy table
+ public static final String POLICY_KEY = "policyKey";
}
public static final class Account extends EmailContent implements AccountColumns, Parcelable {
@@ -1039,13 +1053,14 @@ public abstract class EmailContent {
public String mRingtoneUri;
public String mProtocolVersion;
public int mNewMessageCount;
- public long mSecurityFlags;
public String mSecuritySyncKey;
public String mSignature;
+ public long mPolicyKey;
// Convenience for creating an account
public transient HostAuth mHostAuthRecv;
public transient HostAuth mHostAuthSend;
+ public transient Policy mPolicy;
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
@@ -1062,9 +1077,9 @@ public abstract class EmailContent {
public static final int CONTENT_RINGTONE_URI_COLUMN = 12;
public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 13;
public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 14;
- public static final int CONTENT_SECURITY_FLAGS_COLUMN = 15;
- public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 16;
- public static final int CONTENT_SIGNATURE_COLUMN = 17;
+ public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 15;
+ public static final int CONTENT_SIGNATURE_COLUMN = 16;
+ public static final int CONTENT_POLICY_KEY = 17;
public static final String[] CONTENT_PROJECTION = new String[] {
RECORD_ID, AccountColumns.DISPLAY_NAME,
@@ -1073,8 +1088,8 @@ public abstract class EmailContent {
AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.IS_DEFAULT,
AccountColumns.COMPATIBILITY_UUID, AccountColumns.SENDER_NAME,
AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
- AccountColumns.NEW_MESSAGE_COUNT, AccountColumns.SECURITY_FLAGS,
- AccountColumns.SECURITY_SYNC_KEY, AccountColumns.SIGNATURE
+ AccountColumns.NEW_MESSAGE_COUNT, AccountColumns.SECURITY_SYNC_KEY,
+ AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY
};
public static final int CONTENT_MAILBOX_TYPE_COLUMN = 1;
@@ -1100,7 +1115,7 @@ public abstract class EmailContent {
private static final String UUID_SELECTION = AccountColumns.COMPATIBILITY_UUID + " =?";
public static final String SECURITY_NONZERO_SELECTION =
- Account.SECURITY_FLAGS + " IS NOT NULL AND " + Account.SECURITY_FLAGS + "!=0";
+ Account.POLICY_KEY + " IS NOT NULL AND " + Account.POLICY_KEY + "!=0";
private static final String FIND_INBOX_SELECTION =
MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX +
@@ -1128,19 +1143,8 @@ public abstract class EmailContent {
}
public static Account restoreAccountWithId(Context context, long id) {
- Uri u = ContentUris.withAppendedId(Account.CONTENT_URI, id);
- Cursor c = context.getContentResolver().query(u, Account.CONTENT_PROJECTION,
- null, null, null);
-
- try {
- if (c.moveToFirst()) {
- return getContent(c, Account.class);
- } else {
- return null;
- }
- } finally {
- c.close();
- }
+ return EmailContent.restoreContentWithId(context, Account.class,
+ Account.CONTENT_URI, Account.CONTENT_PROJECTION, id);
}
/**
@@ -1178,9 +1182,9 @@ public abstract class EmailContent {
mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN);
mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN);
mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN);
- mSecurityFlags = cursor.getLong(CONTENT_SECURITY_FLAGS_COLUMN);
mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN);
mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN);
+ mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY);
}
private long getId(Uri u) {
@@ -1639,6 +1643,10 @@ public abstract class EmailContent {
*/
@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
+ mPolicy.setAccountPolicy(context, this, null);
+ }
if (cv.containsKey(AccountColumns.IS_DEFAULT) &&
cv.getAsBoolean(AccountColumns.IS_DEFAULT)) {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
@@ -1676,13 +1684,15 @@ public abstract class EmailContent {
// This logic is in place so I can (a) short circuit the expensive stuff when
// possible, and (b) override (and throw) if anyone tries to call save() or update()
// directly for Account, which are unsupported.
- if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false) {
- return super.save(context);
+ if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false &&
+ mPolicy != null) {
+ return super.save(context);
}
int index = 0;
int recvIndex = -1;
int sendIndex = -1;
+ int policyIndex = -1;
// Create operations for saving the send and recv hostAuths
// Also, remember which operation in the array they represent
@@ -1699,6 +1709,12 @@ public abstract class EmailContent {
.withValues(mHostAuthSend.toContentValues())
.build());
}
+ if (mPolicy != null) {
+ policyIndex = index++;
+ ops.add(ContentProviderOperation.newInsert(mPolicy.mBaseUri)
+ .withValues(mPolicy.toContentValues())
+ .build());
+ }
// Create operations for making this the only default account
// Note, these are always updates because they change existing accounts
@@ -1719,6 +1735,9 @@ public abstract class EmailContent {
if (sendIndex >= 0) {
cv.put(Account.HOST_AUTH_KEY_SEND, sendIndex);
}
+ if (policyIndex >= 0) {
+ cv.put(Account.POLICY_KEY, policyIndex);
+ }
}
ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri);
@@ -1742,6 +1761,11 @@ public abstract class EmailContent {
mHostAuthKeySend = newId;
mHostAuthSend.mId = newId;
}
+ if (policyIndex >= 0) {
+ long newId = getId(results[policyIndex].uri);
+ mPolicyKey = newId;
+ mPolicy.mId = newId;
+ }
Uri u = results[index].uri;
mId = getId(u);
return u;
@@ -1770,9 +1794,9 @@ public abstract class EmailContent {
values.put(AccountColumns.RINGTONE_URI, mRingtoneUri);
values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount);
- values.put(AccountColumns.SECURITY_FLAGS, mSecurityFlags);
values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey);
values.put(AccountColumns.SIGNATURE, mSignature);
+ values.put(AccountColumns.POLICY_KEY, mPolicyKey);
return values;
}
@@ -1817,9 +1841,9 @@ public abstract class EmailContent {
dest.writeString(mRingtoneUri);
dest.writeString(mProtocolVersion);
dest.writeInt(mNewMessageCount);
- dest.writeLong(mSecurityFlags);
dest.writeString(mSecuritySyncKey);
dest.writeString(mSignature);
+ dest.writeLong(mPolicyKey);
if (mHostAuthRecv != null) {
dest.writeByte((byte)1);
@@ -1856,9 +1880,9 @@ public abstract class EmailContent {
mRingtoneUri = in.readString();
mProtocolVersion = in.readString();
mNewMessageCount = in.readInt();
- mSecurityFlags = in.readLong();
mSecuritySyncKey = in.readString();
mSignature = in.readString();
+ mPolicyKey = in.readLong();
mHostAuthRecv = null;
if (in.readByte() == 1) {
@@ -2004,19 +2028,8 @@ public abstract class EmailContent {
* @return the instantiated Attachment
*/
public static Attachment restoreAttachmentWithId (Context context, long id) {
- Uri u = ContentUris.withAppendedId(Attachment.CONTENT_URI, id);
- Cursor c = context.getContentResolver().query(u, Attachment.CONTENT_PROJECTION,
- null, null, null);
-
- try {
- if (c.moveToFirst()) {
- return getContent(c, Attachment.class);
- } else {
- return null;
- }
- } finally {
- c.close();
- }
+ return EmailContent.restoreContentWithId(context, Attachment.class,
+ Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id);
}
/**
@@ -2397,19 +2410,8 @@ public abstract class EmailContent {
* @return the instantiated Mailbox
*/
public static Mailbox restoreMailboxWithId(Context context, long id) {
- Uri u = ContentUris.withAppendedId(Mailbox.CONTENT_URI, id);
- Cursor c = context.getContentResolver().query(u, Mailbox.CONTENT_PROJECTION,
- null, null, null);
-
- try {
- if (c.moveToFirst()) {
- return getContent(c, Mailbox.class);
- } else {
- return null;
- }
- } finally {
- c.close();
- }
+ return EmailContent.restoreContentWithId(context, Mailbox.class,
+ Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, id);
}
/**
@@ -2737,19 +2739,8 @@ public abstract class EmailContent {
* @return the instantiated HostAuth
*/
public static HostAuth restoreHostAuthWithId(Context context, long id) {
- Uri u = ContentUris.withAppendedId(EmailContent.HostAuth.CONTENT_URI, id);
- Cursor c = context.getContentResolver().query(u, HostAuth.CONTENT_PROJECTION,
- null, null, null);
-
- try {
- if (c.moveToFirst()) {
- return getContent(c, HostAuth.class);
- } else {
- return null;
- }
- } finally {
- c.close();
- }
+ return EmailContent.restoreContentWithId(context, HostAuth.class,
+ HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, id);
}
@@ -3015,4 +3006,18 @@ public abstract class EmailContent {
&& Utility.areStringsEqual(mDomain, that.mDomain);
}
}
+
+ public interface PolicyColumns {
+ public static final String ID = "_id";
+ public static final String PASSWORD_MODE = "passwordMode";
+ public static final String PASSWORD_MIN_LENGTH = "passwordMinLength";
+ public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays";
+ public static final String PASSWORD_HISTORY = "passwordHistory";
+ public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars";
+ public static final String PASSWORD_MAX_FAILS = "passwordMaxFails";
+ public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime";
+ public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe";
+ public static final String REQUIRE_ENCRYPTION = "requireEncryption";
+ public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal";
+ }
}
diff --git a/emailcommon/src/com/android/emailcommon/service/PolicySet.aidl b/emailcommon/src/com/android/emailcommon/provider/Policy.aidl
index e825c62c3..02be51b9a 100644
--- a/emailcommon/src/com/android/emailcommon/service/PolicySet.aidl
+++ b/emailcommon/src/com/android/emailcommon/provider/Policy.aidl
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-package com.android.emailcommon.service;
+package com.android.emailcommon.provider;
-parcelable PolicySet;
+parcelable Policy;
diff --git a/emailcommon/src/com/android/emailcommon/provider/Policy.java b/emailcommon/src/com/android/emailcommon/provider/Policy.java
new file mode 100644
index 000000000..4f85997db
--- /dev/null
+++ b/emailcommon/src/com/android/emailcommon/provider/Policy.java
@@ -0,0 +1,395 @@
+/*
+ * 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.emailcommon.provider;
+import com.android.emailcommon.utility.Utility;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ContentProviderOperation;
+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 java.util.ArrayList;
+
+/**
+ * The Policy class represents a set of security requirements that are associated with an Account.
+ * The requirements may be either device-specific (e.g. password) or application-specific (e.g.
+ * a limit on the sync window for the Account)
+ */
+public final class Policy extends EmailContent implements EmailContent.PolicyColumns, Parcelable {
+ // STOPSHIP Change to false after a few days of debugging
+ public static final boolean DEBUG_POLICY = true; // DO NOT SUBMIT WITH THIS SET TO FALSE
+ public static final String TAG = "Email/Policy";
+
+ public static final String TABLE_NAME = "Policy";
+ @SuppressWarnings("hiding")
+ public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/policy");
+
+ /* 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;
+
+ public static final int PASSWORD_MODE_NONE = 0;
+ public static final int PASSWORD_MODE_SIMPLE = 1;
+ public static final int PASSWORD_MODE_STRONG = 2;
+
+ public int mPasswordMode;
+ public int mPasswordMinLength;
+ public int mPasswordMaxFails;
+ public int mPasswordExpirationDays;
+ public int mPasswordHistory;
+ public int mPasswordComplexChars;
+ public int mMaxScreenLockTime;
+ public boolean mRequireRemoteWipe;
+ public boolean mRequireEncryption;
+ public boolean mRequireEncryptionExternal;
+
+ public static final int CONTENT_ID_COLUMN = 0;
+ public static final int CONTENT_PASSWORD_MODE_COLUMN = 1;
+ public static final int CONTENT_PASSWORD_MIN_LENGTH_COLUMN = 2;
+ public static final int CONTENT_PASSWORD_EXPIRATION_DAYS_COLUMN = 3;
+ public static final int CONTENT_PASSWORD_HISTORY_COLUMN = 4;
+ public static final int CONTENT_PASSWORD_COMPLEX_CHARS_COLUMN = 5;
+ public static final int CONTENT_PASSWORD_MAX_FAILS_COLUMN = 6;
+ public static final int CONTENT_MAX_SCREEN_LOCK_TIME_COLUMN = 7;
+ public static final int CONTENT_REQUIRE_REMOTE_WIPE_COLUMN = 8;
+ public static final int CONTENT_REQUIRE_ENCRYPTION_COLUMN = 9;
+ public static final int CONTENT_REQUIRE_ENCRYPTION_EXTERNAL_COLUMN = 10;
+
+ public static final String[] CONTENT_PROJECTION = new String[] {RECORD_ID,
+ PolicyColumns.PASSWORD_MODE, PolicyColumns.PASSWORD_MIN_LENGTH,
+ PolicyColumns.PASSWORD_EXPIRATION_DAYS, PolicyColumns.PASSWORD_HISTORY,
+ PolicyColumns.PASSWORD_COMPLEX_CHARS, PolicyColumns.PASSWORD_MAX_FAILS,
+ PolicyColumns.MAX_SCREEN_LOCK_TIME, PolicyColumns.REQUIRE_REMOTE_WIPE,
+ PolicyColumns.REQUIRE_ENCRYPTION, PolicyColumns.REQUIRE_ENCRYPTION_EXTERNAL
+ };
+
+ public static final Policy NO_POLICY = new Policy();
+
+ public Policy() {
+ mBaseUri = CONTENT_URI;
+ // By default, the password mode is "none"
+ mPasswordMode = PASSWORD_MODE_NONE;
+ // All server policies require the ability to wipe the device
+ mRequireRemoteWipe = true;
+ }
+
+ public static Policy restorePolicyWithId(Context context, long id) {
+ return EmailContent.restoreContentWithId(context, Policy.class, Policy.CONTENT_URI,
+ Policy.CONTENT_PROJECTION, id);
+ }
+
+ public static long getAccountIdWithPolicyKey(Context context, long id) {
+ return Utility.getFirstRowLong(context, Account.CONTENT_URI, Account.ID_PROJECTION,
+ AccountColumns.POLICY_KEY + "=?", new String[] {Long.toString(id)}, null,
+ Account.ID_PROJECTION_COLUMN);
+ }
+
+ // We override this method to insure that we never write invalid policy data to the provider
+ public Uri save(Context context) {
+ normalize();
+ return super.save(context);
+ }
+
+ /**
+ * Associate the policy with an account; this also removes any other policy associated with
+ * the account and sets the policy key for the account. This is all done atomically
+ * @param context the caller's context
+ * @param account the account whose policy is to be set
+ * @param securitySyncKey the current security sync key for this account
+ */
+ public void setAccountPolicy(Context context, Account account, String securitySyncKey) {
+ if (DEBUG_POLICY) {
+ Log.d(TAG, "Set policy for account " + account.mDisplayName + ": " + toString());
+ }
+ // Make sure this is a valid policy set
+ normalize();
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+ // Add the new policy (no account will yet reference this)
+ ops.add(ContentProviderOperation.newInsert(
+ Policy.CONTENT_URI).withValues(toContentValues()).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());
+ }
+ // 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());
+ try {
+ context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
+ } 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
+ }
+ }
+
+ /**
+ * Clear any existing policy for a given account and clear the account's security sync key,
+ * and do so atomically
+ * @param context the caller's context
+ * @param account the account whose policy is to be cleared
+ */
+ public static void clearAccountPolicy(Context context, Account account) {
+ if (DEBUG_POLICY) {
+ Log.d(TAG, "Clearing policy for account: " + account.mDisplayName);
+ }
+ ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
+ // 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());
+ }
+ // Clear the security sync key and policy key
+ ops.add(ContentProviderOperation.newUpdate(
+ ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
+ .withValue(AccountColumns.SECURITY_SYNC_KEY, null)
+ .withValue(AccountColumns.POLICY_KEY, 0)
+ .build());
+ try {
+ context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
+ } 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
+ }
+ }
+
+ /**
+ * Normalize the Policy. If the password mode is "none", zero out all password-related fields;
+ * zero out complex characters for simple passwords.
+ */
+ public void normalize() {
+ if (mPasswordMode == PASSWORD_MODE_NONE) {
+ mPasswordMaxFails = 0;
+ mMaxScreenLockTime = 0;
+ mPasswordMinLength = 0;
+ mPasswordComplexChars = 0;
+ mPasswordHistory = 0;
+ mPasswordExpirationDays = 0;
+ } else {
+ if ((mPasswordMode != PASSWORD_MODE_SIMPLE) &&
+ (mPasswordMode != PASSWORD_MODE_STRONG)) {
+ throw new IllegalArgumentException("password mode");
+ }
+ // 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 (mPasswordMode == PASSWORD_MODE_SIMPLE) {
+ mPasswordComplexChars = 0;
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Policy)) return false;
+ Policy otherPolicy = (Policy)other;
+ if (mRequireEncryption != otherPolicy.mRequireEncryption) return false;
+ if (mRequireEncryptionExternal != otherPolicy.mRequireEncryptionExternal) return false;
+ if (mRequireRemoteWipe != otherPolicy.mRequireRemoteWipe) return false;
+ if (mMaxScreenLockTime != otherPolicy.mMaxScreenLockTime) return false;
+ if (mPasswordComplexChars != otherPolicy.mPasswordComplexChars) return false;
+ if (mPasswordExpirationDays != otherPolicy.mPasswordExpirationDays) return false;
+ if (mPasswordHistory != otherPolicy.mPasswordHistory) return false;
+ if (mPasswordMaxFails != otherPolicy.mPasswordMaxFails) return false;
+ if (mPasswordMinLength != otherPolicy.mPasswordMinLength) return false;
+ if (mPasswordMode != otherPolicy.mPasswordMode) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int code = mRequireEncryption ? 1 : 0;
+ code += (mRequireEncryptionExternal ? 1 : 0) << 1;
+ code += (mRequireRemoteWipe ? 1 : 0) << 2;
+ code += (mMaxScreenLockTime << 3);
+ code += (mPasswordComplexChars << 6);
+ code += (mPasswordExpirationDays << 12);
+ code += (mPasswordHistory << 15);
+ code += (mPasswordMaxFails << 18);
+ code += (mPasswordMinLength << 22);
+ code += (mPasswordMode << 26);
+ return code;
+ }
+
+ @Override
+ public void restore(Cursor cursor) {
+ mBaseUri = CONTENT_URI;
+ mId = cursor.getLong(CONTENT_ID_COLUMN);
+ mPasswordMode = cursor.getInt(CONTENT_PASSWORD_MODE_COLUMN);
+ mPasswordMinLength = cursor.getInt(CONTENT_PASSWORD_MIN_LENGTH_COLUMN);
+ mPasswordMaxFails = cursor.getInt(CONTENT_PASSWORD_MAX_FAILS_COLUMN);
+ mPasswordHistory = cursor.getInt(CONTENT_PASSWORD_HISTORY_COLUMN);
+ mPasswordExpirationDays = cursor.getInt(CONTENT_PASSWORD_EXPIRATION_DAYS_COLUMN);
+ mPasswordComplexChars = cursor.getInt(CONTENT_PASSWORD_COMPLEX_CHARS_COLUMN);
+ mMaxScreenLockTime = cursor.getInt(CONTENT_MAX_SCREEN_LOCK_TIME_COLUMN);
+ mRequireRemoteWipe = cursor.getInt(CONTENT_REQUIRE_REMOTE_WIPE_COLUMN) == 1;
+ mRequireEncryption = cursor.getInt(CONTENT_REQUIRE_ENCRYPTION_COLUMN) == 1;
+ mRequireEncryptionExternal =
+ cursor.getInt(CONTENT_REQUIRE_ENCRYPTION_EXTERNAL_COLUMN) == 1;
+ }
+
+ @Override
+ public ContentValues toContentValues() {
+ ContentValues values = new ContentValues();
+ values.put(PolicyColumns.PASSWORD_MODE, mPasswordMode);
+ values.put(PolicyColumns.PASSWORD_MIN_LENGTH, mPasswordMinLength);
+ values.put(PolicyColumns.PASSWORD_MAX_FAILS, mPasswordMaxFails);
+ values.put(PolicyColumns.PASSWORD_HISTORY, mPasswordHistory);
+ values.put(PolicyColumns.PASSWORD_EXPIRATION_DAYS, mPasswordExpirationDays);
+ values.put(PolicyColumns.PASSWORD_COMPLEX_CHARS, mPasswordComplexChars);
+ values.put(PolicyColumns.MAX_SCREEN_LOCK_TIME, mMaxScreenLockTime);
+ values.put(PolicyColumns.REQUIRE_REMOTE_WIPE, mRequireRemoteWipe);
+ values.put(PolicyColumns.REQUIRE_ENCRYPTION, mRequireEncryption);
+ values.put(PolicyColumns.REQUIRE_ENCRYPTION_EXTERNAL, mRequireEncryptionExternal);
+ return values;
+ }
+
+ /**
+ * Helper to map our internal encoding to DevicePolicyManager password modes.
+ */
+ public int getDPManagerPasswordQuality() {
+ switch (mPasswordMode) {
+ case PASSWORD_MODE_SIMPLE:
+ return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ case PASSWORD_MODE_STRONG:
+ if (mPasswordComplexChars == 0) {
+ return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+ } else {
+ return DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+ }
+ default:
+ return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ private void appendPolicy(StringBuilder sb, String code, int value) {
+ sb.append(code);
+ sb.append(":");
+ sb.append(value);
+ sb.append(" ");
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder("[");
+ if (this.equals(NO_POLICY)) {
+ sb.append("No policies]");
+ } else {
+ if (mPasswordMode == PASSWORD_MODE_NONE) {
+ sb.append("Pwd no ");
+ } else {
+ appendPolicy(sb, "Pwd strong", mPasswordMode == PASSWORD_MODE_STRONG ? 1 : 0);
+ appendPolicy(sb, "len", mPasswordMinLength);
+ appendPolicy(sb, "cmpx", mPasswordComplexChars);
+ appendPolicy(sb, "expy", mPasswordExpirationDays);
+ appendPolicy(sb, "hist", mPasswordHistory);
+ appendPolicy(sb, "fail", mPasswordMaxFails);
+ appendPolicy(sb, "idle", mMaxScreenLockTime);
+ }
+ appendPolicy(sb, "crypt", mRequireEncryption ? 1 : 0);
+ appendPolicy(sb, "crypt/ex", mRequireEncryptionExternal ? 1 : 0);
+ sb.append("]");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Supports Parcelable
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Supports Parcelable
+ */
+ public static final Parcelable.Creator<Policy> CREATOR = new Parcelable.Creator<Policy>() {
+ public Policy createFromParcel(Parcel in) {
+ return new Policy(in);
+ }
+
+ public Policy[] newArray(int size) {
+ return new Policy[size];
+ }
+ };
+
+ /**
+ * Supports Parcelable
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // mBaseUri is not parceled
+ dest.writeLong(mId);
+ dest.writeInt(mPasswordMode);
+ dest.writeInt(mPasswordMinLength);
+ dest.writeInt(mPasswordMaxFails);
+ dest.writeInt(mPasswordHistory);
+ dest.writeInt(mPasswordExpirationDays);
+ dest.writeInt(mPasswordComplexChars);
+ dest.writeInt(mMaxScreenLockTime);
+ dest.writeInt(mRequireRemoteWipe ? 1 : 0);
+ dest.writeInt(mRequireEncryption ? 1 : 0);
+ dest.writeInt(mRequireEncryptionExternal ? 1 : 0);
+ }
+
+ /**
+ * Supports Parcelable
+ */
+ public Policy(Parcel in) {
+ mBaseUri = CONTENT_URI;
+ mId = in.readLong();
+ mPasswordMode = in.readInt();
+ mPasswordMinLength = in.readInt();
+ mPasswordMaxFails = in.readInt();
+ mPasswordHistory = in.readInt();
+ mPasswordExpirationDays = in.readInt();
+ mPasswordComplexChars = in.readInt();
+ mMaxScreenLockTime = in.readInt();
+ mRequireRemoteWipe = in.readInt() == 1;
+ mRequireEncryption = in.readInt() == 1;
+ mRequireEncryptionExternal = in.readInt() == 1;
+ }
+} \ No newline at end of file
diff --git a/emailcommon/src/com/android/emailcommon/service/EmailServiceProxy.java b/emailcommon/src/com/android/emailcommon/service/EmailServiceProxy.java
index cc41505e5..3a274f50f 100644
--- a/emailcommon/src/com/android/emailcommon/service/EmailServiceProxy.java
+++ b/emailcommon/src/com/android/emailcommon/service/EmailServiceProxy.java
@@ -20,6 +20,7 @@ import com.android.emailcommon.Api;
import com.android.emailcommon.Device;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.EmailContent.HostAuth;
+import com.android.emailcommon.provider.Policy;
import android.content.Context;
import android.content.Intent;
@@ -212,7 +213,7 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
return bundle;
} else {
Bundle bundle = (Bundle) mReturn;
- bundle.setClassLoader(PolicySet.class.getClassLoader());
+ bundle.setClassLoader(Policy.class.getClassLoader());
Log.v(TAG, "validate returns " + bundle.getInt(VALIDATE_BUNDLE_RESULT_CODE));
return bundle;
}
diff --git a/emailcommon/src/com/android/emailcommon/service/IPolicyService.aidl b/emailcommon/src/com/android/emailcommon/service/IPolicyService.aidl
index 646abf353..e9bcf42dd 100644
--- a/emailcommon/src/com/android/emailcommon/service/IPolicyService.aidl
+++ b/emailcommon/src/com/android/emailcommon/service/IPolicyService.aidl
@@ -15,16 +15,16 @@
*/
package com.android.emailcommon.service;
-import com.android.emailcommon.service.PolicySet;
+import com.android.emailcommon.provider.Policy;
interface IPolicyService {
- boolean isActive(in PolicySet policies);
+ boolean isActive(in Policy policies);
void policiesRequired(long accountId);
- void updatePolicies(long accountId);
+ void policiesUpdated(long accountId);
void setAccountHoldFlag(long accountId, boolean newState);
boolean isActiveAdmin();
// This is about as oneway as you can get
oneway void remoteWipe();
- boolean isSupported(in PolicySet policies);
- PolicySet clearUnsupportedPolicies(in PolicySet policies);
+ boolean isSupported(in Policy policies);
+ Policy clearUnsupportedPolicies(in Policy policies);
} \ No newline at end of file
diff --git a/emailcommon/src/com/android/emailcommon/service/LegacyPolicySet.java b/emailcommon/src/com/android/emailcommon/service/LegacyPolicySet.java
new file mode 100644
index 000000000..e5d443688
--- /dev/null
+++ b/emailcommon/src/com/android/emailcommon/service/LegacyPolicySet.java
@@ -0,0 +1,87 @@
+/* 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.emailcommon.service;
+
+import com.android.emailcommon.provider.Policy;
+
+/**
+ * Legacy class for policy storage as a bit field of flags
+ */
+public class LegacyPolicySet {
+
+ // Security (provisioning) flags
+ // bits 0..4: password length (0=no password required)
+ public static final int PASSWORD_LENGTH_MASK = 31;
+ public static final int PASSWORD_LENGTH_SHIFT = 0;
+ public static final int PASSWORD_LENGTH_MAX = 30;
+ // bits 5..8: password mode
+ public static final int PASSWORD_MODE_SHIFT = 5;
+ public 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)
+ public static final int PASSWORD_MAX_FAILS_SHIFT = 9;
+ public 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)
+ public static final int SCREEN_LOCK_TIME_SHIFT = 14;
+ public 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
+ public static final int REQUIRE_REMOTE_WIPE = 1 << 25;
+ // bit 26..35: password expiration (days; 0=not required)
+ public static final int PASSWORD_EXPIRATION_SHIFT = 26;
+ public static final long PASSWORD_EXPIRATION_MASK = 1023L << PASSWORD_EXPIRATION_SHIFT;
+ public static final int PASSWORD_EXPIRATION_MAX = 1023;
+ // bit 36..43: password history (length; 0=not required)
+ public static final int PASSWORD_HISTORY_SHIFT = 36;
+ public static final long PASSWORD_HISTORY_MASK = 255L << PASSWORD_HISTORY_SHIFT;
+ public static final int PASSWORD_HISTORY_MAX = 255;
+ // bit 44..48: min complex characters (0=not required)
+ public static final int PASSWORD_COMPLEX_CHARS_SHIFT = 44;
+ public static final long PASSWORD_COMPLEX_CHARS_MASK = 31L << PASSWORD_COMPLEX_CHARS_SHIFT;
+ public static final int PASSWORD_COMPLEX_CHARS_MAX = 31;
+ // bit 49: requires device encryption (internal)
+ public static final long REQUIRE_ENCRYPTION = 1L << 49;
+ // bit 50: requires external storage encryption
+ public static final long REQUIRE_ENCRYPTION_EXTERNAL = 1L << 50;
+
+ /**
+ * Convert legacy policy flags to a Policy
+ * @param flags legacy policy flags
+ * @return a Policy representing the legacy policy flag
+ */
+ public static Policy flagsToPolicy(long flags) {
+ Policy policy = new Policy();
+ policy.mPasswordMode = ((int) (flags & PASSWORD_MODE_MASK)) >> PASSWORD_MODE_SHIFT;
+ policy.mPasswordMinLength = (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT);
+ policy.mPasswordMaxFails =
+ (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT);
+ policy.mPasswordComplexChars =
+ (int) ((flags & PASSWORD_COMPLEX_CHARS_MASK) >> PASSWORD_COMPLEX_CHARS_SHIFT);
+ policy.mPasswordHistory = (int) ((flags & PASSWORD_HISTORY_MASK) >> PASSWORD_HISTORY_SHIFT);
+ policy.mPasswordExpirationDays =
+ (int) ((flags & PASSWORD_EXPIRATION_MASK) >> PASSWORD_EXPIRATION_SHIFT);
+ policy.mMaxScreenLockTime =
+ (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT);
+ policy.mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
+ policy.mRequireEncryption = 0 != (flags & REQUIRE_ENCRYPTION);
+ policy.mRequireEncryptionExternal = 0 != (flags & REQUIRE_ENCRYPTION_EXTERNAL);
+ return policy;
+ }
+}
+
diff --git a/emailcommon/src/com/android/emailcommon/service/PolicyServiceProxy.java b/emailcommon/src/com/android/emailcommon/service/PolicyServiceProxy.java
index 45e868a2e..53d7a24ac 100644
--- a/emailcommon/src/com/android/emailcommon/service/PolicyServiceProxy.java
+++ b/emailcommon/src/com/android/emailcommon/service/PolicyServiceProxy.java
@@ -17,6 +17,7 @@
package com.android.emailcommon.service;
import com.android.emailcommon.provider.EmailContent.Account;
+import com.android.emailcommon.provider.Policy;
import android.content.Context;
import android.content.Intent;
@@ -48,7 +49,7 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
}
@Override
- public PolicySet clearUnsupportedPolicies(final PolicySet arg0) throws RemoteException {
+ public Policy clearUnsupportedPolicies(final Policy arg0) throws RemoteException {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mReturn = mService.clearUnsupportedPolicies(arg0);
@@ -62,12 +63,12 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
// Can this happen?
return null;
} else {
- return (PolicySet)mReturn;
+ return (Policy)mReturn;
}
}
@Override
- public boolean isActive(final PolicySet arg0) throws RemoteException {
+ public boolean isActive(final Policy arg0) throws RemoteException {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mReturn = mService.isActive(arg0);
@@ -105,7 +106,7 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
}
@Override
- public boolean isSupported(final PolicySet arg0) throws RemoteException {
+ public boolean isSupported(final Policy arg0) throws RemoteException {
setTask(new ProxyTask() {
public void run() throws RemoteException {
mReturn = mService.isSupported(arg0);
@@ -151,16 +152,16 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
}
@Override
- public void updatePolicies(final long arg0) throws RemoteException {
+ public void policiesUpdated(final long arg0) throws RemoteException {
setTask(new ProxyTask() {
public void run() throws RemoteException {
- mService.updatePolicies(arg0);
+ mService.policiesUpdated(arg0);
}
- }, "updatePolicies");
+ }, "policiesUpdated");
}
// Static methods that encapsulate the proxy calls above
- public static boolean isActive(Context context, PolicySet policies) {
+ public static boolean isActive(Context context, Policy policies) {
try {
return new PolicyServiceProxy(context).isActive(policies);
} catch (RemoteException e) {
@@ -176,9 +177,9 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
}
}
- public static void updatePolicies(Context context, long accountId) {
+ public static void policiesUpdated(Context context, long accountId) {
try {
- new PolicyServiceProxy(context).updatePolicies(accountId);
+ new PolicyServiceProxy(context).policiesUpdated(accountId);
} catch (RemoteException e) {
throw new IllegalStateException("PolicyService transaction failed");
}
@@ -208,17 +209,17 @@ public class PolicyServiceProxy extends ServiceProxy implements IPolicyService {
}
}
- public static boolean isSupported(Context context, PolicySet policies) {
+ public static boolean isSupported(Context context, Policy policy) {
try {
- return new PolicyServiceProxy(context).isSupported(policies);
+ return new PolicyServiceProxy(context).isSupported(policy);
} catch (RemoteException e) {
}
return false;
}
- public static PolicySet clearUnsupportedPolicies(Context context, PolicySet policies) {
+ public static Policy clearUnsupportedPolicies(Context context, Policy policy) {
try {
- return new PolicyServiceProxy(context).clearUnsupportedPolicies(policies);
+ return new PolicyServiceProxy(context).clearUnsupportedPolicies(policy);
} catch (RemoteException e) {
}
throw new IllegalStateException("PolicyService transaction failed");
diff --git a/emailcommon/src/com/android/emailcommon/service/PolicySet.java b/emailcommon/src/com/android/emailcommon/service/PolicySet.java
deleted file mode 100644
index 70538b440..000000000
--- a/emailcommon/src/com/android/emailcommon/service/PolicySet.java
+++ /dev/null
@@ -1,375 +0,0 @@
-/* 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.emailcommon.service;
-
-import com.android.emailcommon.provider.EmailContent.Account;
-import com.android.emailcommon.provider.EmailContent.AccountColumns;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ContentValues;
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-
-/**
- * Class for tracking policies and reading/writing into accounts
- */
-public class PolicySet implements Parcelable {
-
- // 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;
- public static final int PASSWORD_LENGTH_MAX = 30;
- // 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;
- // 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;
- // bit 36..43: password history (length; 0=not required)
- 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;
- // bit 44..48: min complex characters (0=not required)
- 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;
- // bit 49: requires device encryption (internal)
- private static final long REQUIRE_ENCRYPTION = 1L << 49;
- // bit 50: requires external storage encryption
- private static final long REQUIRE_ENCRYPTION_EXTERNAL = 1L << 50;
-
- /* 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;
-
- public final int mMinPasswordLength;
- public final int mPasswordMode;
- public final int mMaxPasswordFails;
- public final int mMaxScreenLockTime;
- public final boolean mRequireRemoteWipe;
- public final int mPasswordExpirationDays;
- public final int mPasswordHistory;
- public final int mPasswordComplexChars;
- public final boolean mRequireEncryption;
- public final boolean mRequireEncryptionExternal;
-
- public int getMinPasswordLengthForTest() {
- return mMinPasswordLength;
- }
-
- public int getPasswordModeForTest() {
- return mPasswordMode;
- }
-
- public int getMaxPasswordFailsForTest() {
- return mMaxPasswordFails;
- }
-
- public int getMaxScreenLockTimeForTest() {
- return mMaxScreenLockTime;
- }
-
- public boolean isRequireRemoteWipeForTest() {
- return mRequireRemoteWipe;
- }
-
- public boolean isRequireEncryptionForTest() {
- return mRequireEncryption;
- }
-
- public boolean isRequireEncryptionExternalForTest() {
- return mRequireEncryptionExternal;
- }
-
- /**
- * Create from raw values.
- * @param minPasswordLength (0=not enforced)
- * @param passwordMode
- * @param maxPasswordFails (0=not enforced)
- * @param maxScreenLockTime in seconds (0=not enforced)
- * @param requireRemoteWipe
- * @param passwordExpirationDays in days (0=not enforced)
- * @param passwordHistory (0=not enforced)
- * @param passwordComplexChars (0=not enforced)
- * @param requireEncryption
- * @param requireEncryptionExternal
- * @throws IllegalArgumentException for illegal arguments.
- */
- public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
- int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpirationDays,
- int passwordHistory, int passwordComplexChars, boolean requireEncryption,
- boolean requireEncryptionExternal) throws IllegalArgumentException {
- // 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;
- passwordExpirationDays = 0;
- } else {
- if ((passwordMode != PASSWORD_MODE_SIMPLE) &&
- (passwordMode != PASSWORD_MODE_STRONG)) {
- throw new IllegalArgumentException("password mode");
- }
- // 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;
- }
- // The next four values have hard limits which cannot be supported if exceeded.
- if (minPasswordLength > PASSWORD_LENGTH_MAX) {
- throw new IllegalArgumentException("password length");
- }
- if (passwordExpirationDays > PASSWORD_EXPIRATION_MAX) {
- 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;
- }
- }
- mMinPasswordLength = minPasswordLength;
- mPasswordMode = passwordMode;
- mMaxPasswordFails = maxPasswordFails;
- mMaxScreenLockTime = maxScreenLockTime;
- mRequireRemoteWipe = requireRemoteWipe;
- mPasswordExpirationDays = passwordExpirationDays;
- mPasswordHistory = passwordHistory;
- mPasswordComplexChars = passwordComplexChars;
- mRequireEncryption = requireEncryption;
- mRequireEncryptionExternal = requireEncryptionExternal;
- }
-
- /**
- * 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
- */
- public PolicySet(long flags) {
- mMinPasswordLength =
- (int) ((flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT);
- mPasswordMode =
- (int) (flags & PASSWORD_MODE_MASK);
- mMaxPasswordFails =
- (int) ((flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT);
- mMaxScreenLockTime =
- (int) ((flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT);
- mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
- mPasswordExpirationDays =
- (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);
- mRequireEncryption = 0 != (flags & REQUIRE_ENCRYPTION);
- mRequireEncryptionExternal = 0 != (flags & REQUIRE_ENCRYPTION_EXTERNAL);
- }
-
- /**
- * Helper to map our internal encoding to DevicePolicyManager password modes.
- */
- public int getDPManagerPasswordQuality() {
- switch (mPasswordMode) {
- case PASSWORD_MODE_SIMPLE:
- return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
- case PASSWORD_MODE_STRONG:
- if (mPasswordComplexChars == 0) {
- return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
- } else {
- return DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
- }
- default:
- return DevicePolicyManager .PASSWORD_QUALITY_UNSPECIFIED;
- }
- }
-
- /**
- * 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;
- }
-
- /**
- * Record flags (and a sync key for the flags) into an Account
- * Note: the hash code is defined as the encoding used in Account
- *
- * @param account to write the values mSecurityFlags and mSecuritySyncKey
- * @param syncKey the value to write into the account's mSecuritySyncKey
- * @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)
- */
- public boolean writeAccount(Account account, String syncKey, boolean update,
- Context context) {
- long newFlags = getSecurityCode();
- 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;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof PolicySet) {
- PolicySet other = (PolicySet)o;
- return (this.getSecurityCode() == other.getSecurityCode());
- }
- return false;
- }
-
- /**
- * 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);
- dest.writeInt(mPasswordExpirationDays);
- dest.writeInt(mPasswordHistory);
- dest.writeInt(mPasswordComplexChars);
- dest.writeInt(mRequireEncryption ? 1 : 0);
- dest.writeInt(mRequireEncryptionExternal ? 1 : 0);
- }
-
- /**
- * Supports Parcelable
- */
- public PolicySet(Parcel in) {
- mMinPasswordLength = in.readInt();
- mPasswordMode = in.readInt();
- mMaxPasswordFails = in.readInt();
- mMaxScreenLockTime = in.readInt();
- mRequireRemoteWipe = in.readInt() == 1;
- mPasswordExpirationDays = in.readInt();
- mPasswordHistory = in.readInt();
- mPasswordComplexChars = in.readInt();
- mRequireEncryption = in.readInt() == 1;
- mRequireEncryptionExternal = in.readInt() == 1;
- }
-
- @Override
- public int hashCode() {
- long code = getSecurityCode();
- return (int) code;
- }
-
- public long getSecurityCode() {
- long flags = 0;
- flags = (long)mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
- flags |= mPasswordMode;
- flags |= (long)mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
- flags |= (long)mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
- if (mRequireRemoteWipe) flags |= REQUIRE_REMOTE_WIPE;
- flags |= (long)mPasswordHistory << PASSWORD_HISTORY_SHIFT;
- flags |= (long)mPasswordExpirationDays << PASSWORD_EXPIRATION_SHIFT;
- flags |= (long)mPasswordComplexChars << PASSWORD_COMPLEX_CHARS_SHIFT;
- if (mRequireEncryption) flags |= REQUIRE_ENCRYPTION;
- if (mRequireEncryptionExternal) flags |= REQUIRE_ENCRYPTION_EXTERNAL;
- return flags;
- }
-
- @Override
- public String toString() {
- return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
- + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
- + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe
- + " pw-expiration=" + mPasswordExpirationDays
- + " pw-history=" + mPasswordHistory
- + " pw-complex-chars=" + mPasswordComplexChars
- + " require-encryption=" + mRequireEncryption
- + " require-encryptionExternal=" + mRequireEncryptionExternal + "}";
- }
-}
-
diff --git a/src/com/android/email/AccountBackupRestore.java b/src/com/android/email/AccountBackupRestore.java
index d545e7b4a..a0d38523c 100644
--- a/src/com/android/email/AccountBackupRestore.java
+++ b/src/com/android/email/AccountBackupRestore.java
@@ -71,7 +71,7 @@ public class AccountBackupRestore {
// after restoring accounts, register services appropriately
Log.w(Logging.LOG_TAG, "Register services after restoring accounts");
// update security profile
- SecurityPolicy.getInstance(context).updatePolicies(-1);
+ SecurityPolicy.getInstance(context).policiesUpdated(-1);
// enable/disable other email services as necessary
Email.setServicesEnabledSync(context);
ExchangeUtils.startExchangeService(context);
diff --git a/src/com/android/email/Controller.java b/src/com/android/email/Controller.java
index 280b6eac7..ed130e316 100644
--- a/src/com/android/email/Controller.java
+++ b/src/com/android/email/Controller.java
@@ -37,6 +37,7 @@ import com.android.emailcommon.service.IEmailServiceCallback;
import com.android.emailcommon.utility.AttachmentUtilities;
import com.android.emailcommon.utility.EmailAsyncTask;
import com.android.emailcommon.utility.Utility;
+import com.google.common.annotations.VisibleForTesting;
import android.app.Service;
import android.content.ContentResolver;
@@ -1006,6 +1007,15 @@ public class Controller {
}
/**
+ * Backup our accounts; define this here so that unit tests can override the behavior
+ * @param context the caller's context
+ */
+ @VisibleForTesting
+ protected void backupAccounts(Context context) {
+ AccountBackupRestore.backupAccounts(context);
+ }
+
+ /**
* Delete an account synchronously.
*/
public void deleteAccountSync(long accountId, Context context) {
@@ -1030,8 +1040,7 @@ public class Controller {
EmailContent.Account.CONTENT_URI, accountId);
context.getContentResolver().delete(uri, null, null);
- // Update the backup (side copy) of the accounts
- AccountBackupRestore.backupAccounts(context);
+ backupAccounts(context);
// Release or relax device administration, if relevant
SecurityPolicy.getInstance(context).reducePolicies();
diff --git a/src/com/android/email/LegacyConversions.java b/src/com/android/email/LegacyConversions.java
index c937fbc38..10ec6d33b 100644
--- a/src/com/android/email/LegacyConversions.java
+++ b/src/com/android/email/LegacyConversions.java
@@ -26,9 +26,9 @@ import com.android.emailcommon.internet.TextBody;
import com.android.emailcommon.mail.Address;
import com.android.emailcommon.mail.Flag;
import com.android.emailcommon.mail.Message;
+import com.android.emailcommon.mail.Message.RecipientType;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.mail.Part;
-import com.android.emailcommon.mail.Message.RecipientType;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
@@ -475,7 +475,6 @@ public class LegacyConversions {
result.setRingtone(fromAccount.mRingtoneUri);
result.mProtocolVersion = fromAccount.mProtocolVersion;
// int fromAccount.mNewMessageCount = will be reset on next sync
- result.mSecurityFlags = fromAccount.mSecurityFlags;
result.mSignature = fromAccount.mSignature;
// Use the existing conversions from HostAuth <-> Uri
@@ -518,10 +517,9 @@ public class LegacyConversions {
result.setRingtone(fromAccount.getRingtone());
result.mProtocolVersion = fromAccount.mProtocolVersion;
result.mNewMessageCount = 0;
- result.mSecurityFlags = fromAccount.mSecurityFlags;
result.mSecuritySyncKey = null;
+ result.mPolicyKey = 0;
result.mSignature = fromAccount.mSignature;
-
try {
HostAuth recvAuth = result.getOrCreateHostAuthRecv(context);
Utility.setHostAuthFromString(recvAuth, fromAccount.getStoreUri());
diff --git a/src/com/android/email/SecurityPolicy.java b/src/com/android/email/SecurityPolicy.java
index 557156b7e..f8c482683 100644
--- a/src/com/android/email/SecurityPolicy.java
+++ b/src/com/android/email/SecurityPolicy.java
@@ -16,13 +16,15 @@
package com.android.email;
-import com.android.email.activity.setup.AccountSecurity;
import com.android.email.service.EmailBroadcastProcessorService;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.AccountColumns;
-import com.android.emailcommon.service.PolicySet;
+import com.android.emailcommon.provider.EmailContent.PolicyColumns;
+import com.android.emailcommon.provider.Policy;
+import com.android.emailcommon.utility.Utility;
+import com.google.common.annotations.VisibleForTesting;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DeviceAdminReceiver;
@@ -41,24 +43,12 @@ import android.util.Log;
* into and out of various security states.
*/
public class SecurityPolicy {
- private static final String TAG = "SecurityPolicy";
+ private static final String TAG = "Email/SecurityPolicy";
private static SecurityPolicy sInstance = null;
private Context mContext;
private DevicePolicyManager mDPM;
private ComponentName mAdminName;
- private PolicySet mAggregatePolicy;
-
- /* package */ static final PolicySet NO_POLICY_SET =
- new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0, false, false);
-
- /**
- * This projection on Account is for scanning/reading
- */
- private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] {
- AccountColumns.ID, AccountColumns.SECURITY_FLAGS
- };
- private static final int ACCOUNT_SECURITY_COLUMN_ID = 0;
- private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1;
+ private Policy mAggregatePolicy;
// Messages used for DevicePolicyManager callbacks
private static final int DEVICE_ADMIN_MESSAGE_ENABLED = 1;
@@ -66,6 +56,9 @@ public class SecurityPolicy {
private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED = 3;
private static final int DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING = 4;
+ private static final String HAS_PASSWORD_EXPIRATION =
+ PolicyColumns.PASSWORD_EXPIRATION_DAYS + ">0";
+
/**
* Get the security policy instance
*/
@@ -111,77 +104,86 @@ public class SecurityPolicy {
* @return a policy representing the strongest aggregate. If no policy sets are defined,
* a lightweight "nothing required" policy will be returned. Never null.
*/
- /*package*/ PolicySet computeAggregatePolicy() {
+ /*package*/ Policy computeAggregatePolicy() {
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;
- int passwordHistory = Integer.MIN_VALUE;
- int passwordExpirationDays = Integer.MAX_VALUE;
- int passwordComplexChars = Integer.MIN_VALUE;
- boolean requireEncryption = false;
- boolean requireEncryptionExternal = false;
-
- Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
- ACCOUNT_SECURITY_PROJECTION, Account.SECURITY_NONZERO_SELECTION, null, null);
+ Policy aggregate = new Policy();
+ aggregate.mPasswordMinLength = Integer.MIN_VALUE;
+ aggregate.mPasswordMode = Integer.MIN_VALUE;
+ aggregate.mPasswordMaxFails = Integer.MAX_VALUE;
+ aggregate.mPasswordHistory = Integer.MIN_VALUE;
+ aggregate.mPasswordExpirationDays = Integer.MAX_VALUE;
+ aggregate.mPasswordComplexChars = Integer.MIN_VALUE;
+ aggregate.mMaxScreenLockTime = Integer.MAX_VALUE;
+ aggregate.mRequireRemoteWipe = false;
+ aggregate.mRequireEncryption = false;
+ aggregate.mRequireEncryptionExternal = false;
+
+ Cursor c = mContext.getContentResolver().query(Policy.CONTENT_URI,
+ Policy.CONTENT_PROJECTION, null, null, null);
+ Policy policy = new Policy();
try {
while (c.moveToNext()) {
- long flags = c.getLong(ACCOUNT_SECURITY_COLUMN_FLAGS);
- if (flags != 0) {
- PolicySet p = new PolicySet(flags);
- minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
- passwordMode = Math.max(p.mPasswordMode, passwordMode);
- if (p.mMaxPasswordFails > 0) {
- maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
- }
- if (p.mMaxScreenLockTime > 0) {
- maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime);
- }
- if (p.mPasswordHistory > 0) {
- passwordHistory = Math.max(p.mPasswordHistory, passwordHistory);
- }
- if (p.mPasswordExpirationDays > 0) {
- passwordExpirationDays =
- Math.min(p.mPasswordExpirationDays, passwordExpirationDays);
- }
- if (p.mPasswordComplexChars > 0) {
- passwordComplexChars = Math.max(p.mPasswordComplexChars,
- passwordComplexChars);
- }
- requireRemoteWipe |= p.mRequireRemoteWipe;
- requireEncryption |= p.mRequireEncryption;
- requireEncryptionExternal |= p.mRequireEncryptionExternal;
- policiesFound = true;
+ policy.restore(c);
+ if (Email.DEBUG) {
+ Log.d(TAG, "Aggregate from: " + policy);
+ }
+ aggregate.mPasswordMinLength =
+ Math.max(policy.mPasswordMinLength, aggregate.mPasswordMinLength);
+ aggregate.mPasswordMode = Math.max(policy.mPasswordMode, aggregate.mPasswordMode);
+ if (policy.mPasswordMaxFails > 0) {
+ aggregate.mPasswordMaxFails =
+ Math.min(policy.mPasswordMaxFails, aggregate.mPasswordMaxFails);
+ }
+ if (policy.mMaxScreenLockTime > 0) {
+ aggregate.mMaxScreenLockTime = Math.min(policy.mMaxScreenLockTime,
+ aggregate.mMaxScreenLockTime);
+ }
+ if (policy.mPasswordHistory > 0) {
+ aggregate.mPasswordHistory =
+ Math.max(policy.mPasswordHistory, aggregate.mPasswordHistory);
}
+ if (policy.mPasswordExpirationDays > 0) {
+ aggregate.mPasswordExpirationDays =
+ Math.min(policy.mPasswordExpirationDays, aggregate.mPasswordExpirationDays);
+ }
+ if (policy.mPasswordComplexChars > 0) {
+ aggregate.mPasswordComplexChars = Math.max(policy.mPasswordComplexChars,
+ aggregate.mPasswordComplexChars);
+ }
+ aggregate.mRequireRemoteWipe |= policy.mRequireRemoteWipe;
+ aggregate.mRequireEncryption |= policy.mRequireEncryption;
+ aggregate.mRequireEncryptionExternal |= policy.mRequireEncryptionExternal;
+ policiesFound = true;
}
} finally {
c.close();
}
if (policiesFound) {
// 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;
- if (passwordHistory == Integer.MIN_VALUE) passwordHistory = 0;
- if (passwordExpirationDays == Integer.MAX_VALUE) passwordExpirationDays = 0;
- if (passwordComplexChars == Integer.MIN_VALUE) passwordComplexChars = 0;
-
- return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
- maxScreenLockTime, requireRemoteWipe, passwordExpirationDays, passwordHistory,
- passwordComplexChars, requireEncryption, requireEncryptionExternal);
- } else {
- return NO_POLICY_SET;
+ if (aggregate.mPasswordMinLength == Integer.MIN_VALUE) aggregate.mPasswordMinLength = 0;
+ if (aggregate.mPasswordMode == Integer.MIN_VALUE) aggregate.mPasswordMode = 0;
+ if (aggregate.mPasswordMaxFails == Integer.MAX_VALUE) aggregate.mPasswordMaxFails = 0;
+ if (aggregate.mMaxScreenLockTime == Integer.MAX_VALUE) aggregate.mMaxScreenLockTime = 0;
+ if (aggregate.mPasswordHistory == Integer.MIN_VALUE) aggregate.mPasswordHistory = 0;
+ if (aggregate.mPasswordExpirationDays == Integer.MAX_VALUE)
+ aggregate.mPasswordExpirationDays = 0;
+ if (aggregate.mPasswordComplexChars == Integer.MIN_VALUE)
+ aggregate.mPasswordComplexChars = 0;
+ if (Email.DEBUG) {
+ Log.d(TAG, "Calculated Aggregate: " + aggregate);
+ }
+ return aggregate;
}
+ if (Email.DEBUG) {
+ Log.d(TAG, "Calculated Aggregate: no policy");
+ }
+ return Policy.NO_POLICY;
}
/**
* Return updated aggregate policy, from cached value if possible
*/
- public synchronized PolicySet getAggregatePolicy() {
+ public synchronized Policy getAggregatePolicy() {
if (mAggregatePolicy == null) {
mAggregatePolicy = computeAggregatePolicy();
}
@@ -202,7 +204,7 @@ 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
*/
- public synchronized void updatePolicies(long accountId) {
+ public synchronized void policiesUpdated(long accountId) {
mAggregatePolicy = null;
}
@@ -213,7 +215,10 @@ public class SecurityPolicy {
* rollbacks.
*/
public void reducePolicies() {
- updatePolicies(-1);
+ if (Email.DEBUG) {
+ Log.d(TAG, "reducePolicies");
+ }
+ policiesUpdated(-1);
setActivePolicies();
}
@@ -223,20 +228,20 @@ public class SecurityPolicy {
* @param policies the polices that were requested
* @return boolean if supported
*/
- public boolean isSupported(PolicySet policies) {
+ 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 (policies.mRequireEncryption) {
+ if (policy.mRequireEncryption) {
int encryptionStatus = getDPM().getStorageEncryptionStatus();
if (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED) {
return false;
}
}
- if (policies.mRequireEncryptionExternal) {
+ if (policy.mRequireEncryptionExternal) {
// At this time, we only support "external encryption" when it is provided by virtue
// of emulating the external storage inside an encrypted device.
- if (!policies.mRequireEncryption) return false;
+ if (!policy.mRequireEncryption) return false;
if (Environment.isExternalStorageRemovable()) return false;
if (!Environment.isExternalStorageEmulated()) return false;
}
@@ -253,34 +258,25 @@ public class SecurityPolicy {
* @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;
+ 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 (policies.mRequireEncryption) {
+ if (policy.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, false);
+ policy.mRequireEncryption = false;
}
}
// At this time, we only support "external encryption" when it is provided by virtue
// of emulating the external storage inside an encrypted device.
- if (policies.mRequireEncryptionExternal) {
+ if (policy.mRequireEncryptionExternal) {
if (Environment.isExternalStorageRemovable()
|| !Environment.isExternalStorageEmulated()) {
- // 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, false);
+ policy.mRequireEncryptionExternal = false;
}
}
- return result;
+ return policy;
}
/**
@@ -290,8 +286,29 @@ public class SecurityPolicy {
* @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);
+ public boolean isActive(Policy policy) {
+ int reasons = getInactiveReasons(policy);
+ if (Email.DEBUG && (reasons != 0)) {
+ StringBuilder sb = new StringBuilder("isActive for " + policy + ": ");
+ if (reasons == 0) {
+ sb.append("true");
+ } else {
+ sb.append("FALSE -> ");
+ }
+ if ((reasons & INACTIVE_NEED_ACTIVATION) != 0) {
+ sb.append("no_admin ");
+ }
+ if ((reasons & INACTIVE_NEED_CONFIGURATION) != 0) {
+ sb.append("config ");
+ }
+ if ((reasons & INACTIVE_NEED_PASSWORD) != 0) {
+ sb.append("password ");
+ }
+ if ((reasons & INACTIVE_NEED_ENCRYPTION) != 0) {
+ sb.append("encryption ");
+ }
+ Log.d(TAG, sb.toString());
+ }
return reasons == 0;
}
@@ -335,43 +352,43 @@ public class SecurityPolicy {
* @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.
*/
- public int getInactiveReasons(PolicySet policies) {
+ public int getInactiveReasons(Policy policy) {
// select aggregate set if needed
- if (policies == null) {
- policies = getAggregatePolicy();
+ if (policy == null) {
+ policy = getAggregatePolicy();
}
// quick check for the "empty set" of no policies
- if (policies == NO_POLICY_SET) {
+ if (policy == Policy.NO_POLICY) {
return 0;
}
int reasons = 0;
DevicePolicyManager dpm = getDPM();
if (isActiveAdmin()) {
// check each policy explicitly
- if (policies.mMinPasswordLength > 0) {
- if (dpm.getPasswordMinimumLength(mAdminName) < policies.mMinPasswordLength) {
+ if (policy.mPasswordMinLength > 0) {
+ if (dpm.getPasswordMinimumLength(mAdminName) < policy.mPasswordMinLength) {
reasons |= INACTIVE_NEED_PASSWORD;
}
}
- if (policies.mPasswordMode > 0) {
- if (dpm.getPasswordQuality(mAdminName) < policies.getDPManagerPasswordQuality()) {
+ if (policy.mPasswordMode > 0) {
+ if (dpm.getPasswordQuality(mAdminName) < policy.getDPManagerPasswordQuality()) {
reasons |= INACTIVE_NEED_PASSWORD;
}
if (!dpm.isActivePasswordSufficient()) {
reasons |= INACTIVE_NEED_PASSWORD;
}
}
- if (policies.mMaxScreenLockTime > 0) {
+ if (policy.mMaxScreenLockTime > 0) {
// Note, we use seconds, dpm uses milliseconds
- if (dpm.getMaximumTimeToLock(mAdminName) > policies.mMaxScreenLockTime * 1000) {
+ if (dpm.getMaximumTimeToLock(mAdminName) > policy.mMaxScreenLockTime * 1000) {
reasons |= INACTIVE_NEED_CONFIGURATION;
}
}
- if (policies.mPasswordExpirationDays > 0) {
+ if (policy.mPasswordExpirationDays > 0) {
// confirm that expirations are currently set
long currentTimeout = dpm.getPasswordExpirationTimeout(mAdminName);
if (currentTimeout == 0
- || currentTimeout > policies.getDPManagerPasswordExpirationTimeout()) {
+ || currentTimeout > policy.getDPManagerPasswordExpirationTimeout()) {
reasons |= INACTIVE_NEED_PASSWORD;
}
// confirm that the current password hasn't expired
@@ -382,17 +399,17 @@ public class SecurityPolicy {
reasons |= INACTIVE_NEED_PASSWORD;
}
}
- if (policies.mPasswordHistory > 0) {
- if (dpm.getPasswordHistoryLength(mAdminName) < policies.mPasswordHistory) {
+ if (policy.mPasswordHistory > 0) {
+ if (dpm.getPasswordHistoryLength(mAdminName) < policy.mPasswordHistory) {
reasons |= INACTIVE_NEED_PASSWORD;
}
}
- if (policies.mPasswordComplexChars > 0) {
- if (dpm.getPasswordMinimumNonLetter(mAdminName) < policies.mPasswordComplexChars) {
+ if (policy.mPasswordComplexChars > 0) {
+ if (dpm.getPasswordMinimumNonLetter(mAdminName) < policy.mPasswordComplexChars) {
reasons |= INACTIVE_NEED_PASSWORD;
}
}
- if (policies.mRequireEncryption) {
+ if (policy.mRequireEncryption) {
int encryptionStatus = getDPM().getStorageEncryptionStatus();
if (encryptionStatus != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) {
reasons |= INACTIVE_NEED_ENCRYPTION;
@@ -421,24 +438,30 @@ public class SecurityPolicy {
public void setActivePolicies() {
DevicePolicyManager dpm = getDPM();
// compute aggregate set of policies
- PolicySet policies = getAggregatePolicy();
+ Policy aggregatePolicy = getAggregatePolicy();
// if empty set, detach from policy manager
- if (policies == NO_POLICY_SET) {
+ if (aggregatePolicy == Policy.NO_POLICY) {
+ if (Email.DEBUG) {
+ Log.d(TAG, "setActivePolicies: none, remove admin");
+ }
dpm.removeActiveAdmin(mAdminName);
} else if (isActiveAdmin()) {
+ if (Email.DEBUG) {
+ Log.d(TAG, "setActivePolicies: " + aggregatePolicy);
+ }
// set each policy in the policy manager
// password mode & length
- dpm.setPasswordQuality(mAdminName, policies.getDPManagerPasswordQuality());
- dpm.setPasswordMinimumLength(mAdminName, policies.mMinPasswordLength);
+ dpm.setPasswordQuality(mAdminName, aggregatePolicy.getDPManagerPasswordQuality());
+ dpm.setPasswordMinimumLength(mAdminName, aggregatePolicy.mPasswordMinLength);
// screen lock time
- dpm.setMaximumTimeToLock(mAdminName, policies.mMaxScreenLockTime * 1000);
+ dpm.setMaximumTimeToLock(mAdminName, aggregatePolicy.mMaxScreenLockTime * 1000);
// local wipe (failed passwords limit)
- dpm.setMaximumFailedPasswordsForWipe(mAdminName, policies.mMaxPasswordFails);
+ dpm.setMaximumFailedPasswordsForWipe(mAdminName, aggregatePolicy.mPasswordMaxFails);
// password expiration (days until a password expires). API takes mSec.
dpm.setPasswordExpirationTimeout(mAdminName,
- policies.getDPManagerPasswordExpirationTimeout());
+ aggregatePolicy.getDPManagerPasswordExpirationTimeout());
// password history length (number of previous passwords that may not be reused)
- dpm.setPasswordHistoryLength(mAdminName, policies.mPasswordHistory);
+ dpm.setPasswordHistoryLength(mAdminName, aggregatePolicy.mPasswordHistory);
// password minimum complex characters.
// Note, in Exchange, "complex chars" simply means "non alpha", but in the DPM,
// setting the quality to complex also defaults min symbols=1 and min numeric=1.
@@ -446,9 +469,9 @@ public class SecurityPolicy {
// configuration in which we explicitly require a minimum number of digits or symbols.)
dpm.setPasswordMinimumSymbols(mAdminName, 0);
dpm.setPasswordMinimumNumeric(mAdminName, 0);
- dpm.setPasswordMinimumNonLetter(mAdminName, policies.mPasswordComplexChars);
+ dpm.setPasswordMinimumNonLetter(mAdminName, aggregatePolicy.mPasswordComplexChars);
// encryption required
- dpm.setStorageEncryption(mAdminName, policies.mRequireEncryption);
+ dpm.setStorageEncryption(mAdminName, aggregatePolicy.mRequireEncryption);
// TODO: If we ever support external storage encryption as a first-class feature,
// it will need to be set here. For now, if there is a policy request for
// external storage encryption, it's sufficient that we've activated internal
@@ -497,6 +520,18 @@ public class SecurityPolicy {
Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
// In case the account has been deleted, just return
if (account == 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);
+ }
+ }
+ }
// Mark the account as "on hold".
setAccountHoldFlag(mContext, account, true);
@@ -559,7 +594,7 @@ public class SecurityPolicy {
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);
+ Account.SECURITY_NONZERO_SELECTION, null, null);
try {
Log.w(TAG, "Email administration disabled; deleting " + c.getCount() +
" secured account(s)");
@@ -570,7 +605,7 @@ public class SecurityPolicy {
} finally {
c.close();
}
- updatePolicies(-1);
+ policiesUpdated(-1);
}
/**
@@ -626,27 +661,13 @@ public class SecurityPolicy {
* 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;
+ @VisibleForTesting
+ /*package*/ static long findShortestExpiration(Context context) {
+ long policyId = Utility.getFirstRowLong(context, Policy.CONTENT_URI, Policy.ID_PROJECTION,
+ HAS_PASSWORD_EXPIRATION, null, PolicyColumns.PASSWORD_EXPIRATION_DAYS + " ASC",
+ EmailContent.ID_PROJECTION_COLUMN, -1L);
+ if (policyId < 0) return -1L;
+ return Policy.getAccountIdWithPolicyKey(context, policyId);
}
/**
@@ -656,27 +677,24 @@ public class SecurityPolicy {
* @param controller
* @return true if one or more accounts were wiped
*/
- /* package */ static boolean wipeExpiredAccounts(Context context, Controller controller) {
+ @VisibleForTesting
+ /*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);
+ Cursor c = context.getContentResolver().query(Policy.CONTENT_URI,
+ Policy.ID_PROJECTION, HAS_PASSWORD_EXPIRATION, 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;
- }
- }
+ long policyId = c.getLong(Policy.ID_PROJECTION_COLUMN);
+ long accountId = Policy.getAccountIdWithPolicyKey(context, policyId);
+ if (accountId < 0) continue;
+ 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 {
diff --git a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java
index 6de156211..b0c721fc3 100644
--- a/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java
+++ b/src/com/android/email/activity/setup/AccountCheckSettingsFragment.java
@@ -23,8 +23,8 @@ import com.android.emailcommon.Logging;
import com.android.emailcommon.mail.MessagingException;
import com.android.emailcommon.provider.EmailContent.Account;
import com.android.emailcommon.provider.EmailContent.HostAuth;
+import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.EmailServiceProxy;
-import com.android.emailcommon.service.PolicySet;
import com.android.emailcommon.utility.Utility;
import android.app.Activity;
@@ -456,7 +456,7 @@ public class AccountCheckSettingsFragment extends Fragment {
EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE);
}
if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED) {
- SetupData.setPolicySet((PolicySet)bundle.getParcelable(
+ SetupData.setPolicy((Policy)bundle.getParcelable(
EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET));
return new MessagingException(resultCode, mStoreHost);
} else if (resultCode == MessagingException.SECURITY_POLICIES_UNSUPPORTED) {
diff --git a/src/com/android/email/activity/setup/AccountSecurity.java b/src/com/android/email/activity/setup/AccountSecurity.java
index ec15cb38d..7f066a570 100644
--- a/src/com/android/email/activity/setup/AccountSecurity.java
+++ b/src/com/android/email/activity/setup/AccountSecurity.java
@@ -16,6 +16,7 @@
package com.android.email.activity.setup;
+import com.android.email.Email;
import com.android.email.R;
import com.android.email.SecurityPolicy;
import com.android.email.activity.ActivityHelper;
@@ -35,6 +36,7 @@ import android.content.Intent;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.util.Log;
/**
* Psuedo-activity (no UI) to bootstrap the user up to a higher desired security level. This
@@ -48,6 +50,7 @@ import android.os.Bundle;
* 6. If necessary, request for user to activate device encryption
*/
public class AccountSecurity extends Activity {
+ private static final String TAG = "Email/AccountSecurity";
private static final String EXTRA_ACCOUNT_ID = "ACCOUNT_ID";
private static final String EXTRA_SHOW_DIALOG = "SHOW_DIALOG";
@@ -139,7 +142,7 @@ public class AccountSecurity extends Activity {
return;
}
// Otherwise, handle normal security settings flow
- if (mAccount.mSecurityFlags != 0) {
+ if (mAccount.mPolicyKey != 0) {
// This account wants to control security
if (showDialog) {
// Show dialog first, unless already showing (e.g. after rotation)
@@ -184,10 +187,12 @@ public class AccountSecurity extends Activity {
*/
private void tryAdvanceSecurity(Account account) {
SecurityPolicy security = SecurityPolicy.getInstance(this);
-
// Step 1. Check if we are an active device administrator, and stop here to activate
if (!security.isActiveAdmin()) {
if (mTriedAddAdministrator) {
+ if (Email.DEBUG) {
+ Log.d(TAG, "Not active admin: repost notification");
+ }
repostNotification(account, security);
finish();
} else {
@@ -195,9 +200,15 @@ public class AccountSecurity extends Activity {
// retrieve name of server for the format string
HostAuth hostAuth = HostAuth.restoreHostAuthWithId(this, account.mHostAuthKeyRecv);
if (hostAuth == null) {
+ if (Email.DEBUG) {
+ Log.d(TAG, "No HostAuth: repost notification");
+ }
repostNotification(account, security);
finish();
} else {
+ if (Email.DEBUG) {
+ Log.d(TAG, "Not active admin: post initial notification");
+ }
// try to become active - must happen here in activity, to get result
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
@@ -214,6 +225,9 @@ public class AccountSecurity extends Activity {
// Step 2. Check if the current aggregate security policy is being satisfied by the
// DevicePolicyManager (the current system security level).
if (security.isActive(null)) {
+ if (Email.DEBUG) {
+ Log.d(TAG, "Security active; clear holds");
+ }
Account.clearSecurityHoldOnAllAccounts(this);
finish();
return;
@@ -229,9 +243,15 @@ public class AccountSecurity extends Activity {
// Step 5. If password is needed, try to have the user set it
if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_PASSWORD) != 0) {
if (mTriedSetPassword) {
+ if (Email.DEBUG) {
+ Log.d(TAG, "Password needed; repost notification");
+ }
repostNotification(account, security);
finish();
} else {
+ if (Email.DEBUG) {
+ Log.d(TAG, "Password needed; request it via DPM");
+ }
mTriedSetPassword = true;
// launch the activity to have the user set a new password.
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
@@ -243,10 +263,16 @@ public class AccountSecurity extends Activity {
// Step 6. If encryption is needed, try to have the user set it
if ((inactiveReasons & SecurityPolicy.INACTIVE_NEED_ENCRYPTION) != 0) {
if (mTriedSetEncryption) {
+ if (Email.DEBUG) {
+ Log.d(TAG, "Encryption needed; repost notification");
+ }
repostNotification(account, security);
finish();
} else {
- mTriedSetEncryption = true;
+ if (Email.DEBUG) {
+ Log.d(TAG, "Encryption needed; request it via DPM");
+ }
+ mTriedSetEncryption = true;
// launch the activity to start up encryption.
Intent intent = new Intent(DevicePolicyManager.ACTION_START_ENCRYPTION);
startActivityForResult(intent, REQUEST_ENCRYPTION);
@@ -255,6 +281,9 @@ public class AccountSecurity extends Activity {
}
// Step 7. No problems were found, so clear holds and exit
+ if (Email.DEBUG) {
+ Log.d(TAG, "Policies enforced; clear holds");
+ }
Account.clearSecurityHoldOnAllAccounts(this);
finish();
}
@@ -304,6 +333,9 @@ public class AccountSecurity extends Activity {
b.setMessage(res.getString(R.string.account_security_dialog_content_fmt, accountName));
b.setPositiveButton(R.string.okay_action, this);
b.setNegativeButton(R.string.cancel_action, this);
+ if (Email.DEBUG) {
+ Log.d(TAG, "Posting security needed dialog");
+ }
return b.create();
}
@@ -318,9 +350,15 @@ public class AccountSecurity extends Activity {
}
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
+ if (Email.DEBUG) {
+ Log.d(TAG, "User accepts; advance to next step");
+ }
activity.tryAdvanceSecurity(activity.mAccount);
break;
case DialogInterface.BUTTON_NEGATIVE:
+ if (Email.DEBUG) {
+ Log.d(TAG, "User declines; repost notification");
+ }
activity.repostNotification(
activity.mAccount, SecurityPolicy.getInstance(activity));
activity.finish();
diff --git a/src/com/android/email/activity/setup/AccountSettingsUtils.java b/src/com/android/email/activity/setup/AccountSettingsUtils.java
index 3cadc45e9..0db2b9604 100644
--- a/src/com/android/email/activity/setup/AccountSettingsUtils.java
+++ b/src/com/android/email/activity/setup/AccountSettingsUtils.java
@@ -59,8 +59,8 @@ public class AccountSettingsUtils {
}
/**
- * Returns a set of content values to commit account changes (not including HostAuth) to
- * the database. Does not actually commit anything.
+ * Returns a set of content values to commit account changes (not including the foreign keys
+ * for the two host auth's and policy) to the database. Does not actually commit anything.
*/
public static ContentValues getAccountContentValues(EmailContent.Account account) {
ContentValues cv = new ContentValues();
@@ -72,7 +72,6 @@ public class AccountSettingsUtils {
cv.put(AccountColumns.RINGTONE_URI, account.mRingtoneUri);
cv.put(AccountColumns.FLAGS, account.mFlags);
cv.put(AccountColumns.SYNC_LOOKBACK, account.mSyncLookback);
- cv.put(AccountColumns.SECURITY_FLAGS, account.mSecurityFlags);
cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
return cv;
}
diff --git a/src/com/android/email/activity/setup/AccountSetupOptions.java b/src/com/android/email/activity/setup/AccountSetupOptions.java
index 8009856ec..e842cf755 100644
--- a/src/com/android/email/activity/setup/AccountSetupOptions.java
+++ b/src/com/android/email/activity/setup/AccountSetupOptions.java
@@ -26,7 +26,6 @@ import com.android.email.service.MailService;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
-import com.android.emailcommon.service.PolicySet;
import com.android.emailcommon.service.SyncWindow;
import com.android.emailcommon.utility.Utility;
@@ -238,11 +237,9 @@ public class AccountSetupOptions extends AccountSetupActivity implements OnClick
boolean contacts = false;
boolean email = mSyncEmailView.isChecked();
if (account.mHostAuthRecv.mProtocol.equals("eas")) {
- // Set security hold if necessary to prevent sync until policies are accepted
- PolicySet policySet = SetupData.getPolicySet();
- if (policySet != null && policySet.getSecurityCode() != 0) {
- account.mSecurityFlags = policySet.getSecurityCode();
+ if (SetupData.getPolicy() != null) {
account.mFlags |= Account.FLAGS_SECURITY_HOLD;
+ account.mPolicy = SetupData.getPolicy();
}
// Get flags for contacts/calendar sync
contacts = mSyncContactsView.isChecked();
diff --git a/src/com/android/email/activity/setup/SetupData.java b/src/com/android/email/activity/setup/SetupData.java
index 3d9575620..685d0462d 100644
--- a/src/com/android/email/activity/setup/SetupData.java
+++ b/src/com/android/email/activity/setup/SetupData.java
@@ -17,7 +17,7 @@
package com.android.email.activity.setup;
import com.android.emailcommon.provider.EmailContent.Account;
-import com.android.emailcommon.service.PolicySet;
+import com.android.emailcommon.provider.Policy;
import android.accounts.AccountAuthenticatorResponse;
import android.os.Bundle;
@@ -56,7 +56,7 @@ public class SetupData implements Parcelable {
private String mPassword;
private int mCheckSettingsMode = 0;
private boolean mAllowAutodiscover = true;
- private PolicySet mPolicySet;
+ private Policy mPolicy;
private boolean mAutoSetup = false;
private boolean mDefault = false;
private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null;
@@ -130,28 +130,30 @@ public class SetupData implements Parcelable {
getInstance().mAllowAutodiscover = mAllowAutodiscover;
}
- static public PolicySet getPolicySet() {
- return getInstance().mPolicySet;
+ static public Policy getPolicy() {
+ return getInstance().mPolicy;
}
- static public void setPolicySet(PolicySet mPolicySet) {
- getInstance().mPolicySet = mPolicySet;
+ static public void setPolicy(Policy policy) {
+ SetupData data = getInstance();
+ data.mPolicy = policy;
+ data.mAccount.mPolicy = policy;
}
static public boolean isAutoSetup() {
return getInstance().mAutoSetup;
}
- static public void setAutoSetup(boolean mAutoSetup) {
- getInstance().mAutoSetup = mAutoSetup;
+ static public void setAutoSetup(boolean autoSetup) {
+ getInstance().mAutoSetup = autoSetup;
}
static public boolean isDefault() {
return getInstance().mDefault;
}
- static public void setDefault(boolean mDefault) {
- getInstance().mDefault = mDefault;
+ static public void setDefault(boolean _default) {
+ getInstance().mDefault = _default;
}
static public AccountAuthenticatorResponse getAccountAuthenticatorResponse() {
@@ -176,7 +178,7 @@ public class SetupData implements Parcelable {
}
void commonInit() {
- mPolicySet = null;
+ mPolicy = null;
mAutoSetup = false;
mAllowAutodiscover = true;
mCheckSettingsMode = 0;
@@ -210,7 +212,7 @@ public class SetupData implements Parcelable {
dest.writeString(mPassword);
dest.writeInt(mCheckSettingsMode);
dest.writeInt(mAllowAutodiscover ? 1 : 0);
- dest.writeParcelable(mPolicySet, 0);
+ dest.writeParcelable(mPolicy, 0);
dest.writeInt(mAutoSetup ? 1 : 0);
dest.writeInt(mDefault ? 1 : 0);
dest.writeParcelable(mAccountAuthenticatorResponse, 0);
@@ -224,7 +226,7 @@ public class SetupData implements Parcelable {
mPassword = in.readString();
mCheckSettingsMode = in.readInt();
mAllowAutodiscover = in.readInt() == 1;
- mPolicySet = in.readParcelable(loader);
+ mPolicy = in.readParcelable(loader);
mAutoSetup = in.readInt() == 1;
mDefault = in.readInt() == 1;
mAccountAuthenticatorResponse = in.readParcelable(loader);
@@ -262,7 +264,7 @@ public class SetupData implements Parcelable {
if (SetupData.isCheckIncoming()) sb.append("in+");
if (SetupData.isCheckOutgoing()) sb.append("out+");
if (SetupData.isCheckAutodiscover()) sb.append("a/d");
- sb.append(":policy=" + (data.mPolicySet == null ? "none" : "exists"));
+ sb.append(":policy=" + (data.mPolicy == null ? "none" : "exists"));
return sb.toString();
}
}
diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java
index 75eae3b3d..f0d47ae74 100644
--- a/src/com/android/email/provider/EmailProvider.java
+++ b/src/com/android/email/provider/EmailProvider.java
@@ -33,7 +33,10 @@ import com.android.emailcommon.provider.EmailContent.Mailbox;
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.EmailContent.PolicyColumns;
import com.android.emailcommon.provider.EmailContent.SyncColumns;
+import com.android.emailcommon.provider.Policy;
+import com.android.emailcommon.service.LegacyPolicySet;
import com.google.common.annotations.VisibleForTesting;
import android.accounts.AccountManager;
@@ -53,6 +56,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
+import android.os.Debug;
import android.util.Log;
import java.io.File;
@@ -93,7 +97,9 @@ public class EmailProvider extends ContentProvider {
/*package*/ static final ContentCache sCacheMailbox =
new ContentCache("Mailbox", Mailbox.CONTENT_PROJECTION, 8);
private static final ContentCache sCacheMessage =
- new ContentCache("Message", Message.CONTENT_PROJECTION, 3);
+ new ContentCache("Message", Message.CONTENT_PROJECTION, 8);
+ private static final ContentCache sCachePolicy =
+ new ContentCache("Policy", Policy.CONTENT_PROJECTION, 4);
// Any changes to the database format *must* include update-in-place code.
// Original version: 3
@@ -114,7 +120,9 @@ public class EmailProvider extends ContentProvider {
// Version 17: Add parentKey to Mailbox table
// Version 18: Copy Mailbox.displayName to Mailbox.serverId for all IMAP & POP3 mailboxes.
// Column Mailbox.serverId is used for the server-side pathname of a mailbox.
- public static final int DATABASE_VERSION = 18;
+ // Version 19: Add Policy table; add policyKey to Account table and trigger to delete an
+ // Account's policy when the Account is deleted
+ public static final int DATABASE_VERSION = 19;
// Any changes to the database format *must* include update-in-place code.
// Original version: 2
@@ -158,8 +166,12 @@ public class EmailProvider extends ContentProvider {
private static final int DELETED_MESSAGE = DELETED_MESSAGE_BASE;
private static final int DELETED_MESSAGE_ID = DELETED_MESSAGE_BASE + 1;
+ private static final int POLICY_BASE = 0x7000;
+ private static final int POLICY = POLICY_BASE;
+ private static final int POLICY_ID = POLICY_BASE + 1;
+
// MUST ALWAYS EQUAL THE LAST OF THE PREVIOUS BASE CONSTANTS
- private static final int LAST_EMAIL_PROVIDER_DB_BASE = DELETED_MESSAGE_BASE;
+ private static final int LAST_EMAIL_PROVIDER_DB_BASE = POLICY_BASE;
// DO NOT CHANGE BODY_BASE!!
private static final int BODY_BASE = LAST_EMAIL_PROVIDER_DB_BASE + 0x1000;
@@ -178,6 +190,7 @@ public class EmailProvider extends ContentProvider {
EmailContent.HostAuth.TABLE_NAME,
EmailContent.Message.UPDATED_TABLE_NAME,
EmailContent.Message.DELETED_TABLE_NAME,
+ Policy.TABLE_NAME,
EmailContent.Body.TABLE_NAME
};
@@ -186,11 +199,13 @@ public class EmailProvider extends ContentProvider {
sCacheAccount,
sCacheMailbox,
sCacheMessage,
- null,
+ null, // Attachment
sCacheHostAuth,
- null,
- null,
- null};
+ null, // Updated message
+ null, // Deleted message
+ sCachePolicy,
+ null // Body
+ };
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@@ -230,6 +245,18 @@ public class EmailProvider extends ContentProvider {
" where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID +
"; end";
+ private static final String TRIGGER_ACCOUNT_DELETE =
+ "create trigger account_delete before delete on " + Account.TABLE_NAME +
+ " begin delete from " + Mailbox.TABLE_NAME +
+ " where " + MailboxColumns.ACCOUNT_KEY + "=old." + EmailContent.RECORD_ID +
+ "; delete from " + HostAuth.TABLE_NAME +
+ " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_RECV +
+ "; delete from " + HostAuth.TABLE_NAME +
+ " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_SEND +
+ "; delete from " + Policy.TABLE_NAME +
+ " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.POLICY_KEY +
+ "; end";
+
private static final ContentValues CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
public static final String MESSAGE_URI_PARAMETER_MAILBOX_ID = "mailboxId";
@@ -312,6 +339,9 @@ public class EmailProvider extends ContentProvider {
CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT = new ContentValues();
CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT.put(Account.NEW_MESSAGE_COUNT, 0);
+
+ matcher.addURI(EmailContent.AUTHORITY, "policy", POLICY);
+ matcher.addURI(EmailContent.AUTHORITY, "policy/#", POLICY_ID);
}
@@ -505,18 +535,12 @@ public class EmailProvider extends ContentProvider {
+ AccountColumns.NEW_MESSAGE_COUNT + " integer, "
+ AccountColumns.SECURITY_FLAGS + " integer, "
+ AccountColumns.SECURITY_SYNC_KEY + " text, "
- + AccountColumns.SIGNATURE + " text "
+ + AccountColumns.SIGNATURE + " text, "
+ + AccountColumns.POLICY_KEY + " integer"
+ ");";
db.execSQL("create table " + Account.TABLE_NAME + s);
// Deleting an account deletes associated Mailboxes and HostAuth's
- db.execSQL("create trigger account_delete before delete on " + Account.TABLE_NAME +
- " begin delete from " + Mailbox.TABLE_NAME +
- " where " + MailboxColumns.ACCOUNT_KEY + "=old." + EmailContent.RECORD_ID +
- "; delete from " + HostAuth.TABLE_NAME +
- " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_RECV +
- "; delete from " + HostAuth.TABLE_NAME +
- " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_SEND +
- "; end");
+ db.execSQL(TRIGGER_ACCOUNT_DELETE);
}
static void resetAccountTable(SQLiteDatabase db, int oldVersion, int newVersion) {
@@ -527,6 +551,22 @@ public class EmailProvider extends ContentProvider {
createAccountTable(db);
}
+ static void createPolicyTable(SQLiteDatabase db) {
+ String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
+ + PolicyColumns.PASSWORD_MODE + " integer, "
+ + PolicyColumns.PASSWORD_MIN_LENGTH + " integer, "
+ + PolicyColumns.PASSWORD_EXPIRATION_DAYS + " integer, "
+ + PolicyColumns.PASSWORD_HISTORY + " integer, "
+ + PolicyColumns.PASSWORD_COMPLEX_CHARS + " integer, "
+ + PolicyColumns.PASSWORD_MAX_FAILS + " integer, "
+ + PolicyColumns.MAX_SCREEN_LOCK_TIME + " integer, "
+ + PolicyColumns.REQUIRE_REMOTE_WIPE + " integer, "
+ + PolicyColumns.REQUIRE_ENCRYPTION + " integer, "
+ + PolicyColumns.REQUIRE_ENCRYPTION_EXTERNAL + " integer"
+ + ");";
+ db.execSQL("create table " + Policy.TABLE_NAME + s);
+ }
+
static void createHostAuthTable(SQLiteDatabase db) {
String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
+ HostAuthColumns.PROTOCOL + " text, "
@@ -783,6 +823,7 @@ public class EmailProvider extends ContentProvider {
createMailboxTable(db);
createHostAuthTable(db);
createAccountTable(db);
+ createPolicyTable(db);
}
@Override
@@ -951,6 +992,21 @@ public class EmailProvider extends ContentProvider {
upgradeFromVersion17ToVersion18(db);
oldVersion = 18;
}
+ if (oldVersion == 18) {
+ Debug.waitForDebugger();
+ try {
+ db.execSQL("alter table " + Account.TABLE_NAME
+ + " add column " + Account.POLICY_KEY + " integer;");
+ db.execSQL("drop trigger account_delete;");
+ db.execSQL(TRIGGER_ACCOUNT_DELETE);
+ createPolicyTable(db);
+ convertPolicyFlagsToPolicyTable(db);
+ } catch (SQLException e) {
+ // Shouldn't be needed unless we're debugging and interrupt the process
+ Log.w(TAG, "Exception upgrading EmailProvider.db from 18 to 19 " + e);
+ }
+ oldVersion = 19;
+ }
}
@Override
@@ -1009,6 +1065,7 @@ public class EmailProvider extends ContentProvider {
case MAILBOX_ID:
case ACCOUNT_ID:
case HOSTAUTH_ID:
+ case POLICY_ID:
id = uri.getPathSegments().get(1);
if (match == SYNCED_MESSAGE_ID) {
// For synced messages, first copy the old message to the deleted table and
@@ -1062,6 +1119,7 @@ public class EmailProvider extends ContentProvider {
case MAILBOX:
case ACCOUNT:
case HOSTAUTH:
+ case POLICY:
switch(match) {
// See the comments above for deletion of ACCOUNT_ID, etc
case ACCOUNT:
@@ -1185,6 +1243,7 @@ public class EmailProvider extends ContentProvider {
case MAILBOX:
case ACCOUNT:
case HOSTAUTH:
+ case POLICY:
id = db.insert(TABLE_NAMES[table], "foo", values);
resultUri = ContentUris.withAppendedId(uri, id);
// Clients shouldn't normally be adding rows to these tables, as they are
@@ -1298,6 +1357,7 @@ public class EmailProvider extends ContentProvider {
case MAILBOX_ID:
case ACCOUNT_ID:
case HOSTAUTH_ID:
+ case POLICY_ID:
return new MatrixCursor(projection, 0);
}
}
@@ -1331,6 +1391,7 @@ public class EmailProvider extends ContentProvider {
case MAILBOX:
case ACCOUNT:
case HOSTAUTH:
+ case POLICY:
c = db.query(tableName, projection,
selection, selectionArgs, null, null, sortOrder, limit);
break;
@@ -1342,6 +1403,7 @@ public class EmailProvider extends ContentProvider {
case MAILBOX_ID:
case ACCOUNT_ID:
case HOSTAUTH_ID:
+ case POLICY_ID:
id = uri.getPathSegments().get(1);
if (cache != null) {
c = cache.getCachedCursor(id, projection);
@@ -1617,6 +1679,25 @@ public class EmailProvider extends ContentProvider {
Mailbox.TABLE_NAME + "." + EmailContent.RECORD_ID + ")");
}
+ @VisibleForTesting
+ void convertPolicyFlagsToPolicyTable(SQLiteDatabase db) {
+ Debug.waitForDebugger();
+ Cursor c = db.query(Account.TABLE_NAME,
+ new String[] {EmailContent.RECORD_ID /*0*/, AccountColumns.SECURITY_FLAGS /*1*/},
+ AccountColumns.SECURITY_FLAGS + ">0", null, null, null, null);
+ ContentValues cv = new ContentValues();
+ String[] args = new String[1];
+ while (c.moveToNext()) {
+ long securityFlags = c.getLong(1 /*SECURITY_FLAGS*/);
+ Policy policy = LegacyPolicySet.flagsToPolicy(securityFlags);
+ long policyId = db.insert(Policy.TABLE_NAME, null, policy.toContentValues());
+ cv.put(AccountColumns.POLICY_KEY, policyId);
+ cv.putNull(AccountColumns.SECURITY_FLAGS);
+ args[0] = Long.toString(c.getLong(0 /*RECORD_ID*/));
+ db.update(Account.TABLE_NAME, cv, EmailContent.RECORD_ID + "=?", args);
+ }
+ }
+
/** Upgrades the database from v17 to v18 */
@VisibleForTesting
static void upgradeFromVersion17ToVersion18(SQLiteDatabase db) {
diff --git a/src/com/android/email/service/PolicyService.java b/src/com/android/email/service/PolicyService.java
index ace65a716..a97f65954 100644
--- a/src/com/android/email/service/PolicyService.java
+++ b/src/com/android/email/service/PolicyService.java
@@ -17,8 +17,8 @@
package com.android.email.service;
import com.android.email.SecurityPolicy;
+import com.android.emailcommon.provider.Policy;
import com.android.emailcommon.service.IPolicyService;
-import com.android.emailcommon.service.PolicySet;
import android.app.Service;
import android.content.Context;
@@ -31,16 +31,16 @@ public class PolicyService extends Service {
private Context mContext;
private final IPolicyService.Stub mBinder = new IPolicyService.Stub() {
- public boolean isActive(PolicySet policies) {
- return mSecurityPolicy.isActive(policies);
+ public boolean isActive(Policy policy) {
+ return mSecurityPolicy.isActive(policy);
}
public void policiesRequired(long accountId) {
mSecurityPolicy.policiesRequired(accountId);
}
- public void updatePolicies(long accountId) {
- mSecurityPolicy.updatePolicies(accountId);
+ public void policiesUpdated(long accountId) {
+ mSecurityPolicy.policiesUpdated(accountId);
}
public void setAccountHoldFlag(long accountId, boolean newState) {
@@ -55,12 +55,12 @@ public class PolicyService extends Service {
mSecurityPolicy.remoteWipe();
}
- public boolean isSupported(PolicySet policies) {
- return mSecurityPolicy.isSupported(policies);
+ public boolean isSupported(Policy policy) {
+ return mSecurityPolicy.isSupported(policy);
}
- public PolicySet clearUnsupportedPolicies(PolicySet policies) {
- return mSecurityPolicy.clearUnsupportedPolicies(policies);
+ public Policy clearUnsupportedPolicies(Policy policy) {
+ return mSecurityPolicy.clearUnsupportedPolicies(policy);
}
};
diff --git a/tests/src/com/android/email/LegacyConversionsTests.java b/tests/src/com/android/email/LegacyConversionsTests.java
index 4bc0954ff..6d31892a4 100644
--- a/tests/src/com/android/email/LegacyConversionsTests.java
+++ b/tests/src/com/android/email/LegacyConversionsTests.java
@@ -687,7 +687,6 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
assertEquals(tag + " ringtone", expect.getRingtone(), actual.mRingtoneUri);
assertEquals(tag + " proto vers", expect.mProtocolVersion, actual.mProtocolVersion);
assertEquals(tag + " new count", 0, actual.mNewMessageCount);
- assertEquals(tag + " security", expect.mSecurityFlags, actual.mSecurityFlags);
assertEquals(tag + " sec sync key", null, actual.mSecuritySyncKey);
assertEquals(tag + " signature", expect.mSignature, actual.mSignature);
}
@@ -728,7 +727,6 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
assertEquals(tag + " backup flags", 0, actual.mBackupFlags);
assertEquals(tag + " proto vers", expect.mProtocolVersion, actual.mProtocolVersion);
assertEquals(tag + " delete policy", expect.getDeletePolicy(), actual.getDeletePolicy());
- assertEquals(tag + " security", expect.mSecurityFlags, actual.mSecurityFlags);
assertEquals(tag + " signature", expect.mSignature, actual.mSignature);
}
}
diff --git a/tests/src/com/android/email/SecurityPolicyTests.java b/tests/src/com/android/email/SecurityPolicyTests.java
index f1f8039c0..53247ef7e 100644
--- a/tests/src/com/android/email/SecurityPolicyTests.java
+++ b/tests/src/com/android/email/SecurityPolicyTests.java
@@ -21,17 +21,14 @@ import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.EmailContent.Account;
-import com.android.emailcommon.provider.EmailContent.AccountColumns;
import com.android.emailcommon.provider.EmailContent.Mailbox;
import com.android.emailcommon.provider.EmailContent.Message;
-import com.android.emailcommon.service.PolicySet;
+import com.android.emailcommon.provider.Policy;
+import com.android.emailcommon.service.LegacyPolicySet;
import android.app.admin.DevicePolicyManager;
-import android.content.ContentUris;
-import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
-import android.net.Uri;
import android.test.ProviderTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
@@ -47,18 +44,18 @@ import android.test.suitebuilder.annotation.SmallTest;
public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
private Context mMockContext;
-
- private static final PolicySet EMPTY_POLICY_SET =
- new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0, false, false);
+ private SecurityPolicy mSecurityPolicy;
public SecurityPolicyTests() {
super(EmailProvider.class, EmailContent.AUTHORITY);
}
+ private static final Policy EMPTY_POLICY = new Policy();
+
@Override
protected void setUp() throws Exception {
super.setUp();
- mMockContext = new MockContext2(getMockContext(), this.mContext);
+ mMockContext = new MockContext2(getMockContext(), mContext);
// Invalidate all caches, since we reset the database for each test
ContentCache.invalidateAllCachesForTest();
}
@@ -102,88 +99,56 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
}
/**
- * Retrieve the security policy object, and inject the mock context so it works as expected
+ * Create a Policy using the arguments formerly used to create a PolicySet; this minimizes the
+ * changes needed for re-using the PolicySet unit test logic
*/
- private SecurityPolicy getSecurityPolicy() {
- SecurityPolicy sp = SecurityPolicy.getInstance(mMockContext);
- sp.setContext(mMockContext);
- return sp;
- }
-
- public void testPolicySetConstructor() {
- // We know that EMPTY_POLICY_SET doesn't generate an Exception or we wouldn't be here
- // Try some illegal parameters
- try {
- new PolicySet(100, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0, false, false);
- fail("Too-long password allowed");
- } catch (IllegalArgumentException e) {
- }
- try {
- new PolicySet(0, PolicySet.PASSWORD_MODE_STRONG + 1, 0, 0, false, 0, 0, 0, false,
- false);
- fail("Illegal password mode allowed");
- } catch (IllegalArgumentException e) {
- }
-
- PolicySet ps = new PolicySet(0, PolicySet.PASSWORD_MODE_SIMPLE, 0,
- PolicySet.SCREEN_LOCK_TIME_MAX + 1, false, 0, 0, 0, false, false);
- assertEquals(PolicySet.SCREEN_LOCK_TIME_MAX, ps.getMaxScreenLockTimeForTest());
-
- ps = new PolicySet(0, PolicySet.PASSWORD_MODE_SIMPLE,
- PolicySet.PASSWORD_MAX_FAILS_MAX + 1, 0, false, 0, 0, 0, false, false);
- assertEquals(PolicySet.PASSWORD_MAX_FAILS_MAX, ps.getMaxPasswordFailsForTest());
- // All password related fields should be zero when password mode is NONE
- // Illegal values for these fields should be ignored
- ps = new PolicySet(999/*length*/, PolicySet.PASSWORD_MODE_NONE,
- 999/*fails*/, 9999/*screenlock*/, false, 999/*expir*/, 999/*history*/,
- 999/*complex*/, false, false);
- assertEquals(0, ps.mMinPasswordLength);
- assertEquals(0, ps.mMaxScreenLockTime);
- assertEquals(0, ps.mMaxPasswordFails);
- assertEquals(0, ps.mPasswordExpirationDays);
- assertEquals(0, ps.mPasswordHistory);
- assertEquals(0, ps.mPasswordComplexChars);
-
- // With a simple password, we should set complex chars to zero
- ps = new PolicySet(4/*length*/, PolicySet.PASSWORD_MODE_SIMPLE,
- 0, 0, false, 0, 0, 3/*complex*/, false, false);
- assertEquals(4, ps.mMinPasswordLength);
- assertEquals(0, ps.mPasswordComplexChars);
+ private Policy setupPolicy(int minPasswordLength, int passwordMode, int maxPasswordFails,
+ int maxScreenLockTime, boolean requireRemoteWipe, int passwordExpirationDays,
+ int passwordHistory, int passwordComplexChars, boolean requireEncryption,
+ boolean requireEncryptionExternal) throws IllegalArgumentException {
+ Policy policy = new Policy();
+ policy.mPasswordMinLength = minPasswordLength;
+ policy.mPasswordMode = passwordMode;
+ policy.mPasswordMaxFails = maxPasswordFails;
+ policy.mMaxScreenLockTime = maxScreenLockTime;
+ policy.mRequireRemoteWipe = requireRemoteWipe;
+ policy.mPasswordExpirationDays = passwordExpirationDays;
+ policy.mPasswordHistory = passwordHistory;
+ policy.mPasswordComplexChars = passwordComplexChars;
+ policy.mRequireEncryption = requireEncryption;
+ policy.mRequireEncryptionExternal = requireEncryptionExternal;
+ return policy;
}
/**
* Test business logic of aggregating accounts with policies
*/
public void testAggregator() {
- SecurityPolicy sp = getSecurityPolicy();
+ mSecurityPolicy = SecurityPolicy.getInstance(mMockContext);
// with no accounts, should return empty set
- assertEquals(EMPTY_POLICY_SET, sp.computeAggregatePolicy());
+ assertEquals(EMPTY_POLICY, mSecurityPolicy.computeAggregatePolicy());
// with accounts having no security, empty set
- Account a1 = ProviderTestUtils.setupAccount("no-sec-1", false, mMockContext);
- a1.mSecurityFlags = 0;
- a1.save(mMockContext);
- Account a2 = ProviderTestUtils.setupAccount("no-sec-2", false, mMockContext);
- a2.mSecurityFlags = 0;
- a2.save(mMockContext);
- assertEquals(EMPTY_POLICY_SET, sp.computeAggregatePolicy());
+ ProviderTestUtils.setupAccount("no-sec-1", true, mMockContext);
+ ProviderTestUtils.setupAccount("no-sec-2", true, mMockContext);
+ assertEquals(EMPTY_POLICY, mSecurityPolicy.computeAggregatePolicy());
// with a single account in security mode, should return same security as in account
// first test with partially-populated policies
- Account a3 = ProviderTestUtils.setupAccount("sec-3", false, mMockContext);
- PolicySet p3ain = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0,
+ Account a3 = ProviderTestUtils.setupAccount("sec-3", true, mMockContext);
+ Policy p3ain = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0,
false, false);
- p3ain.writeAccount(a3, null, true, mMockContext);
- PolicySet p3aout = sp.computeAggregatePolicy();
+ p3ain.setAccountPolicy(mMockContext, a3, "0");
+ Policy p3aout = mSecurityPolicy.computeAggregatePolicy();
assertNotNull(p3aout);
assertEquals(p3ain, p3aout);
// Repeat that test with fully-populated policies
- PolicySet p3bin = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 15, 16, false, 6, 2, 3,
+ Policy p3bin = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 15, 16, false, 6, 2, 3,
false, false);
- p3bin.writeAccount(a3, null, true, mMockContext);
- PolicySet p3bout = sp.computeAggregatePolicy();
+ p3bin.setAccountPolicy(mMockContext, a3, "0");
+ Policy p3bout = mSecurityPolicy.computeAggregatePolicy();
assertNotNull(p3bout);
assertEquals(p3bin, p3bout);
@@ -195,15 +160,15 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
// max complex chars - max logic - will change
// encryption required - OR logic - will *not* change here because false
// encryption external req'd - OR logic - will *not* change here because false
- PolicySet p4in = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false, 0, 5, 7,
+ Policy p4in = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 0, 5, 7,
false, false);
- Account a4 = ProviderTestUtils.setupAccount("sec-4", false, mMockContext);
- p4in.writeAccount(a4, null, true, mMockContext);
- PolicySet p4out = sp.computeAggregatePolicy();
+ Account a4 = ProviderTestUtils.setupAccount("sec-4", true, mMockContext);
+ p4in.setAccountPolicy(mMockContext, a4, "0");
+ Policy p4out = mSecurityPolicy.computeAggregatePolicy();
assertNotNull(p4out);
- assertEquals(20, p4out.mMinPasswordLength);
- assertEquals(PolicySet.PASSWORD_MODE_STRONG, p4out.mPasswordMode);
- assertEquals(15, p4out.mMaxPasswordFails);
+ assertEquals(20, p4out.mPasswordMinLength);
+ assertEquals(Policy.PASSWORD_MODE_STRONG, p4out.mPasswordMode);
+ assertEquals(15, p4out.mPasswordMaxFails);
assertEquals(16, p4out.mMaxScreenLockTime);
assertEquals(6, p4out.mPasswordExpirationDays);
assertEquals(5, p4out.mPasswordHistory);
@@ -220,15 +185,15 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
// history & complex chars - will not change because 0 (unspecified)
// encryption required - OR logic - will change here because true
// encryption external req'd - OR logic - will *not* change here because false
- PolicySet p5in = new PolicySet(4, PolicySet.PASSWORD_MODE_SIMPLE, 5, 6, true, 1, 0, 0,
+ Policy p5in = setupPolicy(4, Policy.PASSWORD_MODE_SIMPLE, 5, 6, true, 1, 0, 0,
true, false);
- Account a5 = ProviderTestUtils.setupAccount("sec-5", false, mMockContext);
- p5in.writeAccount(a5, null, true, mMockContext);
- PolicySet p5out = sp.computeAggregatePolicy();
+ Account a5 = ProviderTestUtils.setupAccount("sec-5", true, mMockContext);
+ p5in.setAccountPolicy(mMockContext, a5, "0");
+ Policy p5out = mSecurityPolicy.computeAggregatePolicy();
assertNotNull(p5out);
- assertEquals(20, p5out.mMinPasswordLength);
- assertEquals(PolicySet.PASSWORD_MODE_STRONG, p5out.mPasswordMode);
- assertEquals(5, p5out.mMaxPasswordFails);
+ assertEquals(20, p5out.mPasswordMinLength);
+ assertEquals(Policy.PASSWORD_MODE_STRONG, p5out.mPasswordMode);
+ assertEquals(5, p5out.mPasswordMaxFails);
assertEquals(6, p5out.mMaxScreenLockTime);
assertEquals(1, p5out.mPasswordExpirationDays);
assertEquals(5, p5out.mPasswordHistory);
@@ -238,203 +203,27 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
// add another account that continues to mutate fields
// encryption external req'd - OR logic - will change here because true
- PolicySet p6in = new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0,
+ Policy p6in = setupPolicy(0, Policy.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0,
false, true);
- Account a6 = ProviderTestUtils.setupAccount("sec-6", false, mMockContext);
- p6in.writeAccount(a6, null, true, mMockContext);
- PolicySet p6out = sp.computeAggregatePolicy();
+ Account a6 = ProviderTestUtils.setupAccount("sec-6", true, mMockContext);
+ p6in.setAccountPolicy(mMockContext, a6, "0");
+ Policy p6out = mSecurityPolicy.computeAggregatePolicy();
assertNotNull(p6out);
assertTrue(p6out.mRequireEncryptionExternal);
}
/**
- * Make sure aggregator (and any other direct DB accessors) handle the case of upgraded
- * accounts properly (where the security flags will be NULL instead of zero).
- */
- public void testNullFlags() {
- SecurityPolicy sp = getSecurityPolicy();
-
- Account a1 = ProviderTestUtils.setupAccount("null-sec-1", true, mMockContext);
- ContentValues cv = new ContentValues();
- cv.putNull(AccountColumns.SECURITY_FLAGS);
- Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, a1.mId);
- mMockContext.getContentResolver().update(uri, cv, null, null);
-
- Account a2 = ProviderTestUtils.setupAccount("no-sec-2", false, mMockContext);
- a2.mSecurityFlags = 0;
- a2.save(mMockContext);
- assertEquals(EMPTY_POLICY_SET, sp.computeAggregatePolicy());
- }
-
- /**
- * Make sure the fields are encoded properly for their max ranges. This is looking
- * for any encoding mask/shift errors, which would cause bits to overflow into other fields.
- */
- @SmallTest
- public void testFieldIsolation() {
- // Check PASSWORD_LENGTH
- PolicySet p = new PolicySet(PolicySet.PASSWORD_LENGTH_MAX, PolicySet.PASSWORD_MODE_SIMPLE,
- 0, 0, false, 0, 0 ,0, false, false);
- assertEquals(PolicySet.PASSWORD_MODE_SIMPLE, p.mPasswordMode);
- assertEquals(PolicySet.PASSWORD_LENGTH_MAX, p.mMinPasswordLength);
- assertEquals(0, p.mMaxPasswordFails);
- assertEquals(0, p.mMaxScreenLockTime);
- assertEquals(0, p.mPasswordExpirationDays);
- assertEquals(0, p.mPasswordHistory);
- assertEquals(0, p.mPasswordComplexChars);
- assertFalse(p.mRequireRemoteWipe);
- assertFalse(p.mRequireEncryption);
- assertFalse(p.mRequireEncryptionExternal);
-
- // Check PASSWORD_MODE
- p = new PolicySet(0, PolicySet.PASSWORD_MODE_STRONG, 0, 0, false, 0, 0, 0, false, false);
- assertEquals(PolicySet.PASSWORD_MODE_STRONG, p.mPasswordMode);
- assertEquals(0, p.mMinPasswordLength);
- assertEquals(0, p.mMaxPasswordFails);
- assertEquals(0, p.mMaxScreenLockTime);
- assertEquals(0, p.mPasswordExpirationDays);
- assertEquals(0, p.mPasswordHistory);
- assertEquals(0, p.mPasswordComplexChars);
- assertFalse(p.mRequireRemoteWipe);
- assertFalse(p.mRequireEncryption);
- assertFalse(p.mRequireEncryptionExternal);
-
- // Check PASSWORD_FAILS (note, mode must be set for this to be non-zero)
- p = new PolicySet(0, PolicySet.PASSWORD_MODE_SIMPLE, PolicySet.PASSWORD_MAX_FAILS_MAX, 0,
- false, 0, 0, 0, false, false);
- assertEquals(PolicySet.PASSWORD_MODE_SIMPLE, p.mPasswordMode);
- assertEquals(0, p.mMinPasswordLength);
- assertEquals(PolicySet.PASSWORD_MAX_FAILS_MAX, p.mMaxPasswordFails);
- assertEquals(0, p.mMaxScreenLockTime);
- assertEquals(0, p.mPasswordExpirationDays);
- assertEquals(0, p.mPasswordHistory);
- assertEquals(0, p.mPasswordComplexChars);
- assertFalse(p.mRequireRemoteWipe);
- assertFalse(p.mRequireEncryption);
- assertFalse(p.mRequireEncryptionExternal);
-
- // Check SCREEN_LOCK_TIME (note, mode must be set for this to be non-zero)
- p = new PolicySet(0, PolicySet.PASSWORD_MODE_SIMPLE, 0, PolicySet.SCREEN_LOCK_TIME_MAX,
- false, 0, 0, 0, false, false);
- assertEquals(PolicySet.PASSWORD_MODE_SIMPLE, p.mPasswordMode);
- assertEquals(0, p.mMinPasswordLength);
- assertEquals(0, p.mMaxPasswordFails);
- assertEquals(PolicySet.SCREEN_LOCK_TIME_MAX, p.mMaxScreenLockTime);
- assertEquals(0, p.mPasswordExpirationDays);
- assertEquals(0, p.mPasswordHistory);
- assertEquals(0, p.mPasswordComplexChars);
- assertFalse(p.mRequireRemoteWipe);
- assertFalse(p.mRequireEncryption);
- assertFalse(p.mRequireEncryptionExternal);
-
- // Check REQUIRE_REMOTE_WIPE
- p = new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, true, 0, 0, 0, false, false);
- assertEquals(PolicySet.PASSWORD_MODE_NONE, p.mPasswordMode);
- assertEquals(0, p.mMinPasswordLength);
- assertEquals(0, p.mMaxPasswordFails);
- assertEquals(0, p.mMaxScreenLockTime);
- assertEquals(0, p.mPasswordExpirationDays);
- assertEquals(0, p.mPasswordHistory);
- assertEquals(0, p.mPasswordComplexChars);
- assertTrue(p.mRequireRemoteWipe);
- assertFalse(p.mRequireEncryption);
- assertFalse(p.mRequireEncryptionExternal);
-
- // Check PASSWORD_EXPIRATION (note, mode must be set for this to be non-zero)
- p = new PolicySet(0, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false,
- PolicySet.PASSWORD_EXPIRATION_MAX, 0, 0, false, false);
- assertEquals(PolicySet.PASSWORD_MODE_SIMPLE, p.mPasswordMode);
- assertEquals(0, p.mMinPasswordLength);
- assertEquals(0, p.mMaxPasswordFails);
- assertEquals(0, p.mMaxScreenLockTime);
- assertEquals(PolicySet.PASSWORD_EXPIRATION_MAX, p.mPasswordExpirationDays);
- assertEquals(0, p.mPasswordHistory);
- assertEquals(0, p.mPasswordComplexChars);
- assertFalse(p.mRequireRemoteWipe);
- assertFalse(p.mRequireEncryption);
- assertFalse(p.mRequireEncryptionExternal);
-
- // Check PASSWORD_HISTORY (note, mode must be set for this to be non-zero)
- p = new PolicySet(0, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false, 0,
- PolicySet.PASSWORD_HISTORY_MAX, 0, false, false);
- assertEquals(PolicySet.PASSWORD_MODE_SIMPLE, p.mPasswordMode);
- assertEquals(0, p.mMinPasswordLength);
- assertEquals(0, p.mMaxPasswordFails);
- assertEquals(0, p.mMaxScreenLockTime);
- assertEquals(0, p.mPasswordExpirationDays);
- assertEquals(PolicySet.PASSWORD_HISTORY_MAX, p.mPasswordHistory);
- assertEquals(0, p.mPasswordComplexChars);
- assertFalse(p.mRequireRemoteWipe);
- assertFalse(p.mRequireEncryption);
- assertFalse(p.mRequireEncryptionExternal);
-
- // Check PASSWORD_COMPLEX_CHARS (note, mode must be set for this to be non-zero)
- p = new PolicySet(0, PolicySet.PASSWORD_MODE_STRONG, 0, 0, false, 0, 0,
- PolicySet.PASSWORD_COMPLEX_CHARS_MAX, false, false);
- assertEquals(PolicySet.PASSWORD_MODE_STRONG, p.mPasswordMode);
- assertEquals(0, p.mMinPasswordLength);
- assertEquals(0, p.mMaxPasswordFails);
- assertEquals(0, p.mMaxScreenLockTime);
- assertEquals(0, p.mPasswordExpirationDays);
- assertEquals(0, p.mPasswordHistory);
- assertEquals(PolicySet.PASSWORD_COMPLEX_CHARS_MAX, p.mPasswordComplexChars);
- assertFalse(p.mRequireRemoteWipe);
- assertFalse(p.mRequireEncryption);
- assertFalse(p.mRequireEncryptionExternal);
-
- // Check REQUIRE_ENCRYPTION
- p = new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0, true, false);
- assertEquals(PolicySet.PASSWORD_MODE_NONE, p.mPasswordMode);
- assertEquals(0, p.mMinPasswordLength);
- assertEquals(0, p.mMaxPasswordFails);
- assertEquals(0, p.mMaxScreenLockTime);
- assertEquals(0, p.mPasswordExpirationDays);
- assertEquals(0, p.mPasswordHistory);
- assertEquals(0, p.mPasswordComplexChars);
- assertFalse(p.mRequireRemoteWipe);
- assertTrue(p.mRequireEncryption);
- assertFalse(p.mRequireEncryptionExternal);
-
- // Check REQUIRE_ENCRYPTION_EXTERNAL
- p = new PolicySet(0, PolicySet.PASSWORD_MODE_NONE, 0, 0, false, 0, 0, 0, false, true);
- assertEquals(PolicySet.PASSWORD_MODE_NONE, p.mPasswordMode);
- assertEquals(0, p.mMinPasswordLength);
- assertEquals(0, p.mMaxPasswordFails);
- assertEquals(0, p.mMaxScreenLockTime);
- assertEquals(0, p.mPasswordExpirationDays);
- assertEquals(0, p.mPasswordHistory);
- assertEquals(0, p.mPasswordComplexChars);
- assertFalse(p.mRequireRemoteWipe);
- assertFalse(p.mRequireEncryption);
- assertTrue(p.mRequireEncryptionExternal);
- }
-
- /**
- * Test encoding into an Account and out again
- */
- @SmallTest
- public void testAccountEncoding() {
- PolicySet p1 =
- new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, false);
- Account a = new Account();
- final String SYNC_KEY = "test_sync_key";
- p1.writeAccount(a, SYNC_KEY, false, null);
- PolicySet p2 = new PolicySet(a);
- assertEquals(p1, p2);
- }
-
- /**
* Test equality. Note, the tests for inequality are poor, as each field should
* be tested individually.
*/
@SmallTest
public void testEquals() {
- PolicySet p1 =
- new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, false);
- PolicySet p2 =
- new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, false);
- PolicySet p3 =
- new PolicySet(2, PolicySet.PASSWORD_MODE_SIMPLE, 5, 6, true, 7, 8, 9, false, false);
+ 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, false, false);
+ Policy p3 =
+ setupPolicy(2, Policy.PASSWORD_MODE_SIMPLE, 5, 6, true, 7, 8, 9, false, false);
assertTrue(p1.equals(p2));
assertFalse(p2.equals(p3));
}
@@ -443,8 +232,6 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
* Test the API to set/clear policy hold flags in an account
*/
public void testSetClearHoldFlag() {
- SecurityPolicy sp = getSecurityPolicy();
-
Account a1 = ProviderTestUtils.setupAccount("holdflag-1", false, mMockContext);
a1.mFlags = Account.FLAGS_NOTIFY_NEW_MAIL;
a1.save(mMockContext);
@@ -455,7 +242,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
// confirm clear until set
Account a1a = Account.restoreAccountWithId(mMockContext, a1.mId);
assertEquals(Account.FLAGS_NOTIFY_NEW_MAIL, a1a.mFlags);
- sp.setAccountHoldFlag(mMockContext, a1, true);
+ SecurityPolicy.setAccountHoldFlag(mMockContext, a1, true);
assertEquals(Account.FLAGS_NOTIFY_NEW_MAIL | Account.FLAGS_SECURITY_HOLD, a1.mFlags);
Account a1b = Account.restoreAccountWithId(mMockContext, a1.mId);
assertEquals(Account.FLAGS_NOTIFY_NEW_MAIL | Account.FLAGS_SECURITY_HOLD, a1b.mFlags);
@@ -463,108 +250,111 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
// confirm set until cleared
Account a2a = Account.restoreAccountWithId(mMockContext, a2.mId);
assertEquals(Account.FLAGS_VIBRATE_ALWAYS | Account.FLAGS_SECURITY_HOLD, a2a.mFlags);
- sp.setAccountHoldFlag(mMockContext, a2, false);
+ SecurityPolicy.setAccountHoldFlag(mMockContext, a2, false);
assertEquals(Account.FLAGS_VIBRATE_ALWAYS, a2.mFlags);
Account a2b = Account.restoreAccountWithId(mMockContext, a2.mId);
assertEquals(Account.FLAGS_VIBRATE_ALWAYS, a2b.mFlags);
}
-// private static class MockController extends Controller {
-// protected MockController(Context context) {
-// super(context);
-// }
-// }
+ private static class MockController extends Controller {
+ protected MockController(Context context) {
+ super(context);
+ }
+
+ protected void backupAccounts(Context context) {
+ // For testing, we don't want to back up our accounts
+ }
+ }
/**
* Test the response to disabling DeviceAdmin status
- *
- * TODO: Reenable the 2nd portion of this test - it fails because it gets into the Controller
- * and spins up an account backup on another thread.
*/
public void testDisableAdmin() {
- Account a1 = ProviderTestUtils.setupAccount("disable-1", false, mMockContext);
- PolicySet p1 = new PolicySet(10, PolicySet.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0,
+ Account a1 = ProviderTestUtils.setupAccount("disable-1", true, mMockContext);
+ Policy p1 = setupPolicy(10, Policy.PASSWORD_MODE_SIMPLE, 0, 0, false, 0, 0, 0,
false, false);
- p1.writeAccount(a1, "sync-key-1", true, mMockContext);
+ p1.setAccountPolicy(mMockContext, a1, "security-sync-key-1");
- Account a2 = ProviderTestUtils.setupAccount("disable-2", false, mMockContext);
- PolicySet p2 = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false, 0, 0, 0,
+ Account a2 = ProviderTestUtils.setupAccount("disable-2", true, mMockContext);
+ Policy p2 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 0, 0, 0,
false, false);
- p2.writeAccount(a2, "sync-key-2", true, mMockContext);
+ p2.setAccountPolicy(mMockContext, a2, "security-sync-key-2");
- Account a3 = ProviderTestUtils.setupAccount("disable-3", false, mMockContext);
- a3.mSecurityFlags = 0;
- a3.mSecuritySyncKey = null;
- a3.save(mMockContext);
+ Account a3 = ProviderTestUtils.setupAccount("disable-3", true, mMockContext);
+ Policy.clearAccountPolicy(mMockContext, a3);
- SecurityPolicy sp = getSecurityPolicy();
+ mSecurityPolicy = SecurityPolicy.getInstance(mMockContext);
- // Confirm that "enabling" device admin does not change security status (flags & sync key)
- PolicySet before = sp.getAggregatePolicy();
- sp.onAdminEnabled(true); // "enabled" should not change anything
- PolicySet after1 = sp.getAggregatePolicy();
+ // Confirm that "enabling" device admin does not change security status (policy & sync key)
+ Policy before = mSecurityPolicy.getAggregatePolicy();
+ mSecurityPolicy.onAdminEnabled(true); // "enabled" should not change anything
+ Policy after1 = mSecurityPolicy.getAggregatePolicy();
assertEquals(before, after1);
Account a1a = Account.restoreAccountWithId(mMockContext, a1.mId);
assertNotNull(a1a.mSecuritySyncKey);
+ assertTrue(a1a.mPolicyKey > 0);
Account a2a = Account.restoreAccountWithId(mMockContext, a2.mId);
assertNotNull(a2a.mSecuritySyncKey);
+ assertTrue(a2a.mPolicyKey > 0);
Account a3a = Account.restoreAccountWithId(mMockContext, a3.mId);
assertNull(a3a.mSecuritySyncKey);
+ assertTrue(a3a.mPolicyKey == 0);
// Simulate revoke of device admin; directly call deleteSecuredAccounts, which is normally
// called from a background thread
-// MockController mockController = new MockController(mMockContext);
-// Controller.injectMockControllerForTest(mockController);
-// try {
-// sp.deleteSecuredAccounts(mMockContext);
-// PolicySet after2 = sp.getAggregatePolicy();
-// assertEquals(SecurityPolicy.NO_POLICY_SET, after2);
-// Account a1b = Account.restoreAccountWithId(mMockContext, a1.mId);
-// assertNull(a1b);
-// Account a2b = Account.restoreAccountWithId(mMockContext, a2.mId);
-// assertNull(a2b);
-// Account a3b = Account.restoreAccountWithId(mMockContext, a3.mId);
-// assertNull(a3b.mSecuritySyncKey);
-// } finally {
-// Controller.injectMockControllerForTest(null);
-// }
+ MockController mockController = new MockController(mMockContext);
+ Controller.injectMockControllerForTest(mockController);
+ try {
+ mSecurityPolicy.deleteSecuredAccounts(mMockContext);
+ Policy after2 = mSecurityPolicy.getAggregatePolicy();
+ assertEquals(EMPTY_POLICY, after2);
+ Account a1b = Account.restoreAccountWithId(mMockContext, a1.mId);
+ assertNull(a1b);
+ Account a2b = Account.restoreAccountWithId(mMockContext, a2.mId);
+ assertNull(a2b);
+ Account a3b = Account.restoreAccountWithId(mMockContext, a3.mId);
+ assertNull(a3b.mSecuritySyncKey);
+ } finally {
+ Controller.injectMockControllerForTest(null);
+ }
}
/**
* Test the scanner that finds expiring accounts
*/
public void testFindExpiringAccount() {
- Account a1 = ProviderTestUtils.setupAccount("expiring-1", true, mMockContext);
+ ProviderTestUtils.setupAccount("expiring-1", true, mMockContext);
// With no expiring accounts, this should return null.
long nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext);
assertEquals(-1, nextExpiringAccountId);
// Add a single expiring account
- Account a2 = ProviderTestUtils.setupAccount("expiring-2", false, mMockContext);
- PolicySet p2 = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false, 30, 0, 0,
+ Account a2 =
+ ProviderTestUtils.setupAccount("expiring-2", true, mMockContext);
+ Policy p2 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 30, 0, 0,
false, false);
- p2.writeAccount(a2, "sync-key-2", true, mMockContext);
+ p2.setAccountPolicy(mMockContext, a2, "0");
// The expiring account should be returned
nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext);
assertEquals(a2.mId, nextExpiringAccountId);
// Add an account with a longer expiration
- Account a3 = ProviderTestUtils.setupAccount("expiring-3", false, mMockContext);
- PolicySet p3 = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false, 60, 0, 0,
+ Account a3 = ProviderTestUtils.setupAccount("expiring-3", true, mMockContext);
+ Policy p3 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 60, 0, 0,
false, false);
- p3.writeAccount(a3, "sync-key-3", true, mMockContext);
+ p3.setAccountPolicy(mMockContext, a3, "0");
// The original expiring account (a2) should be returned
nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext);
assertEquals(a2.mId, nextExpiringAccountId);
// Add an account with a shorter expiration
- Account a4 = ProviderTestUtils.setupAccount("expiring-4", false, mMockContext);
- PolicySet p4 = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false, 15, 0, 0,
+ Account a4 = ProviderTestUtils.setupAccount("expiring-4", true, mMockContext);
+ Policy p4 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 15, 0, 0,
false, false);
- p4.writeAccount(a4, "sync-key-4", true, mMockContext);
+ p4.setAccountPolicy(mMockContext, a4, "0");
// The new expiring account (a4) should be returned
nextExpiringAccountId = SecurityPolicy.findShortestExpiration(mMockContext);
@@ -586,15 +376,15 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
* Test the scanner that wipes expiring accounts
*/
public void testWipeExpiringAccounts() {
- SecurityPolicy sp = getSecurityPolicy();
+ mSecurityPolicy = SecurityPolicy.getInstance(mMockContext);
TestController testController = new TestController(mMockContext, getContext());
// Two accounts - a1 is normal, a2 has security (but no expiration)
Account a1 = ProviderTestUtils.setupAccount("expired-1", true, mMockContext);
- Account a2 = ProviderTestUtils.setupAccount("expired-2", false, mMockContext);
- PolicySet p2 = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false, 0, 0, 0,
+ Account a2 = ProviderTestUtils.setupAccount("expired-2", true, mMockContext);
+ Policy p2 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 0, 0, 0,
false, false);
- p2.writeAccount(a2, "sync-key-2", true, mMockContext);
+ p2.setAccountPolicy(mMockContext, a2, "0");
// Add a mailbox & messages to each account
long account1Id = a1.mId;
@@ -609,7 +399,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
ProviderTestUtils.setupMessage("message4", account2Id, box2Id, false, true, mMockContext);
// Run the expiration code - should do nothing
- boolean wiped = sp.wipeExpiredAccounts(mMockContext, testController);
+ boolean wiped = SecurityPolicy.wipeExpiredAccounts(mMockContext, testController);
assertFalse(wiped);
// check mailboxes & messages not wiped
assertEquals(2, EmailContent.count(mMockContext, Account.CONTENT_URI));
@@ -617,10 +407,10 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
assertEquals(4, EmailContent.count(mMockContext, Message.CONTENT_URI));
// Add 3rd account that really expires
- Account a3 = ProviderTestUtils.setupAccount("expired-3", false, mMockContext);
- PolicySet p3 = new PolicySet(20, PolicySet.PASSWORD_MODE_STRONG, 25, 26, false, 30, 0, 0,
+ Account a3 = ProviderTestUtils.setupAccount("expired-3", true, mMockContext);
+ Policy p3 = setupPolicy(20, Policy.PASSWORD_MODE_STRONG, 25, 26, false, 30, 0, 0,
false, false);
- p3.writeAccount(a3, "sync-key-3", true, mMockContext);
+ p3.setAccountPolicy(mMockContext, a3, "0");
// Add mailbox & messages to 3rd account
long account3Id = a3.mId;
@@ -635,7 +425,7 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
assertEquals(6, EmailContent.count(mMockContext, Message.CONTENT_URI));
// Run the expiration code - wipe acct #3
- wiped = sp.wipeExpiredAccounts(mMockContext, testController);
+ wiped = SecurityPolicy.wipeExpiredAccounts(mMockContext, testController);
assertTrue(wiped);
// check new counts - account survives but data is wiped
assertEquals(3, EmailContent.count(mMockContext, Account.CONTENT_URI));
@@ -656,21 +446,21 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
* TODO inject a mock DPM so we can directly control & test all cases, no matter what device
*/
public void testClearUnsupportedPolicies() {
- PolicySet p1 =
- new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, false);
- PolicySet p2 =
- new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, true, false);
- PolicySet p3 =
- new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, true);
-
- SecurityPolicy sp = getSecurityPolicy();
- DevicePolicyManager dpm = sp.getDPM();
+ 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);
+ Policy p3 =
+ setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, true);
+
+ mSecurityPolicy = SecurityPolicy.getInstance(mMockContext);
+ DevicePolicyManager dpm = mSecurityPolicy.getDPM();
boolean hasEncryption =
dpm.getStorageEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
- PolicySet p1Result = sp.clearUnsupportedPolicies(p1);
- PolicySet p2Result = sp.clearUnsupportedPolicies(p2);
- PolicySet p3Result = sp.clearUnsupportedPolicies(p3);
+ Policy p1Result = mSecurityPolicy.clearUnsupportedPolicies(p1);
+ Policy p2Result = mSecurityPolicy.clearUnsupportedPolicies(p2);
+ Policy p3Result = mSecurityPolicy.clearUnsupportedPolicies(p3);
// No changes expected when encryptionRequested bits were false
assertEquals(p1, p1Result);
@@ -682,8 +472,9 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
assertEquals(p3, p3Result);
} else {
// If encryption is unsupported, encryption policy bits are cleared
- PolicySet policyExpect =
- new PolicySet(1, PolicySet.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false, false);
+ Policy policyExpect =
+ setupPolicy(1, Policy.PASSWORD_MODE_STRONG, 3, 4, true, 7, 8, 9, false,
+ false);
assertEquals(policyExpect, p2Result);
assertEquals(policyExpect, p3Result);
}
@@ -693,28 +484,101 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
* Test the code that converts from exchange-style quality to DPM/Lockscreen style quality.
*/
public void testGetDPManagerPasswordQuality() {
- // PolicySet.PASSWORD_MODE_NONE -> DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
- PolicySet p1 = new PolicySet(0, PolicySet.PASSWORD_MODE_NONE,
+ // Policy.PASSWORD_MODE_NONE -> DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
+ Policy p1 = setupPolicy(0, Policy.PASSWORD_MODE_NONE,
0, 0, false, 0, 0, 0, false, false);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
p1.getDPManagerPasswordQuality());
// PASSWORD_MODE_SIMPLE -> PASSWORD_QUALITY_NUMERIC
- PolicySet p2 = new PolicySet(4, PolicySet.PASSWORD_MODE_SIMPLE,
+ Policy p2 = setupPolicy(4, Policy.PASSWORD_MODE_SIMPLE,
0, 0, false, 0, 0, 0, false, false);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
p2.getDPManagerPasswordQuality());
// PASSWORD_MODE_STRONG -> PASSWORD_QUALITY_ALPHANUMERIC
- PolicySet p3 = new PolicySet(4, PolicySet.PASSWORD_MODE_STRONG,
+ Policy p3 = setupPolicy(4, Policy.PASSWORD_MODE_STRONG,
0, 0, false, 0, 0, 0, false, false);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
p3.getDPManagerPasswordQuality());
// PASSWORD_MODE_STRONG + complex chars -> PASSWORD_QUALITY_COMPLEX
- PolicySet p4 = new PolicySet(4, PolicySet.PASSWORD_MODE_STRONG,
+ Policy p4 = setupPolicy(4, Policy.PASSWORD_MODE_STRONG,
0, 0, false, 0, 0 , 2, false, false);
assertEquals(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX,
p4.getDPManagerPasswordQuality());
}
+
+ private boolean policySetEqualsPolicy(PolicySet ps, Policy policy) {
+ if ((ps.mPasswordMode >> LegacyPolicySet.PASSWORD_MODE_SHIFT) != policy.mPasswordMode) {
+ return false;
+ }
+ if (ps.mMinPasswordLength != policy.mPasswordMinLength) return false;
+ if (ps.mPasswordComplexChars != policy.mPasswordComplexChars) return false;
+ if (ps.mPasswordHistory != policy.mPasswordHistory) return false;
+ if (ps.mPasswordExpirationDays != policy.mPasswordExpirationDays) return false;
+ if (ps.mMaxPasswordFails != policy.mPasswordMaxFails) return false;
+ if (ps.mMaxScreenLockTime != policy.mMaxScreenLockTime) return false;
+ if (ps.mRequireRemoteWipe != policy.mRequireRemoteWipe) return false;
+ if (ps.mRequireEncryption != policy.mRequireEncryption) return false;
+ if (ps.mRequireEncryptionExternal != policy.mRequireEncryptionExternal) return false;
+ return true;
+ }
+
+ public void testPolicyFlagsToPolicy() {
+ // Policy flags; the three sets included here correspond to policies for three test
+ // accounts that, between them, use all of the possible policies
+ long flags = 67096612L;
+ PolicySet ps = new PolicySet(flags);
+ Policy policy = LegacyPolicySet.flagsToPolicy(flags);
+ assertTrue(policySetEqualsPolicy(ps, policy));
+ flags = 52776591691846L;
+ ps = new PolicySet(flags);
+ policy = LegacyPolicySet.flagsToPolicy(flags);
+ assertTrue(policySetEqualsPolicy(ps, policy));
+ flags = 1689605957029924L;
+ ps = new PolicySet(flags);
+ policy = LegacyPolicySet.flagsToPolicy(flags);
+ assertTrue(policySetEqualsPolicy(ps, policy));
+ }
+
+ /**
+ * The old PolicySet class fields and constructor; we use this to test conversion to the
+ * new Policy table scheme
+ */
+ private static class PolicySet {
+ private final int mMinPasswordLength;
+ private final int mPasswordMode;
+ private final int mMaxPasswordFails;
+ private final int mMaxScreenLockTime;
+ private final boolean mRequireRemoteWipe;
+ private final int mPasswordExpirationDays;
+ private final int mPasswordHistory;
+ private final int mPasswordComplexChars;
+ private final boolean mRequireEncryption;
+ private final boolean mRequireEncryptionExternal;
+
+ /**
+ * Create from values encoded in an account flags int
+ */
+ private PolicySet(long flags) {
+ mMinPasswordLength = (int) ((flags & LegacyPolicySet.PASSWORD_LENGTH_MASK)
+ >> LegacyPolicySet.PASSWORD_LENGTH_SHIFT);
+ mPasswordMode =
+ (int) (flags & LegacyPolicySet.PASSWORD_MODE_MASK);
+ mMaxPasswordFails = (int) ((flags & LegacyPolicySet.PASSWORD_MAX_FAILS_MASK)
+ >> LegacyPolicySet.PASSWORD_MAX_FAILS_SHIFT);
+ mMaxScreenLockTime = (int) ((flags & LegacyPolicySet.SCREEN_LOCK_TIME_MASK)
+ >> LegacyPolicySet.SCREEN_LOCK_TIME_SHIFT);
+ mRequireRemoteWipe = 0 != (flags & LegacyPolicySet.REQUIRE_REMOTE_WIPE);
+ mPasswordExpirationDays = (int) ((flags & LegacyPolicySet.PASSWORD_EXPIRATION_MASK)
+ >> LegacyPolicySet.PASSWORD_EXPIRATION_SHIFT);
+ mPasswordHistory = (int) ((flags & LegacyPolicySet.PASSWORD_HISTORY_MASK)
+ >> LegacyPolicySet.PASSWORD_HISTORY_SHIFT);
+ mPasswordComplexChars = (int) ((flags & LegacyPolicySet.PASSWORD_COMPLEX_CHARS_MASK)
+ >> LegacyPolicySet.PASSWORD_COMPLEX_CHARS_SHIFT);
+ mRequireEncryption = 0 != (flags & LegacyPolicySet.REQUIRE_ENCRYPTION);
+ mRequireEncryptionExternal = 0 != (flags & LegacyPolicySet.REQUIRE_ENCRYPTION_EXTERNAL);
+ }
+ }
}
diff --git a/tests/src/com/android/email/provider/PolicyTests.java b/tests/src/com/android/email/provider/PolicyTests.java
new file mode 100644
index 000000000..8cb319ba8
--- /dev/null
+++ b/tests/src/com/android/email/provider/PolicyTests.java
@@ -0,0 +1,125 @@
+/*
+ * 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.provider;
+
+import com.android.emailcommon.provider.EmailContent;
+import com.android.emailcommon.provider.EmailContent.Account;
+import com.android.emailcommon.provider.Policy;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.test.ProviderTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * This is a series of unit tests for the Policy class
+ *
+ * You can run this entire test case with:
+ * runtest -c com.android.email.provider.PolicyTests email
+ */
+
+@MediumTest
+public class PolicyTests extends ProviderTestCase2<EmailProvider> {
+
+ private Context mMockContext;
+
+ public PolicyTests() {
+ super(EmailProvider.class, EmailContent.AUTHORITY);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mMockContext = getMockContext();
+ // Invalidate all caches, since we reset the database for each test
+ ContentCache.invalidateAllCachesForTest();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public void testGetAccountIdWithPolicyKey() {
+ String securitySyncKey = "key";
+ // Setup two accounts with policies
+ Account account1 = ProviderTestUtils.setupAccount("acct1", true, mMockContext);
+ Policy policy1 = new Policy();
+ policy1.setAccountPolicy(mMockContext, account1, securitySyncKey);
+ Account account2 = ProviderTestUtils.setupAccount("acct2", true, mMockContext);
+ Policy policy2 = new Policy();
+ policy2.setAccountPolicy(mMockContext, account2, securitySyncKey);
+ // Get the accounts back from the database
+ account1.refresh(mMockContext);
+ account2.refresh(mMockContext);
+ // Both should have valid policies
+ assertTrue(account1.mPolicyKey > 0);
+ // And they should be findable via getAccountIdWithPolicyKey
+ assertTrue(account2.mPolicyKey > 0);
+ assertEquals(account1.mId, Policy.getAccountIdWithPolicyKey(mMockContext,
+ account1.mPolicyKey));
+ assertEquals(account2.mId, Policy.getAccountIdWithPolicyKey(mMockContext,
+ account2.mPolicyKey));
+ }
+
+ public void testSetAndClearAccountPolicy() {
+ String securitySyncKey = "key";
+ Account account = ProviderTestUtils.setupAccount("acct", true, mMockContext);
+ // Nothing up my sleeve
+ assertEquals(0, account.mPolicyKey);
+ assertEquals(0, EmailContent.count(mMockContext, Policy.CONTENT_URI));
+ Policy policy = new Policy();
+ policy.setAccountPolicy(mMockContext, account, securitySyncKey);
+ account.refresh(mMockContext);
+ // We should have a policyKey now
+ assertTrue(account.mPolicyKey > 0);
+ Policy dbPolicy = Policy.restorePolicyWithId(mMockContext, account.mPolicyKey);
+ // The policy should exist in the database
+ assertNotNull(dbPolicy);
+ // And it should be the same as the original
+ assertEquals(policy, dbPolicy);
+ // The account should have the security sync key set
+ assertEquals(securitySyncKey, account.mSecuritySyncKey);
+ Policy.clearAccountPolicy(mMockContext, account);
+ account.refresh(mMockContext);
+ // Make sure policyKey is cleared and policy is deleted
+ assertEquals(0, account.mPolicyKey);
+ assertEquals(0, EmailContent.count(mMockContext, Policy.CONTENT_URI));
+ account.refresh(mMockContext);
+ // The account's security sync key should also be null
+ assertNull(account.mSecuritySyncKey);
+ }
+
+ public void testParcel() {
+ Policy policy = new Policy();
+ policy.mPasswordMode = Policy.PASSWORD_MODE_STRONG;
+ policy.mPasswordMinLength = 6;
+ policy.mPasswordComplexChars = 5;
+ policy.mPasswordExpirationDays = 4;
+ policy.mPasswordHistory = 3;
+ policy.mPasswordMaxFails = 8;
+ policy.mMaxScreenLockTime = 600;
+ policy.mRequireRemoteWipe = true;
+ policy.mRequireEncryption = true;
+ policy.mRequireEncryptionExternal = true;
+ Parcel parcel = Parcel.obtain();
+ policy.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ Policy readPolicy = Policy.CREATOR.createFromParcel(parcel);
+ assertEquals(policy, readPolicy);
+ }
+}
diff --git a/tests/src/com/android/email/provider/ProviderTestUtils.java b/tests/src/com/android/email/provider/ProviderTestUtils.java
index f0e9853cc..674295aab 100644
--- a/tests/src/com/android/email/provider/ProviderTestUtils.java
+++ b/tests/src/com/android/email/provider/ProviderTestUtils.java
@@ -64,7 +64,7 @@ public class ProviderTestUtils extends Assert {
account.mRingtoneUri = "content://ringtone-" + name;
account.mProtocolVersion = "2.5" + name;
account.mNewMessageCount = 5 + name.length();
- account.mSecurityFlags = 7;
+ account.mPolicyKey = 0;
account.mSecuritySyncKey = "sec-sync-key-" + name;
account.mSignature = "signature-" + name;
if (saveIt) {
@@ -319,7 +319,7 @@ public class ProviderTestUtils extends Assert {
actual.mProtocolVersion);
assertEquals(caller + " mNewMessageCount", expect.mNewMessageCount,
actual.mNewMessageCount);
- assertEquals(caller + " mSecurityFlags", expect.mSecurityFlags, actual.mSecurityFlags);
+ assertEquals(caller + " mPolicyKey", expect.mPolicyKey, actual.mPolicyKey);
assertEquals(caller + " mSecuritySyncKey", expect.mSecuritySyncKey,
actual.mSecuritySyncKey);
assertEquals(caller + " mSignature", expect.mSignature, actual.mSignature);