Fixed issue where some contacts are not added to delete log.

Contacts with multiple raw contacts were not being inserted into the delete
log when accounts are removed.  This was due to the raw contacts being removed
in batch instead of one at a time. The logic to determine whether a contact
can be deleted was determining whether other raw contacts exist.  This does
not work when all raw contacts are removed in a single batch.

Added logic to pre-select contacts that will be deleted.

Bug: 8696462
Change-Id: I95adccf9e6756bbf6ca9dd7d144c1d9ee8905631
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index e114d4c..5754ced 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -2308,7 +2308,7 @@
         }
 
         final Set<Long> changedRawContacts = mTransactionContext.get().getChangedRawContactIds();
-        ContactsTableUtil.updateContactLastUpdate(db, changedRawContacts);
+        ContactsTableUtil.updateContactLastUpdateByRawContactId(db, changedRawContacts);
 
         // Update sync states.
         for (Map.Entry<Long, Object> entry : mTransactionContext.get().getUpdatedSyncStates()) {
@@ -4701,8 +4701,9 @@
 
                     // getAccountIdOrNull() really shouldn't return null here, but just in case...
                     if (accountIdOrNull != null) {
+                        final String accountId = Long.toString(accountIdOrNull);
                         final String[] accountIdParams =
-                                new String[] {Long.toString(accountIdOrNull)};
+                                new String[] {accountId};
                         db.execSQL(
                                 "DELETE FROM " + Tables.GROUPS +
                                 " WHERE " + GroupsColumns.ACCOUNT_ID + " = ?",
@@ -4737,17 +4738,27 @@
                             // Contacts are deleted by a trigger on the raw_contacts table.
                             // But we also need to insert the contact into the delete log.
                             // This logic is being consolidated into the ContactsTableUtil.
-                            HashSet<Long> rawContactIds = Sets.newHashSet();
-                            final Cursor cursor = db.rawQuery(
-                                    "SELECT " + RawContactsColumns.CONCRETE_ID +
+
+                            // deleteContactIfSingleton() does not work in this case because raw
+                            // contacts will be deleted in a single batch below.  Contacts with
+                            // multiple raw contacts in the same account will be missed.
+
+                            // Find all contacts that do not have raw contacts in other accounts.
+                            // These should be deleted.
+                            Cursor cursor = db.rawQuery(
+                                    "SELECT " + RawContactsColumns.CONCRETE_CONTACT_ID +
                                             " FROM " + Tables.RAW_CONTACTS +
-                                            " WHERE " + RawContactsColumns.ACCOUNT_ID + " = ?",
-                                    accountIdParams);
+                                            " WHERE " + RawContactsColumns.ACCOUNT_ID + " = ?1" +
+                                            " AND " + RawContactsColumns.CONCRETE_CONTACT_ID +
+                                            " NOT IN (" +
+                                            "    SELECT " + RawContactsColumns.CONCRETE_CONTACT_ID +
+                                            "    FROM " + Tables.RAW_CONTACTS +
+                                            "    WHERE " + RawContactsColumns.ACCOUNT_ID + " != ?1"
+                                            + ")", accountIdParams);
                             try {
                                 while (cursor.moveToNext()) {
-                                    final long rawContactId = cursor.getLong(0);
-                                    rawContactIds.add(rawContactId);
-                                    ContactsTableUtil.deleteContactIfSingleton(db, rawContactId);
+                                    final long contactId = cursor.getLong(0);
+                                    ContactsTableUtil.deleteContact(db, contactId);
                                 }
                             } finally {
                                 MoreCloseables.closeQuietly(cursor);
@@ -4755,7 +4766,27 @@
 
                             // If the contact was not deleted, it's last updated timestamp needs to
                             // be refreshed since one of it's raw contacts got removed.
-                            ContactsTableUtil.updateContactLastUpdate(db, rawContactIds);
+                            // Find all contacts that will not be deleted (i.e. contacts with
+                            // raw contacts in other accounts)
+                            cursor = db.rawQuery(
+                                    "SELECT DISTINCT " + RawContactsColumns.CONCRETE_CONTACT_ID +
+                                            " FROM " + Tables.RAW_CONTACTS +
+                                            " WHERE " + RawContactsColumns.ACCOUNT_ID + " = ?1" +
+                                            " AND " + RawContactsColumns.CONCRETE_CONTACT_ID +
+                                            " IN (" +
+                                            "    SELECT " + RawContactsColumns.CONCRETE_CONTACT_ID +
+                                            "    FROM " + Tables.RAW_CONTACTS +
+                                            "    WHERE " + RawContactsColumns.ACCOUNT_ID + " != ?1"
+                                            + ")", accountIdParams);
+                            try {
+                                while (cursor.moveToNext()) {
+                                    final long contactId = cursor.getLong(0);
+                                    ContactsTableUtil.updateContactLastUpdateByContactId(db,
+                                            contactId);
+                                }
+                            } finally {
+                                MoreCloseables.closeQuietly(cursor);
+                            }
                         }
 
                         db.execSQL(
diff --git a/src/com/android/providers/contacts/database/ContactsTableUtil.java b/src/com/android/providers/contacts/database/ContactsTableUtil.java
index 27c3d5b..dbc3d3e 100644
--- a/src/com/android/providers/contacts/database/ContactsTableUtil.java
+++ b/src/com/android/providers/contacts/database/ContactsTableUtil.java
@@ -19,6 +19,7 @@
 import static android.provider.ContactsContract.Contacts;
 import static com.android.providers.contacts.ContactsDatabaseHelper.Tables;
 
+import android.content.ContentValues;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.provider.ContactsContract;
@@ -54,13 +55,22 @@
                 Contacts.CONTACT_LAST_UPDATED_TIMESTAMP));
     }
 
+    public static void updateContactLastUpdateByContactId(SQLiteDatabase db, long contactId) {
+        final ContentValues values = new ContentValues();
+        values.put(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
+                Clock.getInstance().currentTimeMillis());
+        db.update(Tables.CONTACTS, values, Contacts._ID + " = ?",
+                new String[] {String.valueOf(contactId)});
+    }
+
     /**
      * Refreshes the last updated timestamp of the contact with the current time.
      *
      * @param db The sqlite database instance.
      * @param rawContactIds A set of raw contacts ids to refresh the contact for.
      */
-    public static void updateContactLastUpdate(SQLiteDatabase db, Set<Long> rawContactIds) {
+    public static void updateContactLastUpdateByRawContactId(SQLiteDatabase db,
+            Set<Long> rawContactIds) {
         if (rawContactIds.isEmpty()) {
             return;
         }