Adding security hold flag to accounts

* Add hold flag to Account flags
* Add code to set it (when EAS reports policy failure)
* Add code to clear it when we see changes from the device admin side
* unit tests

This should be sufficient to restart sync of an account which is on hold
due to security policy requirements.  Note, this is considered a "retry",
and if the account still does not meet requirements for some reason, it
is expected that EAS sync will call policiesRequired() again.
This commit is contained in:
Andrew Stadler 2010-02-08 17:42:42 -08:00
parent f3332ddac8
commit 2a5eeea921
4 changed files with 120 additions and 5 deletions

View File

@ -28,6 +28,8 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@ -38,7 +40,6 @@ import android.net.Uri;
* Utility functions to support reading and writing security policies
*
* STOPSHIP - these TODO items are all part of finishing the feature
* TODO: When user sets password, and conditions are now satisfied, restart syncs
* TODO: When accounts are deleted, reduce policy and/or give up admin status
* TODO: Provide a way to check for policy issues at synchronous times such as entering
* message list or folder list.
@ -50,7 +51,7 @@ public class SecurityPolicy {
/** STOPSHIP - ok to check in true for now, but must be false for shipping */
/** DO NOT CHECK IN WHILE 'true' */
// Until everything is connected, allow syncs to work
private static final boolean DEBUG_ALWAYS_ACTIVE = true;
private static final boolean DEBUG_ALWAYS_ACTIVE = false;
private static SecurityPolicy sInstance = null;
private Context mContext;
@ -67,13 +68,23 @@ public class SecurityPolicy {
* This projection on Account is for scanning/reading
*/
private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] {
Account.RECORD_ID, Account.SECURITY_FLAGS
AccountColumns.ID, AccountColumns.SECURITY_FLAGS
};
private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1;
// Note, this handles the NULL case to deal with older accounts where the column was added
private static final String WHERE_ACCOUNT_SECURITY_NONZERO =
Account.SECURITY_FLAGS + " IS NOT NULL AND " + Account.SECURITY_FLAGS + "!=0";
/**
* This projection on Account is for clearing the "security hold" column. Also includes
* the security flags column, so we can use it for selecting.
*/
private static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] {
AccountColumns.ID, AccountColumns.FLAGS, AccountColumns.SECURITY_FLAGS
};
private static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
private static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1;
/**
* These are hardcoded limits based on knowledge of the current DevicePolicyManager
* and screen lock mechanisms. Wherever possible, these should be replaced with queries of
@ -265,6 +276,9 @@ public class SecurityPolicy {
}
// password failures are counted locally - no test required here
// no check required for remote wipe (it's supported, if we're the admin)
// making it this far means we passed!
return true;
}
// return false, not active - unless debugging enabled
return DEBUG_ALWAYS_ACTIVE;
@ -295,6 +309,46 @@ public class SecurityPolicy {
}
}
/**
* API: Set/Clear the "hold" flag in any account. This flag serves a dual purpose:
* Setting it gives us an indication that it was blocked, and clearing it gives EAS a
* signal to try syncing again.
*/
public void setAccountHoldFlag(Account account, boolean newState) {
if (newState) {
account.mFlags |= Account.FLAGS_SECURITY_HOLD;
} else {
account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
}
ContentValues cv = new ContentValues();
cv.put(AccountColumns.FLAGS, account.mFlags);
account.update(mContext, cv);
}
/**
* Clear all account hold flags that are set. This will trigger watchers, and in particular
* will cause EAS to try and resync the account(s).
*/
public void clearAccountHoldFlags() {
ContentResolver resolver = mContext.getContentResolver();
Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
try {
while (c.moveToNext()) {
int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
if (0 != (flags & Account.FLAGS_SECURITY_HOLD)) {
ContentValues cv = new ContentValues();
cv.put(AccountColumns.FLAGS, flags & ~Account.FLAGS_SECURITY_HOLD);
long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
resolver.update(uri, cv, null, null);
}
}
} finally {
c.close();
}
}
/**
* API: Sync service should call this any time a sync fails due to isActive() returning false.
* This will kick off the notify-acquire-admin-state process and/or increase the security level.
@ -304,6 +358,12 @@ public class SecurityPolicy {
* @param accountId the account for which sync cannot proceed
*/
public void policiesRequired(long accountId) {
Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
// Mark the account as "on hold".
setAccountHoldFlag(account, true);
// Put up a notification (unless there already is one)
synchronized (this) {
if (mNotificationActive) {
// no need to do anything - we've already been notified, and we've already
@ -316,7 +376,6 @@ public class SecurityPolicy {
}
}
// At this point, we will put up a notification
Account account = EmailContent.Account.restoreAccountWithId(mContext, accountId);
String tickerText = mContext.getString(R.string.security_notification_ticker_fmt,
account.getDisplayName());
@ -595,7 +654,7 @@ public class SecurityPolicy {
*/
@Override
public void onPasswordChanged(Context context, Intent intent) {
// do something
SecurityPolicy.getInstance(context).clearAccountHoldFlags();
}
/**

View File

@ -118,12 +118,14 @@ public class AccountSecurity extends Activity {
SecurityPolicy sp = SecurityPolicy.getInstance(this);
// check current security level - if sufficient, we're done!
if (sp.isActive(null)) {
sp.clearAccountHoldFlags();
return;
}
// set current security level
sp.setActivePolicies();
// check current security level - if sufficient, we're done!
if (sp.isActive(null)) {
sp.clearAccountHoldFlags();
return;
}
// if not sufficient, launch the activity to have the user set a new password.

View File

@ -791,6 +791,7 @@ public abstract class EmailContent {
public static final int FLAGS_DELETE_POLICY_MASK = 4+8;
public static final int FLAGS_DELETE_POLICY_SHIFT = 2;
public static final int FLAGS_INCOMPLETE = 16;
public static final int FLAGS_SECURITY_HOLD = 32;
public static final int DELETE_POLICY_NEVER = 0;
public static final int DELETE_POLICY_7DAYS = 1; // not supported

View File

@ -242,4 +242,57 @@ public class SecurityPolicyTests extends ProviderTestCase2<EmailProvider> {
assertFalse(p2.hashCode() == p3.hashCode());
}
/**
* 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);
Account a2 = ProviderTestUtils.setupAccount("holdflag-2", false, mMockContext);
a2.mFlags = Account.FLAGS_VIBRATE | Account.FLAGS_SECURITY_HOLD;
a2.save(mMockContext);
// confirm clear until set
Account a1a = Account.restoreAccountWithId(mMockContext, a1.mId);
assertEquals(Account.FLAGS_NOTIFY_NEW_MAIL, a1a.mFlags);
sp.setAccountHoldFlag(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);
// confirm set until cleared
Account a2a = Account.restoreAccountWithId(mMockContext, a2.mId);
assertEquals(Account.FLAGS_VIBRATE | Account.FLAGS_SECURITY_HOLD, a2a.mFlags);
sp.setAccountHoldFlag(a2, false);
assertEquals(Account.FLAGS_VIBRATE, a2.mFlags);
Account a2b = Account.restoreAccountWithId(mMockContext, a2.mId);
assertEquals(Account.FLAGS_VIBRATE, a2b.mFlags);
}
/**
* Test the API to clear all policy hold flags in all accounts)
*/
public void testClearHoldFlags() {
SecurityPolicy sp = getSecurityPolicy();
Account a1 = ProviderTestUtils.setupAccount("holdflag-1", false, mMockContext);
a1.mFlags = Account.FLAGS_NOTIFY_NEW_MAIL;
a1.save(mMockContext);
Account a2 = ProviderTestUtils.setupAccount("holdflag-2", false, mMockContext);
a2.mFlags = Account.FLAGS_VIBRATE | Account.FLAGS_SECURITY_HOLD;
a2.save(mMockContext);
// bulk clear
sp.clearAccountHoldFlags();
// confirm new values as expected - no hold flags; other flags unmolested
Account a1a = Account.restoreAccountWithId(mMockContext, a1.mId);
assertEquals(Account.FLAGS_NOTIFY_NEW_MAIL, a1a.mFlags);
Account a2a = Account.restoreAccountWithId(mMockContext, a2.mId);
assertEquals(Account.FLAGS_VIBRATE, a2a.mFlags);
}
}