1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.provider.cts; 18 19 import static android.provider.cts.contacts.DatabaseAsserts.ContactIdPair; 20 21 import android.accounts.Account; 22 import android.accounts.AccountManager; 23 import android.content.ContentResolver; 24 import android.os.SystemClock; 25 import android.provider.cts.contacts.CommonDatabaseUtils; 26 import android.provider.cts.contacts.ContactUtil; 27 import android.provider.cts.contacts.DataUtil; 28 import android.provider.cts.contacts.DatabaseAsserts; 29 import android.provider.cts.contacts.DeletedContactUtil; 30 import android.provider.cts.contacts.RawContactUtil; 31 import android.provider.cts.contacts.account.StaticAccountAuthenticator; 32 import android.test.AndroidTestCase; 33 import android.test.suitebuilder.annotation.MediumTest; 34 35 import com.google.android.collect.Lists; 36 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 40 @MediumTest 41 public class ContactsProvider2_AccountRemovalTest extends AndroidTestCase { 42 43 private static long ASYNC_TIMEOUT_LIMIT_MS = 1000 * 60 * 1; // 3 minutes 44 private static long SLEEP_BETWEEN_POLL_MS = 1000 * 10; // 10 seconds 45 46 private static int NOT_MERGED = -1; 47 48 // Not re-using StaticAcountAuthenticator.ACCOUNT_1 because this test may break 49 // other tests running when the account is removed. No other tests should use the following 50 // accounts. 51 private static final Account ACCT_1 = new Account("cp removal acct 1", 52 StaticAccountAuthenticator.TYPE); 53 private static final Account ACCT_2 = new Account("cp removal acct 2", 54 StaticAccountAuthenticator.TYPE); 55 56 private ContentResolver mResolver; 57 private AccountManager mAccountManager; 58 59 @Override 60 protected void setUp() throws Exception { 61 super.setUp(); 62 mResolver = getContext().getContentResolver(); 63 mAccountManager = AccountManager.get(getContext()); 64 } 65 66 @Override 67 protected void tearDown() throws Exception { 68 super.tearDown(); 69 } 70 71 public void testAccountRemoval_deletesContacts() { 72 mAccountManager.addAccountExplicitly(ACCT_1, null, null); 73 mAccountManager.addAccountExplicitly(ACCT_2, null, null); 74 ArrayList<ContactIdPair> acc1Ids = createContacts(ACCT_1, 5); 75 ArrayList<ContactIdPair> acc2Ids = createContacts(ACCT_2, 15); 76 77 mAccountManager.removeAccount(ACCT_2, null, null); 78 assertContactsDeletedEventually(System.currentTimeMillis(), acc2Ids); 79 80 mAccountManager.removeAccount(ACCT_1, null, null); 81 assertContactsDeletedEventually(System.currentTimeMillis(), acc1Ids); 82 } 83 84 public void testAccountRemoval_hasDeleteLogsForContacts() { 85 mAccountManager.addAccountExplicitly(ACCT_1, null, null); 86 mAccountManager.addAccountExplicitly(ACCT_2, null, null); 87 ArrayList<ContactIdPair> acc1Ids = createContacts(ACCT_1, 5); 88 ArrayList<ContactIdPair> acc2Ids = createContacts(ACCT_2, 15); 89 90 long start = System.currentTimeMillis(); 91 mAccountManager.removeAccount(ACCT_2, null, null); 92 assertContactsInDeleteLogEventually(start, acc2Ids); 93 94 start = System.currentTimeMillis(); 95 mAccountManager.removeAccount(ACCT_1, null, null); 96 assertContactsInDeleteLogEventually(start, acc1Ids); 97 } 98 99 /** 100 * Contact has merged raw contacts from a single account. Contact should be deleted upon 101 * account removal. 102 */ 103 public void testAccountRemovalWithMergedContact_deletesContacts() { 104 mAccountManager.addAccountExplicitly(ACCT_1, null, null); 105 ArrayList<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1); 106 mAccountManager.removeAccount(ACCT_1, null, null); 107 assertContactsDeletedEventually(System.currentTimeMillis(), idList); 108 } 109 110 /** 111 * Contact has merged raw contacts from different accounts. Contact should not be deleted when 112 * one account is removed. But contact should have last updated timestamp updated. 113 */ 114 public void testAccountRemovalWithMergedContact_doesNotDeleteContactAndTimestampUpdated() { 115 mAccountManager.addAccountExplicitly(ACCT_1, null, null); 116 mAccountManager.addAccountExplicitly(ACCT_2, null, null); 117 ArrayList<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_2); 118 long contactId = idList.get(0).mContactId; 119 120 long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId); 121 long start = System.currentTimeMillis(); 122 mAccountManager.removeAccount(ACCT_1, null, null); 123 124 while (ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId) == baseTime) { 125 assertWithinTimeoutLimit(start, 126 "Contact " + contactId + " last updated timestamp has not been updated."); 127 SystemClock.sleep(SLEEP_BETWEEN_POLL_MS); 128 } 129 mAccountManager.removeAccount(ACCT_2, null, null); 130 } 131 132 public void testAccountRemovalWithMergedContact_hasDeleteLogsForContacts() { 133 mAccountManager.addAccountExplicitly(ACCT_1, null, null); 134 ArrayList<ContactIdPair> idList = createAndAssertMergedContact(ACCT_1, ACCT_1); 135 long start = System.currentTimeMillis(); 136 mAccountManager.removeAccount(ACCT_1, null, null); 137 assertContactsInDeleteLogEventually(start, idList); 138 } 139 140 private ArrayList<ContactIdPair> createAndAssertMergedContact(Account acct, Account acct2) { 141 ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(mResolver, acct, 142 "merge me"); 143 DataUtil.insertPhoneNumber(mResolver, ids1.mRawContactId, "555-5555"); 144 145 ContactIdPair ids2 = DatabaseAsserts.assertAndCreateContactWithName(mResolver, acct2, 146 "merge me"); 147 DataUtil.insertPhoneNumber(mResolver, ids2.mRawContactId, "555-5555"); 148 149 // Check merge before continuing. Merge process is async. 150 long mergedContactId = assertMerged(System.currentTimeMillis(), ids1.mRawContactId, 151 ids2.mRawContactId); 152 153 // Update the contact id to the newly merged contact id. 154 ids1.mContactId = mergedContactId; 155 ids2.mContactId = mergedContactId; 156 157 return Lists.newArrayList(ids1, ids2); 158 } 159 160 private long assertMerged(long start, long rawContactId, long rawContactId2) { 161 long contactId = NOT_MERGED; 162 while (contactId == NOT_MERGED) { 163 assertWithinTimeoutLimit(start, 164 "Raw contact " + rawContactId + " and " + rawContactId2 + " are not merged."); 165 166 SystemClock.sleep(SLEEP_BETWEEN_POLL_MS); 167 contactId = checkMerged(rawContactId, rawContactId2); 168 } 169 return contactId; 170 } 171 172 private long checkMerged(long rawContactId, long rawContactId2) { 173 long contactId = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId); 174 long contactId2 = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId2); 175 if (contactId == contactId2) { 176 return contactId; 177 } 178 return NOT_MERGED; 179 } 180 181 private void assertContactsInDeleteLogEventually(long start, ArrayList<ContactIdPair> idList) { 182 // Can not use newArrayList() because the version that accepts size is missing. 183 ArrayList<ContactIdPair> remaining = new ArrayList<ContactIdPair>(idList.size()); 184 remaining.addAll(idList); 185 while (!remaining.isEmpty()) { 186 // Account cleanup is asynchronous, wait a bit before checking. 187 SystemClock.sleep(SLEEP_BETWEEN_POLL_MS); 188 assertWithinTimeoutLimit(start, "Contacts " + Arrays.toString(remaining.toArray()) + 189 " are not in delete log after account removal."); 190 191 // Need a second list to remove since we can't remove from the list while iterating. 192 ArrayList<ContactIdPair> toBeRemoved = Lists.newArrayList(); 193 for (ContactIdPair ids : remaining) { 194 long deletedTime = DeletedContactUtil.queryDeletedTimestampForContactId(mResolver, 195 ids.mContactId); 196 if (deletedTime != CommonDatabaseUtils.NOT_FOUND) { 197 toBeRemoved.add(ids); 198 assertTrue("Deleted contact was found in delete log but insert time is before" 199 + " start time", deletedTime > start); 200 } 201 } 202 remaining.removeAll(toBeRemoved); 203 } 204 205 // All contacts in delete log. Pass. 206 } 207 208 /** 209 * Polls every so often to see if all contacts have been deleted. If not deleted in the 210 * pre-defined threshold, fails. 211 */ 212 private void assertContactsDeletedEventually(long start, ArrayList<ContactIdPair> idList) { 213 // Can not use newArrayList() because the version that accepts size is missing. 214 ArrayList<ContactIdPair> remaining = new ArrayList<ContactIdPair>(idList.size()); 215 remaining.addAll(idList); 216 while (!remaining.isEmpty()) { 217 // Account cleanup is asynchronous, wait a bit before checking. 218 SystemClock.sleep(SLEEP_BETWEEN_POLL_MS); 219 assertWithinTimeoutLimit(start, "Contacts have not been deleted after account" 220 + " removal."); 221 222 ArrayList<ContactIdPair> toBeRemoved = Lists.newArrayList(); 223 for (ContactIdPair ids : remaining) { 224 if (!RawContactUtil.rawContactExistsById(mResolver, ids.mRawContactId)) { 225 toBeRemoved.add(ids); 226 } 227 } 228 remaining.removeAll(toBeRemoved); 229 } 230 231 // All contacts deleted. Pass. 232 } 233 234 private void assertWithinTimeoutLimit(long start, String message) { 235 long now = System.currentTimeMillis(); 236 long elapsed = now - start; 237 if (elapsed > ASYNC_TIMEOUT_LIMIT_MS) { 238 fail(elapsed + "ms has elapsed. The limit is " + ASYNC_TIMEOUT_LIMIT_MS + "ms. " + 239 message); 240 } 241 } 242 243 /** 244 * Creates a given number of contacts for an account. 245 */ 246 private ArrayList<ContactIdPair> createContacts(Account account, int numContacts) { 247 ArrayList<ContactIdPair> accountIds = Lists.newArrayList(); 248 for (int i = 0; i < numContacts; i++) { 249 accountIds.add(DatabaseAsserts.assertAndCreateContact(mResolver, account)); 250 } 251 return accountIds; 252 } 253 } 254