Snap for 5838830 from 56bf6112011e0763857a5a283104cff1e67f37aa to rvc-release
Change-Id: Iac91a9411e4f2c0554bf73c41de40d8d6046f8b2
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index dbf6bea..19ee8a4 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -3802,15 +3802,25 @@
}
private int deleteContact(long contactId, boolean callerIsSyncAdapter) {
+ ArrayList<Long> localRawContactIds = new ArrayList();
+
final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
mSelectionArgs1[0] = Long.toString(contactId);
Cursor c = db.query(Tables.RAW_CONTACTS, new String[] {RawContacts._ID},
RawContacts.CONTACT_ID + "=?", mSelectionArgs1,
null, null, null);
+
+ // Raw contacts need to be deleted after the contact so just loop through and mark
+ // non-local raw contacts as deleted and collect the local raw contacts that will be
+ // deleted after the contact is deleted.
try {
while (c.moveToNext()) {
long rawContactId = c.getLong(0);
- markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
+ if (rawContactIsLocal(rawContactId)) {
+ localRawContactIds.add(rawContactId);
+ } else {
+ markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
+ }
}
} finally {
c.close();
@@ -3819,6 +3829,10 @@
mProviderStatusUpdateNeeded = true;
int result = ContactsTableUtil.deleteContact(db, contactId);
+
+ // Now purge the local raw contacts
+ deleteRawContactsImmediately(db, localRawContactIds);
+
scheduleBackgroundTask(BACKGROUND_TASK_CLEAN_DELETE_LOG);
return result;
}
@@ -3842,19 +3856,19 @@
c.close();
}
+ // When a raw contact is deleted, a sqlite trigger deletes the parent contact.
+ // TODO: all contact deletes was consolidated into ContactTableUtil but this one can't
+ // because it's in a trigger. Consider removing trigger and replacing with java code.
+ // This has to happen before the raw contact is deleted since it relies on the number
+ // of raw contacts.
final boolean contactIsSingleton =
ContactsTableUtil.deleteContactIfSingleton(db, rawContactId) == 1;
final int count;
if (callerIsSyncAdapter || rawContactIsLocal(rawContactId)) {
- // When a raw contact is deleted, a SQLite trigger deletes the parent contact.
- // TODO: all contact deletes was consolidated into ContactTableUtil but this one can't
- // because it's in a trigger. Consider removing trigger and replacing with java code.
- // This has to happen before the raw contact is deleted since it relies on the number
- // of raw contacts.
- db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null);
- count = db.delete(Tables.RAW_CONTACTS, RawContacts._ID + "=" + rawContactId, null);
- mTransactionContext.get().markRawContactChangedOrDeletedOrInserted(rawContactId);
+ ArrayList<Long> rawContactsIds = new ArrayList<>();
+ rawContactsIds.add(rawContactId);
+ count = deleteRawContactsImmediately(db, rawContactsIds);
} else {
count = markRawContactAsDeleted(db, rawContactId, callerIsSyncAdapter);
}
@@ -3865,6 +3879,43 @@
}
/**
+ * Returns the number of raw contacts that were deleted immediately -- we don't merely set
+ * the DELETED column to 1, the entire raw contact row is deleted straightaway.
+ */
+ private int deleteRawContactsImmediately(SQLiteDatabase db, List<Long> rawContactIds) {
+ if (rawContactIds == null || rawContactIds.isEmpty()) {
+ return 0;
+ }
+
+ // Build the where clause for the raw contacts to be deleted
+ ArrayList<String> whereArgs = new ArrayList<>();
+ StringBuilder whereClause = new StringBuilder(rawContactIds.size() * 2 - 1);
+ whereClause.append(" IN (?");
+ whereArgs.add(String.valueOf(rawContactIds.get(0)));
+ for (int i = 1; i < rawContactIds.size(); i++) {
+ whereClause.append(",?");
+ whereArgs.add(String.valueOf(rawContactIds.get(i)));
+ }
+ whereClause.append(")");
+
+ // Remove presence rows
+ db.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + whereClause.toString(),
+ whereArgs.toArray(new String[0]));
+
+ // Remove raw contact rows
+ int result = db.delete(Tables.RAW_CONTACTS, RawContacts._ID + whereClause.toString(),
+ whereArgs.toArray(new String[0]));
+
+ if (result > 0) {
+ for (Long rawContactId : rawContactIds) {
+ mTransactionContext.get().markRawContactChangedOrDeletedOrInserted(rawContactId);
+ }
+ }
+
+ return result;
+ }
+
+ /**
* Returns whether the given raw contact ID is local (i.e. has no account associated with it).
*/
private boolean rawContactIsLocal(long rawContactId) {
diff --git a/test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java b/test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
index 2af9829..e0a7836 100644
--- a/test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
+++ b/test_common/src/com/android/providers/contacts/testutil/DatabaseAsserts.java
@@ -16,6 +16,7 @@
package com.android.providers.contacts.testutil;
+import android.accounts.Account;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
@@ -50,12 +51,21 @@
}
/**
- * Create a contact and assert that the record exists.
+ * Create a contact in the local account and assert that the record exists.
*
* @return The created contact id pair.
*/
public static ContactIdPair assertAndCreateContact(ContentResolver resolver) {
- long rawContactId = RawContactUtil.createRawContactWithName(resolver);
+ return assertAndCreateContact(resolver, null);
+ }
+
+ /**
+ * Create a contact in the given account and assert that the record exists.
+ *
+ * @return The created contact id pair.
+ */
+ public static ContactIdPair assertAndCreateContact(ContentResolver resolver, Account account) {
+ long rawContactId = RawContactUtil.createRawContactWithName(resolver, account);
long contactId = RawContactUtil.queryContactIdByRawContactId(resolver, rawContactId);
MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId);
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 6421c8f..0ad1da6 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -7190,6 +7190,98 @@
assertEquals(1, mResolver.delete(lookupUri, null, null));
}
+ public void testDeleteContactComposedOfSingleLocalRawContact() {
+ // Create a raw contact in the local (null) account
+ long rawContactId = RawContactUtil.createRawContact(mResolver, null);
+ DataUtil.insertStructuredName(mResolver, rawContactId, "John", "Smith");
+
+ // Delete the contact
+ long contactId = queryContactId(rawContactId);
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ assertEquals(1, mResolver.delete(contactUri, null, null));
+
+ // Assert that the raw contact was removed
+ Cursor c1 = queryRawContact(rawContactId);
+ assertEquals(0, c1.getCount());
+ c1.close();
+
+ // Assert that the contact was removed
+ Cursor c2 = mResolver.query(contactUri, null, null, null, "");
+ assertEquals(0, c2.getCount());
+ c2.close();
+ }
+
+ public void testDeleteContactComposedOfTwoLocalRawContacts() {
+ // Create a raw contact in the local (null) account
+ long rawContactId1 = RawContactUtil.createRawContact(mResolver, null);
+ DataUtil.insertStructuredName(mResolver, rawContactId1, "John", "Smith");
+
+ // Create another local raw contact with the same name
+ long rawContactId2 = RawContactUtil.createRawContact(mResolver, null);
+ DataUtil.insertStructuredName(mResolver, rawContactId2, "John", "Smith");
+
+ // Join the two raw contacts explicitly
+ setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
+ rawContactId1, rawContactId2);
+
+ // Check that the two raw contacts are aggregated together
+ assertAggregated(rawContactId1, rawContactId2, "John Smith");
+
+ // Delete the aggregate contact
+ long contactId = queryContactId(rawContactId1);
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ assertEquals(1, mResolver.delete(contactUri, null, null));
+
+ // Assert that both of the local raw contacts were removed completely
+ Cursor c1 = queryRawContact(rawContactId1);
+ assertEquals(0, c1.getCount());
+ c1.close();
+
+ Cursor c2 = queryRawContact(rawContactId2);
+ assertEquals(0, c2.getCount());
+ c2.close();
+
+ // Assert that the contact was removed
+ Cursor c3 = queryContact(contactId);
+ assertEquals(0, c3.getCount());
+ c3.close();
+ }
+
+ public void testDeleteContactComposedOfSomeLocalRawContacts() {
+ // Create a raw contact in the local (null) account
+ long rawContactId1 = RawContactUtil.createRawContact(mResolver, null);
+ DataUtil.insertStructuredName(mResolver, rawContactId1, "John", "Smith");
+
+ // Create another one in a non-local account with the same name
+ long rawContactId2 = RawContactUtil.createRawContact(mResolver, mAccount);
+ DataUtil.insertStructuredName(mResolver, rawContactId2, "John", "Smith");
+
+ // Check that the two new raw contacts are aggregated together
+ assertAggregated(rawContactId1, rawContactId2, "John Smith");
+
+ // Delete the aggregate contact
+ long contactId = queryContactId(rawContactId1);
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ assertEquals(1, mResolver.delete(contactUri, null, null));
+
+ // Assert that the local raw contact was removed completely
+ Cursor c1 = queryRawContact(rawContactId1);
+ assertEquals(0, c1.getCount());
+ c1.close();
+
+ // Assert that the non-local raw contact is still present just marked as deleted
+ Cursor c2 = queryRawContact(rawContactId2);
+ assertEquals(1, c2.getCount());
+ assertTrue(c2.moveToFirst());
+ assertEquals(1, c2.getInt(c2.getColumnIndex(RawContacts.DELETED)));
+ c2.close();
+
+ // Assert that the contact was removed
+ Cursor c3 = queryContact(contactId);
+ assertEquals(0, c3.getCount());
+ c3.close();
+ }
+
public void testQueryContactWithEscapedUri() {
ContentValues values = new ContentValues();
values.put(RawContacts.SOURCE_ID, "!@#$%^&*()_+=-/.,<>?;'\":[]}{\\|`~");
@@ -8889,7 +8981,7 @@
}
public void testContactDelete_marksRawContactsForDeletion() {
- DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
+ DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete(mAccount);
String[] projection = new String[]{ContactsContract.RawContacts.DIRTY,
ContactsContract.RawContacts.DELETED};
@@ -8903,7 +8995,7 @@
}
public void testContactDelete_checkRawContactContactId() {
- DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
+ DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete(mAccount);
String[] projection = new String[]{ContactsContract.RawContacts.CONTACT_ID};
String[] record = RawContactUtil.queryByRawContactId(mResolver, ids.mRawContactId,
@@ -9144,13 +9236,23 @@
}
/**
- * Create a contact. Assert it's not present in the delete log. Delete it.
- * And assert that the contact record is no longer present.
+ * Creates a contact in the local account. Assert it's not present in the delete log.
+ * Delete it. And assert that the contact record is no longer present.
*
* @return The contact id and raw contact id that was created.
*/
private DatabaseAsserts.ContactIdPair assertContactCreateDelete() {
- DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+ return assertContactCreateDelete(null);
+ }
+
+ /**
+ * Creates a contact in the given account. Assert it's not present in the delete log.
+ * Delete it. And assert that the contact record is no longer present.
+ * @return The contact id and raw contact id that was created.
+ */
+ private DatabaseAsserts.ContactIdPair assertContactCreateDelete(Account account) {
+ DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver,
+ account);
assertEquals(CommonDatabaseUtils.NOT_FOUND,
DeletedContactUtil.queryDeletedTimestampForContactId(mResolver, ids.mContactId));