/* * Copyright (C) 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.exchange; import com.android.email.Email; import com.android.email.provider.EmailContent; import com.android.email.provider.EmailProvider; import com.android.email.provider.ProviderTestUtils; import com.android.email.provider.EmailContent.Account; import com.android.email.provider.EmailContent.Mailbox; import com.android.exchange.SyncManager.AccountList; import com.android.exchange.SyncManager.SyncError; import android.accounts.AccountManager; import android.accounts.AccountManagerFuture; import android.accounts.AuthenticatorException; import android.accounts.OperationCanceledException; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.test.ProviderTestCase2; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; /** * You can run this entire test case with: * runtest -c com.android.exchange.SyncManagerAccountTests email */ public class SyncManagerAccountTests extends ProviderTestCase2 { private static final String TEST_ACCOUNT_PREFIX = "__test"; private static final String TEST_ACCOUNT_SUFFIX = "@android.com"; EmailProvider mProvider; Context mMockContext; public SyncManagerAccountTests() { super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY); } @Override public void setUp() throws Exception { super.setUp(); mMockContext = getMockContext(); // Delete any test accounts we might have created earlier deleteTemporaryAccountManagerAccounts(getContext()); } @Override public void tearDown() throws Exception { super.tearDown(); // Delete any test accounts we might have created earlier deleteTemporaryAccountManagerAccounts(getContext()); } private android.accounts.Account makeAccountManagerAccount(String username) { return new android.accounts.Account(username, Email.EXCHANGE_ACCOUNT_MANAGER_TYPE); } private void createAccountManagerAccount(String username) { final android.accounts.Account account = makeAccountManagerAccount(username); AccountManager.get(getContext()).addAccountExplicitly(account, "password", null); } private Account setupProviderAndAccountManagerAccount(String username) { // Note that setupAccount creates the email address username@android.com, so that's what // we need to use for the account manager createAccountManagerAccount(username + TEST_ACCOUNT_SUFFIX); return ProviderTestUtils.setupAccount(username, true, mMockContext); } private AccountList makeSyncManagerAccountList() { AccountList accountList = new AccountList(); Cursor c = mMockContext.getContentResolver().query(Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, null); try { while (c.moveToNext()) { accountList.add(new Account().restore(c)); } } finally { c.close(); } return accountList; } private void deleteAccountManagerAccount(Context context, android.accounts.Account account) { AccountManagerFuture future = AccountManager.get(context).removeAccount(account, null, null); try { future.getResult(); } catch (OperationCanceledException e) { } catch (AuthenticatorException e) { } catch (IOException e) { } } private void deleteTemporaryAccountManagerAccounts(Context context) { android.accounts.Account[] accountManagerAccounts = AccountManager.get(context).getAccountsByType(Email.EXCHANGE_ACCOUNT_MANAGER_TYPE); for (android.accounts.Account accountManagerAccount: accountManagerAccounts) { if (accountManagerAccount.name.startsWith(TEST_ACCOUNT_PREFIX) && accountManagerAccount.name.endsWith(TEST_ACCOUNT_SUFFIX)) { deleteAccountManagerAccount(context, accountManagerAccount); } } } private String getTestAccountName(String name) { return TEST_ACCOUNT_PREFIX + name; } private String getTestAccountEmailAddress(String name) { return TEST_ACCOUNT_PREFIX + name + TEST_ACCOUNT_SUFFIX; } /** * Confirm that the test below is functional (and non-destructive) when there are * prexisting (non-test) accounts in the account manager. */ public void testTestReconcileAccounts() { Account firstAccount = null; final String TEST_USER_ACCOUNT = "__user_account_test_1"; Context context = getContext(); try { // Note: Unlike calls to setupProviderAndAccountManagerAccount(), we are creating // *real* accounts here (not in the mock provider) createAccountManagerAccount(TEST_USER_ACCOUNT + TEST_ACCOUNT_SUFFIX); firstAccount = ProviderTestUtils.setupAccount(TEST_USER_ACCOUNT, true, context); // Now run the test with the "user" accounts in place testReconcileAccounts(); } finally { if (firstAccount != null) { boolean firstAccountFound = false; // delete the provider account context.getContentResolver().delete(firstAccount.getUri(), null, null); // delete the account manager account android.accounts.Account[] accountManagerAccounts = AccountManager.get(context) .getAccountsByType(Email.EXCHANGE_ACCOUNT_MANAGER_TYPE); for (android.accounts.Account accountManagerAccount: accountManagerAccounts) { if ((TEST_USER_ACCOUNT + TEST_ACCOUNT_SUFFIX) .equals(accountManagerAccount.name)) { deleteAccountManagerAccount(context, accountManagerAccount); firstAccountFound = true; } } assertTrue(firstAccountFound); } } } /** * Helper to retrieve account manager accounts *and* remove any preexisting accounts * from the list, to "hide" them from the reconciler. */ private android.accounts.Account[] getAccountManagerAccounts(Context context, android.accounts.Account[] baseline) { android.accounts.Account[] rawList = AccountManager.get(context).getAccountsByType(Email.EXCHANGE_ACCOUNT_MANAGER_TYPE); if (baseline.length == 0) { return rawList; } HashSet set = new HashSet(); for (android.accounts.Account addAccount : rawList) { set.add(addAccount); } for (android.accounts.Account removeAccount : baseline) { set.remove(removeAccount); } return set.toArray(new android.accounts.Account[0]); } /** * Note, there is some inherent risk in this test, as it creates *real* accounts in the * system (it cannot use the mock context with the Account Manager). */ public void testReconcileAccounts() { // Note that we can't use mMockContext for AccountManager interactions, as it isn't a fully // functional Context. Context context = getContext(); // Capture the baseline (account manager accounts) so we can measure the changes // we're making, irrespective of the number of actual accounts, and not destroy them android.accounts.Account[] baselineAccounts = AccountManager.get(context).getAccountsByType(Email.EXCHANGE_ACCOUNT_MANAGER_TYPE); // Set up three accounts, both in AccountManager and in EmailProvider Account firstAccount = setupProviderAndAccountManagerAccount(getTestAccountName("1")); setupProviderAndAccountManagerAccount(getTestAccountName("2")); setupProviderAndAccountManagerAccount(getTestAccountName("3")); // Check that they're set up properly assertEquals(3, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null)); android.accounts.Account[] accountManagerAccounts = getAccountManagerAccounts(context, baselineAccounts); assertEquals(3, accountManagerAccounts.length); // Delete account "2" from AccountManager android.accounts.Account removedAccount = makeAccountManagerAccount(getTestAccountEmailAddress("2")); deleteAccountManagerAccount(context, removedAccount); // Confirm it's deleted accountManagerAccounts = getAccountManagerAccounts(context, baselineAccounts); assertEquals(2, accountManagerAccounts.length); // Run the reconciler ContentResolver resolver = mMockContext.getContentResolver(); SyncManager.reconcileAccountsWithAccountManager(context, makeSyncManagerAccountList(), accountManagerAccounts, true, resolver); // There should now be only two EmailProvider accounts assertEquals(2, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null)); // Ok, now we've got two of each; let's delete a provider account resolver.delete(ContentUris.withAppendedId(Account.CONTENT_URI, firstAccount.mId), null, null); // ...and then there was one assertEquals(1, EmailContent.count(mMockContext, Account.CONTENT_URI, null, null)); // Run the reconciler SyncManager.reconcileAccountsWithAccountManager(context, makeSyncManagerAccountList(), accountManagerAccounts, true, resolver); // There should now be only one AccountManager account accountManagerAccounts = getAccountManagerAccounts(context, baselineAccounts); assertEquals(1, accountManagerAccounts.length); // ... and it should be account "3" assertEquals(getTestAccountEmailAddress("3"), accountManagerAccounts[0].name); } public void testReleaseSyncHolds() { Context context = mMockContext; SyncManager syncManager = new SyncManager(); SyncError securityErrorAccount1 = syncManager.new SyncError(AbstractSyncService.EXIT_SECURITY_FAILURE, false); SyncError ioError = syncManager.new SyncError(AbstractSyncService.EXIT_IO_ERROR, false); SyncError securityErrorAccount2 = syncManager.new SyncError(AbstractSyncService.EXIT_SECURITY_FAILURE, false); // Create account and two mailboxes Account acct1 = ProviderTestUtils.setupAccount("acct1", true, context); Mailbox box1 = ProviderTestUtils.setupMailbox("box1", acct1.mId, true, context); Mailbox box2 = ProviderTestUtils.setupMailbox("box2", acct1.mId, true, context); Account acct2 = ProviderTestUtils.setupAccount("acct2", true, context); Mailbox box3 = ProviderTestUtils.setupMailbox("box3", acct2.mId, true, context); Mailbox box4 = ProviderTestUtils.setupMailbox("box4", acct2.mId, true, context); HashMap errorMap = syncManager.mSyncErrorMap; // Add errors into the map errorMap.put(box1.mId, securityErrorAccount1); errorMap.put(box2.mId, ioError); errorMap.put(box3.mId, securityErrorAccount2); errorMap.put(box4.mId, securityErrorAccount2); // We should have 4 assertEquals(4, errorMap.keySet().size()); // Release the holds on acct2 (there are two of them) syncManager.releaseSyncHolds(context, AbstractSyncService.EXIT_SECURITY_FAILURE, acct2); // There should be two left assertEquals(2, errorMap.keySet().size()); // And these are the two... assertNotNull(errorMap.get(box2.mId)); assertNotNull(errorMap.get(box1.mId)); // Put the two back errorMap.put(box3.mId, securityErrorAccount2); errorMap.put(box4.mId, securityErrorAccount2); // We should have 4 again assertEquals(4, errorMap.keySet().size()); // Release all of the security holds syncManager.releaseSyncHolds(context, AbstractSyncService.EXIT_SECURITY_FAILURE, null); // There should be one left assertEquals(1, errorMap.keySet().size()); // And this is the one assertNotNull(errorMap.get(box2.mId)); // Release the i/o holds on account 2 (there aren't any) syncManager.releaseSyncHolds(context, AbstractSyncService.EXIT_IO_ERROR, acct2); // There should still be one left assertEquals(1, errorMap.keySet().size()); // Release the i/o holds on account 1 (there's one) syncManager.releaseSyncHolds(context, AbstractSyncService.EXIT_IO_ERROR, acct1); // There should still be one left assertEquals(0, errorMap.keySet().size()); } }