diff --git a/res/values/strings.xml b/res/values/strings.xml
index 265e25463..1dee8e703 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -650,6 +650,11 @@ save attachment.
being supported -->
This server requires security features your phone does not support.
+
+ WARNING: Deactivating the Email application\'s authority
+ to administer your device will delete all Email accounts that require it, along with their
+ email, contacts, calendar events, and other data.
diff --git a/src/com/android/email/Controller.java b/src/com/android/email/Controller.java
index a9ea48718..4d49df434 100644
--- a/src/com/android/email/Controller.java
+++ b/src/com/android/email/Controller.java
@@ -942,7 +942,7 @@ public class Controller {
public void deleteAccount(final long accountId) {
Utility.runAsync(new Runnable() {
public void run() {
- deleteAccountSync(accountId);
+ deleteAccountSync(accountId, mContext);
}
});
}
@@ -950,19 +950,19 @@ public class Controller {
/**
* Delete an account synchronously. Intended to be used only by unit tests.
*/
- public void deleteAccountSync(long accountId) {
+ public void deleteAccountSync(long accountId, Context context) {
try {
mLegacyControllerMap.remove(accountId);
// Get the account URI.
- final Account account = Account.restoreAccountWithId(mContext, accountId);
+ final Account account = Account.restoreAccountWithId(context, accountId);
if (account == null) {
return; // Already deleted?
}
- final String accountUri = account.getStoreUri(mContext);
+ final String accountUri = account.getStoreUri(context);
// Delete Remote store at first.
if (!TextUtils.isEmpty(accountUri)) {
- Store.getInstance(accountUri, mContext, null).delete();
+ Store.getInstance(accountUri, context, null).delete();
// Remove the Store instance from cache.
Store.removeInstance(accountUri);
}
@@ -972,12 +972,12 @@ public class Controller {
mContext.getContentResolver().delete(uri, null, null);
// Update the backup (side copy) of the accounts
- AccountBackupRestore.backupAccounts(mContext);
+ AccountBackupRestore.backupAccounts(context);
// Release or relax device administration, if relevant
- SecurityPolicy.getInstance(mContext).reducePolicies();
+ SecurityPolicy.getInstance(context).reducePolicies();
- Email.setServicesEnabled(mContext);
+ Email.setServicesEnabled(context);
} catch (Exception e) {
Log.w(Email.LOG_TAG, "Exception while deleting account", e);
} finally {
diff --git a/src/com/android/email/SecurityPolicy.java b/src/com/android/email/SecurityPolicy.java
index 5f9250016..e30c637b8 100644
--- a/src/com/android/email/SecurityPolicy.java
+++ b/src/com/android/email/SecurityPolicy.java
@@ -20,7 +20,6 @@ import com.android.email.activity.setup.AccountSecurity;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.AccountColumns;
-import com.android.email.service.MailService;
import android.app.Notification;
import android.app.NotificationManager;
@@ -28,6 +27,7 @@ import android.app.PendingIntent;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
@@ -43,7 +43,7 @@ import android.util.Log;
* into and out of various security states.
*/
public class SecurityPolicy {
-
+ private static final String TAG = "SecurityPolicy";
private static SecurityPolicy sInstance = null;
private Context mContext;
private DevicePolicyManager mDPM;
@@ -703,19 +703,40 @@ public class SecurityPolicy {
}
/**
- * Internal handler for enabled->disabled transitions. Resets all security keys
- * forcing EAS to resync security state.
+ * Delete all accounts whose security flags aren't zero (i.e. they have security enabled).
+ * This method is synchronous, so it should normally be called within a worker thread (the
+ * exception being for unit tests)
+ *
+ * @param context the caller's context
*/
- /* package */ void onAdminEnabled(boolean isEnabled) {
+ /*package*/ void deleteSecuredAccounts(Context context) {
+ ContentResolver cr = context.getContentResolver();
+ // Find all accounts with security and delete them
+ Cursor c = cr.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION,
+ AccountColumns.SECURITY_FLAGS + "!=0", null, null);
+ try {
+ Log.w(TAG, "Email administration disabled; deleting " + c.getCount() +
+ " secured account(s)");
+ while (c.moveToNext()) {
+ Controller.getInstance(context).deleteAccountSync(
+ c.getLong(EmailContent.ID_PROJECTION_COLUMN), context);
+ }
+ } finally {
+ c.close();
+ }
+ updatePolicies(-1);
+ }
+
+ /**
+ * Internal handler for enabled->disabled transitions. Deletes all secured accounts.
+ */
+ /*package*/ void onAdminEnabled(boolean isEnabled) {
if (!isEnabled) {
- // transition to disabled state
- // Response: clear *all* security state information from the accounts, forcing
- // them back to the initial configurations requiring policy administration
- ContentValues cv = new ContentValues();
- cv.put(AccountColumns.SECURITY_FLAGS, 0);
- cv.putNull(AccountColumns.SECURITY_SYNC_KEY);
- mContext.getContentResolver().update(Account.CONTENT_URI, cv, null, null);
- updatePolicies(-1);
+ Utility.runAsync(new Runnable() {
+ @Override
+ public void run() {
+ deleteSecuredAccounts(mContext);
+ }});
}
}
@@ -743,6 +764,15 @@ public class SecurityPolicy {
SecurityPolicy.getInstance(context).onAdminEnabled(false);
}
+ /**
+ * Called when the user asks to disable administration; we return a warning string that
+ * will be presented to the user
+ */
+ @Override
+ public CharSequence onDisableRequested(Context context, Intent intent) {
+ return context.getString(R.string.disable_admin_warning);
+ }
+
/**
* Called after the user has changed their password.
*/
diff --git a/tests/src/com/android/email/SecurityPolicyTests.java b/tests/src/com/android/email/SecurityPolicyTests.java
index 2ecd91c89..f21a682e3 100644
--- a/tests/src/com/android/email/SecurityPolicyTests.java
+++ b/tests/src/com/android/email/SecurityPolicyTests.java
@@ -379,6 +379,12 @@ public class SecurityPolicyTests extends ProviderTestCase2 {
assertEquals(Account.FLAGS_VIBRATE_ALWAYS, a2b.mFlags);
}
+ private static class MockController extends Controller {
+ protected MockController(Context context) {
+ super(context);
+ }
+ }
+
/**
* Test the response to disabling DeviceAdmin status
*/
@@ -410,15 +416,22 @@ public class SecurityPolicyTests extends ProviderTestCase2 {
Account a3a = Account.restoreAccountWithId(mMockContext, a3.mId);
assertNull(a3a.mSecuritySyncKey);
- // Revoke device admin status. In the accounts we set up, security values should be reset
- sp.onAdminEnabled(false); // "disabled" should clear policies
- PolicySet after2 = sp.getAggregatePolicy();
- assertEquals(SecurityPolicy.NO_POLICY_SET, after2);
- Account a1b = Account.restoreAccountWithId(mMockContext, a1.mId);
- assertNull(a1b.mSecuritySyncKey);
- Account a2b = Account.restoreAccountWithId(mMockContext, a2.mId);
- assertNull(a2b.mSecuritySyncKey);
- Account a3b = Account.restoreAccountWithId(mMockContext, a3.mId);
- assertNull(a3b.mSecuritySyncKey);
+ // 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);
+ }
}
}