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));