Use semi-stable ids in Contact lists

Manual tests:
-After focusing on a contact in Dialer's and Contacts' contact list
 and updating CP2 in the background, the current contact isn't repeated
 by talkback
-After focusing on a contact and deleting the contact in the background
 a new contact's name is read aloud by talkback.
-Removed and re-added an account. Focused on a contact in Contacts
 while the the focus contacts were synced. Noticed the contact
 name wasn't repeatedly spoken by talkback.

This is Part 1/2 for b/17195707. Part 2 will fix this issue for
Contacts' contact-pickers.

Bug: 17195707
Change-Id: Icb8c1b7f7ab16ad8d2e8c985088583e5b8f7b5ec
diff --git a/src/com/android/contacts/common/list/ContactEntryListAdapter.java b/src/com/android/contacts/common/list/ContactEntryListAdapter.java
index 495b27e..784ab63 100644
--- a/src/com/android/contacts/common/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/common/list/ContactEntryListAdapter.java
@@ -734,6 +734,22 @@
 
     }
 
+    @Override
+    public boolean hasStableIds() {
+        // Whenever bindViewId() is called, the values passed into setId() are stable or
+        // stable-ish. For example, when one contact is modified we don't expect a second
+        // contact's Contact._ID values to change.
+        return true;
+    }
+
+    protected void bindViewId(final ContactListItemView view, Cursor cursor, int idColumn) {
+        // Set a semi-stable id, so that talkback won't get confused when the list gets
+        // refreshed. There is little harm in inserting the same ID twice.
+        long contactId = cursor.getLong(idColumn);
+        view.setId((int) (contactId % Integer.MAX_VALUE));
+
+    }
+
     protected Uri getContactUri(int partitionIndex, Cursor cursor,
             int contactIdColumn, int lookUpKeyColumn) {
         long contactId = cursor.getLong(contactIdColumn);
diff --git a/src/com/android/contacts/common/list/ContactListAdapter.java b/src/com/android/contacts/common/list/ContactListAdapter.java
index 53e589e..7e9a2e9 100644
--- a/src/com/android/contacts/common/list/ContactListAdapter.java
+++ b/src/com/android/contacts/common/list/ContactListAdapter.java
@@ -247,10 +247,12 @@
         }
     }
 
-    protected void bindName(final ContactListItemView view, Cursor cursor) {
+    protected void bindNameAndViewId(final ContactListItemView view, Cursor cursor) {
         view.showDisplayName(
                 cursor, ContactQuery.CONTACT_DISPLAY_NAME, getContactNameDisplayOrder());
         // Note: we don't show phonetic any more (See issue 5265330)
+
+        bindViewId(view, cursor, ContactQuery.CONTACT_ID);
     }
 
     protected void bindPresenceAndStatusMessage(final ContactListItemView view, Cursor cursor) {
diff --git a/src/com/android/contacts/common/list/DefaultContactListAdapter.java b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
index b47608e..acfad88 100644
--- a/src/com/android/contacts/common/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/common/list/DefaultContactListAdapter.java
@@ -196,7 +196,7 @@
             }
         }
 
-        bindName(view, cursor);
+        bindNameAndViewId(view, cursor);
         bindPresenceAndStatusMessage(view, cursor);
 
         if (isSearchMode()) {
diff --git a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
index d6987c5..1342860 100644
--- a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
@@ -357,6 +357,8 @@
         }
         cursor.moveToPosition(position);
 
+        bindViewId(view, cursor, PhoneQuery.PHONE_ID);
+
         bindSectionHeaderAndDivider(view, position);
         if (isFirstEntry) {
             bindName(view, cursor);