Merge "Add api to disable BackgroundImageView animation." into pi-car-dev
diff --git a/car-telephony-common/src/com/android/car/telephony/common/Contact.java b/car-telephony-common/src/com/android/car/telephony/common/Contact.java
index 69904b4..2f6212d 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/Contact.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/Contact.java
@@ -37,128 +37,80 @@
  */
 public class Contact implements Parcelable, Comparable<Contact> {
     private static final String TAG = "CD.Contact";
+
+    /**
+     * Column name for phonebook label column.
+     */
     private static final String PHONEBOOK_LABEL = "phonebook_label";
+    /**
+     * Column name for alternative phonebook label column.
+     */
     private static final String PHONEBOOK_LABEL_ALT = "phonebook_label_alt";
 
     /**
      * Contact belongs to TYPE_LETTER if its display name starts with a letter
      */
     private static final int TYPE_LETTER = 1;
-
     /**
      * Contact belongs to TYPE_DIGIT if its display name starts with a digit
      */
     private static final int TYPE_DIGIT = 2;
-
     /**
-     * Contact belongs to TYPE_OTHER if it does not belong to TYPE_LETTER or TYPE_DIGIT
-     * Such as empty display name or the display name starts with "_"
+     * Contact belongs to TYPE_OTHER if it does not belong to TYPE_LETTER or TYPE_DIGIT Such as
+     * empty display name or the display name starts with "_"
      */
     private static final int TYPE_OTHER = 3;
 
+    /**
+     * A reference to the {@link ContactsContract.RawContacts#CONTACT_ID}.
+     */
+    private long mContactId;
 
     /**
-     * A reference to the {@link ContactsContract.Contacts#_ID} that this data belongs to. See
-     * {@link ContactsContract.Contacts.Entity#CONTACT_ID}
+     * A reference to the {@link ContactsContract.Data#RAW_CONTACT_ID}.
      */
-    private long mId;
+    private long mRawContactId;
 
     /**
-     * Whether this contact entry is starred by user.
+     * The name of the account instance to which this row belongs, which identifies a specific
+     * account. See {@link ContactsContract.RawContacts#ACCOUNT_NAME}.
      */
-    private boolean mIsStarred;
-
-    /**
-     * Contact-specific information about whether or not a contact has been pinned by the user at
-     * a particular position within the system contact application's user interface.
-     */
-    private int mPinnedPosition;
-
-    /**
-     * All phone numbers of this contact mapping to the unique primary key for the raw data entry.
-     */
-    private List<PhoneNumber> mPhoneNumbers = new ArrayList<>();
+    private String mAccountName;
 
     /**
      * The display name.
      * <p>
-     * The standard text shown as the contact's display name, based on the best
-     * available information for the contact.
+     * The standard text shown as the contact's display name, based on the best available
+     * information for the contact.
      * </p>
-     *
-     * @see ContactsContract.CommonDataKinds.Phone#DISPLAY_NAME
+     * <p>
+     * See {@link ContactsContract.CommonDataKinds.Phone#DISPLAY_NAME}.
      */
     private String mDisplayName;
 
     /**
-     * Sort key that takes into account locale-based traditions for sorting names in address books.
-     *
-     * @see ContactsContract.CommonDataKinds.Phone#SORT_KEY_PRIMARY
-     */
-    private String mSortKeyPrimary;
-
-    /**
      * The alternative display name.
      * <p>
-     * An alternative representation of the display name, such as "family name first"
-     * instead of "given name first" for Western names.  If an alternative is not
-     * available, the values should be the same as {@link #mDisplayName}.
+     * An alternative representation of the display name, such as "family name first" instead of
+     * "given name first" for Western names.  If an alternative is not available, the values should
+     * be the same as {@link #mDisplayName}.
      * </p>
-     *
-     * @see ContactsContract.CommonDataKinds.Phone#DISPLAY_NAME_ALTERNATIVE
-     */
-    private String mAltDisplayName;
-
-    /**
-     * Sort key based on the alternative representation of the full name.
-     *
-     * @see ContactsContract.CommonDataKinds.Phone#SORT_KEY_ALTERNATIVE
-     */
-    private String mAltSortKey;
-
-    /**
-     * The phonebook label.
      * <p>
-     * For {@link #mDisplayName}s starting with letters, label will be the first character of
-     * {@link #mDisplayName}. For {@link #mDisplayName}s starting with numbers, the label will
-     * be "#". For {@link #mDisplayName}s starting with other characters, the label will be "...".
-     * </p>
+     * See {@link ContactsContract.CommonDataKinds.Phone#DISPLAY_NAME_ALTERNATIVE}.
      */
-    private String mPhoneBookLabel;
+    private String mDisplayNameAlt;
 
     /**
-     * The alternative phonebook label.
-     * <p>
-     * It is similar with {@link #mPhoneBookLabel}. But instead of generating from
-     * {@link #mDisplayName}, it will use {@link #mAltDisplayName}.
-     * </p>
+     * The given name for the contact. See
+     * {@link ContactsContract.CommonDataKinds.StructuredName#GIVEN_NAME}.
      */
-    private String mPhoneBookLabelAlt;
+    private String mGivenName;
 
     /**
-     * A URI that can be used to retrieve a thumbnail of the contact's photo.
+     * The family name for the contact. See
+     * {@link ContactsContract.CommonDataKinds.StructuredName#FAMILY_NAME}.
      */
-    private Uri mAvatarThumbnailUri;
-
-    /**
-     * A URI that can be used to retrieve the contact's full-size photo.
-     */
-    private Uri mAvatarUri;
-
-    /**
-     * An opaque value that contains hints on how to find the contact if its row id changed
-     * as a result of a sync or aggregation. If a contact has multiple phone numbers, all phone
-     * numbers are recorded in a single entry and they all have the same look up key in a single
-     * load.
-     */
-    private String mLookupKey;
-
-    /**
-     * Whether this contact represents a voice mail.
-     */
-    private boolean mIsVoiceMail;
-
-    private PhoneNumber mPrimaryPhoneNumber;
+    private String mFamilyName;
 
     /**
      * The initials of the contact's name.
@@ -166,69 +118,221 @@
     private String mInitials;
 
     /**
-     * Parses a Contact entry for a Cursor loaded from the Contact Database.
+     * The phonebook label.
+     * <p>
+     * For {@link #mDisplayName}s starting with letters, label will be the first character of {@link
+     * #mDisplayName}. For {@link #mDisplayName}s starting with numbers, the label will be "#". For
+     * {@link #mDisplayName}s starting with other characters, the label will be "...".
+     * </p>
+     */
+    private String mPhoneBookLabel;
+
+    /**
+     * The alternative phonebook label.
+     * <p>
+     * It is similar with {@link #mPhoneBookLabel}. But instead of generating from {@link
+     * #mDisplayName}, it will use {@link #mDisplayNameAlt}.
+     * </p>
+     */
+    private String mPhoneBookLabelAlt;
+
+    /**
+     * Sort key that takes into account locale-based traditions for sorting names in address books.
+     * <p>
+     * See {@link ContactsContract.CommonDataKinds.Phone#SORT_KEY_PRIMARY}.
+     */
+    private String mSortKeyPrimary;
+
+    /**
+     * Sort key based on the alternative representation of the full name.
+     * <p>
+     * See {@link ContactsContract.CommonDataKinds.Phone#SORT_KEY_ALTERNATIVE}.
+     */
+    private String mSortKeyAlt;
+
+    /**
+     * An opaque value that contains hints on how to find the contact if its row id changed as a
+     * result of a sync or aggregation. If a contact has multiple phone numbers, all phone numbers
+     * are recorded in a single entry and they all have the same look up key in a single load. See
+     * {@link ContactsContract.Data#LOOKUP_KEY}.
+     */
+    private String mLookupKey;
+
+    /**
+     * All phone numbers of this contact mapping to the unique primary key for the raw data entry.
+     */
+    private List<PhoneNumber> mPhoneNumbers = new ArrayList<>();
+
+    /**
+     * A URI that can be used to retrieve a thumbnail of the contact's photo.
+     */
+    @Nullable
+    private Uri mAvatarThumbnailUri;
+
+    /**
+     * A URI that can be used to retrieve the contact's full-size photo.
+     */
+    @Nullable
+    private Uri mAvatarUri;
+
+    /**
+     * Whether this contact entry is starred by user.
+     */
+    private boolean mIsStarred;
+
+    /**
+     * Contact-specific information about whether or not a contact has been pinned by the user at a
+     * particular position within the system contact application's user interface.
+     */
+    private int mPinnedPosition;
+
+    /**
+     * This contact's primary phone number. Its value is null if a primary phone number is not set.
+     */
+    @Nullable
+    private PhoneNumber mPrimaryPhoneNumber;
+
+    /**
+     * Whether this contact represents a voice mail.
+     */
+    private boolean mIsVoiceMail;
+
+    /**
+     * Parses a contact entry for a Cursor loaded from the Contact Database. A new contact will be
+     * created and returned.
      */
     public static Contact fromCursor(Context context, Cursor cursor) {
-        int contactIdColumn = cursor.getColumnIndex(
-                ContactsContract.CommonDataKinds.Phone.CONTACT_ID);
-        int starredColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.STARRED);
-        int pinnedColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.PINNED);
-        int displayNameColumn = cursor.getColumnIndex(
-                ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
-        int altDisplayNameColumn = cursor.getColumnIndex(
-                ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_ALTERNATIVE);
+        return fromCursor(context, cursor, null);
+    }
+
+    /**
+     * Parses a contact entry for a Cursor loaded from the Contact Database.
+     *
+     * @param contact should have the same {@link #mLookupKey} and {@link #mAccountName} with the
+     *                data read from the cursor, so all the data from the cursor can be loaded into
+     *                this contact. If either of their {@link #mLookupKey} and {@link #mAccountName}
+     *                is not the same or this contact is null, a new contact will be created and
+     *                returned.
+     */
+    public static Contact fromCursor(Context context, Cursor cursor, @Nullable Contact contact) {
+        int accountNameColumn = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_NAME);
+        int lookupKeyColumn = cursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);
+        String accountName = cursor.getString(accountNameColumn);
+        String lookupKey = cursor.getString(lookupKeyColumn);
+
+        if (contact == null) {
+            Log.d(TAG, "A new contact will be created.");
+            contact = new Contact();
+            contact.loadBasicInfo(cursor);
+        }
+
+        if (!accountName.equals(contact.mAccountName) || !lookupKey.equals(contact.mLookupKey)) {
+            Log.w(TAG, "A wrong contact is passed in. A new contact will be created.");
+            contact = new Contact();
+            contact.loadBasicInfo(cursor);
+        }
+
+        int mimetypeColumn = cursor.getColumnIndex(ContactsContract.Data.MIMETYPE);
+        String mimeType = cursor.getString(mimetypeColumn);
+
+        // More mimeType can be added here if more types of data needs to be loaded.
+        switch (mimeType) {
+            case ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE:
+                contact.loadNameDetails(cursor);
+                break;
+            case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:
+                contact.addPhoneNumber(context, cursor);
+                break;
+            default:
+                Log.d(TAG,
+                        String.format("This mimetype %s will not be loaded right now.", mimeType));
+        }
+
+        return contact;
+    }
+
+    /**
+     * The data columns that are the same in every cursor no matter what the mimetype is will be
+     * loaded here.
+     */
+    private void loadBasicInfo(Cursor cursor) {
+        int contactIdColumn = cursor.getColumnIndex(ContactsContract.RawContacts.CONTACT_ID);
+        int rawContactIdColumn = cursor.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID);
+        int accountNameColumn = cursor.getColumnIndex(ContactsContract.RawContacts.ACCOUNT_NAME);
+        int displayNameColumn = cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME);
+        int displayNameAltColumn = cursor.getColumnIndex(
+                ContactsContract.RawContacts.DISPLAY_NAME_ALTERNATIVE);
         int phoneBookLabelColumn = cursor.getColumnIndex(PHONEBOOK_LABEL);
         int phoneBookLabelAltColumn = cursor.getColumnIndex(PHONEBOOK_LABEL_ALT);
-        int avatarUriColumn = cursor.getColumnIndex(
-                ContactsContract.CommonDataKinds.Phone.PHOTO_URI);
-        int avatarThumbnailColumn = cursor.getColumnIndex(
-                ContactsContract.CommonDataKinds.Phone.PHOTO_THUMBNAIL_URI);
-        int lookupKeyColumn = cursor.getColumnIndex(
-                ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY);
         int sortKeyPrimaryColumn = cursor.getColumnIndex(
-                ContactsContract.CommonDataKinds.Phone.SORT_KEY_PRIMARY);
+                ContactsContract.RawContacts.SORT_KEY_PRIMARY);
         int sortKeyAltColumn = cursor.getColumnIndex(
-                ContactsContract.CommonDataKinds.Phone.SORT_KEY_ALTERNATIVE);
+                ContactsContract.RawContacts.SORT_KEY_ALTERNATIVE);
+        int lookupKeyColumn = cursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);
 
-        Contact contact = new Contact();
-        contact.mId = cursor.getLong(contactIdColumn);
-        contact.mDisplayName = cursor.getString(displayNameColumn);
-        contact.mSortKeyPrimary = cursor.getString(sortKeyPrimaryColumn);
-        contact.mAltDisplayName = cursor.getString(altDisplayNameColumn);
-        contact.mAltSortKey = cursor.getString(sortKeyAltColumn);
-        contact.mPhoneBookLabel = cursor.getString(phoneBookLabelColumn);
-        contact.mPhoneBookLabelAlt = cursor.getString(phoneBookLabelAltColumn);
+        int avatarUriColumn = cursor.getColumnIndex(ContactsContract.Data.PHOTO_URI);
+        int avatarThumbnailColumn = cursor.getColumnIndex(
+                ContactsContract.Data.PHOTO_THUMBNAIL_URI);
+        int starredColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.STARRED);
+        int pinnedColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.PINNED);
 
-        PhoneNumber number = PhoneNumber.fromCursor(context, cursor);
-        contact.mPhoneNumbers.add(number);
-        if (number.isPrimary()) {
-            contact.mPrimaryPhoneNumber = number;
-        }
-
-        contact.mIsStarred = cursor.getInt(starredColumn) > 0;
-        contact.mPinnedPosition = cursor.getInt(pinnedColumn);
-        contact.mIsVoiceMail = TelecomUtils.isVoicemailNumber(context, number.getNumber());
+        mContactId = cursor.getLong(contactIdColumn);
+        mRawContactId = cursor.getLong(rawContactIdColumn);
+        mAccountName = cursor.getString(accountNameColumn);
+        mDisplayName = cursor.getString(displayNameColumn);
+        mDisplayNameAlt = cursor.getString(displayNameAltColumn);
+        mSortKeyPrimary = cursor.getString(sortKeyPrimaryColumn);
+        mSortKeyAlt = cursor.getString(sortKeyAltColumn);
+        mPhoneBookLabel = cursor.getString(phoneBookLabelColumn);
+        mPhoneBookLabelAlt = cursor.getString(phoneBookLabelAltColumn);
+        mLookupKey = cursor.getString(lookupKeyColumn);
 
         String avatarUriStr = cursor.getString(avatarUriColumn);
-        contact.mAvatarUri = avatarUriStr == null ? null : Uri.parse(avatarUriStr);
-
+        mAvatarUri = avatarUriStr == null ? null : Uri.parse(avatarUriStr);
         String avatarThumbnailStringUri = cursor.getString(avatarThumbnailColumn);
-        contact.mAvatarThumbnailUri = avatarThumbnailStringUri == null ? null : Uri.parse(
+        mAvatarThumbnailUri = avatarThumbnailStringUri == null ? null : Uri.parse(
                 avatarThumbnailStringUri);
 
-        String lookUpKey = cursor.getString(lookupKeyColumn);
-        if (lookUpKey != null) {
-            contact.mLookupKey = lookUpKey;
-        } else {
-            Log.w(TAG, "Look up key is null. Fallback to use display name");
-            contact.mLookupKey = contact.mDisplayName;
+        mIsStarred = cursor.getInt(starredColumn) > 0;
+        mPinnedPosition = cursor.getInt(pinnedColumn);
+    }
+
+    /**
+     * Loads the data whose mimetype is
+     * {@link ContactsContract.CommonDataKinds.StructuredName#CONTENT_ITEM_TYPE}.
+     */
+    private void loadNameDetails(Cursor cursor) {
+        int firstNameColumn = cursor.getColumnIndex(
+                ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME);
+        int lastNameColumn = cursor.getColumnIndex(
+                ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME);
+
+        mGivenName = cursor.getString(firstNameColumn);
+        mFamilyName = cursor.getString(lastNameColumn);
+    }
+
+    /**
+     * Loads the data whose mimetype is
+     * {@link ContactsContract.CommonDataKinds.Phone#CONTENT_ITEM_TYPE}.
+     */
+    private void addPhoneNumber(Context context, Cursor cursor) {
+        PhoneNumber number = PhoneNumber.fromCursor(context, cursor);
+        mPhoneNumbers.add(number);
+
+        if (number.isPrimary()) {
+            mPrimaryPhoneNumber = number;
         }
-        return contact;
+
+        // TODO: update voice mail number part when start to support voice mail.
+        if (TelecomUtils.isVoicemailNumber(context, number.getNumber())) {
+            mIsVoiceMail = true;
+        }
     }
 
     @Override
     public boolean equals(Object obj) {
-        return obj instanceof Contact && mLookupKey.equals(((Contact) obj).mLookupKey);
+        return obj instanceof Contact && mLookupKey.equals(((Contact) obj).mLookupKey)
+                && mAccountName.equals(((Contact) obj).mAccountName);
     }
 
     @Override
@@ -241,15 +345,76 @@
         return mDisplayName + mPhoneNumbers;
     }
 
+    /**
+     * Returns the aggregated contact id.
+     */
+    public long getId() {
+        return mContactId;
+    }
+
+    /**
+     * Returns the raw contact id.
+     */
+    public long getRawContactId() {
+        return mRawContactId;
+    }
+
+    /**
+     * Returns a lookup uri using {@link #mContactId} and {@link #mLookupKey}. Returns null if
+     * unable to get a valid lookup URI from the provided parameters. See {@link
+     * ContactsContract.Contacts#getLookupUri(long, String)}.
+     */
+    @Nullable
+    public Uri getLookupUri() {
+        return ContactsContract.Contacts.getLookupUri(mContactId, mLookupKey);
+    }
+
+    /**
+     * Returns {@link #mAccountName}.
+     */
+    public String getAccountName() {
+        return mAccountName;
+    }
+
+    /**
+     * Returns {@link #mDisplayName}.
+     */
     public String getDisplayName() {
         return mDisplayName;
     }
 
     /**
-     * Returns alternative display name.
+     * Returns {@link #mDisplayNameAlt}.
      */
-    public String getAltDisplayName() {
-        return mAltDisplayName;
+    public String getDisplayNameAlt() {
+        return mDisplayNameAlt;
+    }
+
+    /**
+     * Returns {@link #mGivenName}.
+     */
+    public String getGivenName() {
+        return mGivenName;
+    }
+
+    /**
+     * Returns {@link #mFamilyName}.
+     */
+    public String getFamilyName() {
+        return mFamilyName;
+    }
+
+    /**
+     * Returns the initials of the contact's name.
+     */
+    //TODO: update how to get initials after refactoring. Could use last name and first name to
+    // get initials after refactoring to avoid error for those names with prefix.
+    public String getInitials() {
+        if (mInitials == null) {
+            mInitials = TelecomUtils.getInitials(mDisplayName, mDisplayNameAlt);
+        }
+
+        return mInitials;
     }
 
     /**
@@ -266,80 +431,67 @@
         return mPhoneBookLabelAlt;
     }
 
-    public boolean isVoicemail() {
-        return mIsVoiceMail;
-    }
-
-    @Nullable
-    public Uri getAvatarUri() {
-        return mAvatarThumbnailUri != null ? mAvatarThumbnailUri : mAvatarUri;
-    }
-
+    /**
+     * Returns {@link #mLookupKey}.
+     */
     public String getLookupKey() {
         return mLookupKey;
     }
 
-    public Uri getLookupUri() {
-        return ContactsContract.Contacts.getLookupUri(mId, mLookupKey);
+    /**
+     * Returns the Uri for avatar.
+     */
+    @Nullable
+    public Uri getAvatarUri() {
+        return mAvatarUri != null ? mAvatarUri : mAvatarThumbnailUri;
     }
 
-    /** Return all phone numbers associated with this contact. */
+    /**
+     * Return all phone numbers associated with this contact.
+     */
     public List<PhoneNumber> getNumbers() {
         return mPhoneNumbers;
     }
 
-    /** Return the aggregated contact id. */
-    public long getId() {
-        return mId;
+    /**
+     * Returns if this Contact represents a voice mail number.
+     */
+    public boolean isVoicemail() {
+        return mIsVoiceMail;
     }
 
+    /**
+     * Returns if this contact has a primary phone number.
+     */
+    public boolean hasPrimaryPhoneNumber() {
+        return mPrimaryPhoneNumber != null;
+    }
+
+    /**
+     * Returns the primary phone number for this Contact. Returns null if there is not one.
+     */
+    @Nullable
+    public PhoneNumber getPrimaryPhoneNumber() {
+        return mPrimaryPhoneNumber;
+    }
+
+    /**
+     * Returns if this Contact is starred.
+     */
     public boolean isStarred() {
         return mIsStarred;
     }
 
+    /**
+     * Returns {@link #mPinnedPosition}.
+     */
     public int getPinnedPosition() {
         return mPinnedPosition;
     }
 
     /**
-     * Returns the initials of the contact's name.
-     */
-    //TODO: update how to get initials after refactoring. Could use last name and first name to
-    // get initials after refactoring to avoid error for those names with prefix.
-    public String getInitials() {
-        if (mInitials == null) {
-            mInitials = TelecomUtils.getInitials(mDisplayName, mAltDisplayName);
-        }
-
-        return mInitials;
-    }
-
-    /**
-     * Merges a Contact entry with another if they represent different numbers of the same contact.
-     *
-     * @return A merged contact.
-     */
-    public Contact merge(Contact contact) {
-        if (equals(contact)) {
-            for (PhoneNumber phoneNumber : contact.mPhoneNumbers) {
-                int indexOfPhoneNumber = mPhoneNumbers.indexOf(phoneNumber);
-                if (indexOfPhoneNumber < 0) {
-                    mPhoneNumbers.add(phoneNumber);
-                } else {
-                    PhoneNumber existingPhoneNumber = mPhoneNumbers.get(indexOfPhoneNumber);
-                    existingPhoneNumber.merge(phoneNumber);
-                }
-            }
-            if (contact.mPrimaryPhoneNumber != null) {
-                mPrimaryPhoneNumber = contact.mPrimaryPhoneNumber.merge(mPrimaryPhoneNumber);
-            }
-        }
-        return this;
-    }
-
-    /**
-     * Looks up a {@link PhoneNumber} of this contact for the given phone number. Returns {@code
-     * null} if this contact doesn't contain the given phone number.
+     * Looks up a {@link PhoneNumber} of this contact for the given phone number string. Returns
+     * {@code null} if this contact doesn't contain the given phone number.
      */
     @Nullable
     public PhoneNumber getPhoneNumber(Context context, String number) {
@@ -353,15 +505,6 @@
         return null;
     }
 
-    public PhoneNumber getPrimaryPhoneNumber() {
-        return mPrimaryPhoneNumber;
-    }
-
-    /** Return if this contact has a primary phone number. */
-    public boolean hasPrimaryPhoneNumber() {
-        return mPrimaryPhoneNumber != null;
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -369,23 +512,27 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeLong(mId);
-        dest.writeBoolean(mIsStarred);
-        dest.writeInt(mPinnedPosition);
-        dest.writeInt(mPhoneNumbers.size());
-        for (PhoneNumber phoneNumber : mPhoneNumbers) {
-            dest.writeParcelable(phoneNumber, flags);
-        }
+        dest.writeLong(mContactId);
+        dest.writeLong(mRawContactId);
+        dest.writeString(mLookupKey);
+        dest.writeString(mAccountName);
         dest.writeString(mDisplayName);
+        dest.writeString(mDisplayNameAlt);
         dest.writeString(mSortKeyPrimary);
-        dest.writeString(mAltDisplayName);
-        dest.writeString(mAltSortKey);
+        dest.writeString(mSortKeyAlt);
         dest.writeString(mPhoneBookLabel);
         dest.writeString(mPhoneBookLabelAlt);
         dest.writeParcelable(mAvatarThumbnailUri, 0);
         dest.writeParcelable(mAvatarUri, 0);
-        dest.writeString(mLookupKey);
+        dest.writeBoolean(mIsStarred);
+        dest.writeInt(mPinnedPosition);
+
         dest.writeBoolean(mIsVoiceMail);
+        dest.writeParcelable(mPrimaryPhoneNumber, flags);
+        dest.writeInt(mPhoneNumbers.size());
+        for (PhoneNumber phoneNumber : mPhoneNumbers) {
+            dest.writeParcelable(phoneNumber, flags);
+        }
     }
 
     public static final Creator<Contact> CREATOR = new Creator<Contact>() {
@@ -400,12 +547,28 @@
         }
     };
 
-    /** Create {@link Contact} object from saved parcelable. */
+    /**
+     * Create {@link Contact} object from saved parcelable.
+     */
     private static Contact fromParcel(Parcel source) {
         Contact contact = new Contact();
-        contact.mId = source.readLong();
+        contact.mContactId = source.readLong();
+        contact.mRawContactId = source.readLong();
+        contact.mLookupKey = source.readString();
+        contact.mAccountName = source.readString();
+        contact.mDisplayName = source.readString();
+        contact.mDisplayNameAlt = source.readString();
+        contact.mSortKeyPrimary = source.readString();
+        contact.mSortKeyAlt = source.readString();
+        contact.mPhoneBookLabel = source.readString();
+        contact.mPhoneBookLabelAlt = source.readString();
+        contact.mAvatarThumbnailUri = source.readParcelable(Uri.class.getClassLoader());
+        contact.mAvatarUri = source.readParcelable(Uri.class.getClassLoader());
         contact.mIsStarred = source.readBoolean();
         contact.mPinnedPosition = source.readInt();
+
+        contact.mIsVoiceMail = source.readBoolean();
+        contact.mPrimaryPhoneNumber = source.readParcelable(PhoneNumber.class.getClassLoader());
         int phoneNumberListLength = source.readInt();
         contact.mPhoneNumbers = new ArrayList<>();
         for (int i = 0; i < phoneNumberListLength; i++) {
@@ -415,16 +578,7 @@
                 contact.mPrimaryPhoneNumber = phoneNumber;
             }
         }
-        contact.mDisplayName = source.readString();
-        contact.mSortKeyPrimary = source.readString();
-        contact.mAltDisplayName = source.readString();
-        contact.mAltSortKey = source.readString();
-        contact.mPhoneBookLabel = source.readString();
-        contact.mPhoneBookLabelAlt = source.readString();
-        contact.mAvatarThumbnailUri = source.readParcelable(Uri.class.getClassLoader());
-        contact.mAvatarUri = source.readParcelable(Uri.class.getClassLoader());
-        contact.mLookupKey = source.readString();
-        contact.mIsVoiceMail = source.readBoolean();
+
         return contact;
     }
 
@@ -432,24 +586,24 @@
     public int compareTo(Contact otherContact) {
         // Use a helper function to classify Contacts
         // and by default, it should be compared by first name order.
-        return compareByDisplayName(otherContact);
+        return compareBySortKeyPrimary(otherContact);
     }
 
     /**
-     * Compares contacts by their {@link #mDisplayName} in an order of
-     * letters, numbers, then special characters.
+     * Compares contacts by their {@link #mSortKeyPrimary} in an order of letters, numbers, then
+     * special characters.
      */
-    public int compareByDisplayName(@NonNull Contact otherContact) {
-        return compareNames(mSortKeyPrimary, otherContact.mSortKeyPrimary, mPhoneBookLabel,
-                otherContact.getPhonebookLabel());
+    public int compareBySortKeyPrimary(@NonNull Contact otherContact) {
+        return compareNames(mSortKeyPrimary, otherContact.mSortKeyPrimary,
+                mPhoneBookLabel, otherContact.getPhonebookLabel());
     }
 
     /**
-     * Compares contacts by their {@link #mAltDisplayName} in an order of
-     * letters, numbers, then special characters.
+     * Compares contacts by their {@link #mSortKeyAlt} in an order of letters, numbers, then special
+     * characters.
      */
-    public int compareByAltDisplayName(@NonNull Contact otherContact) {
-        return compareNames(mAltSortKey, otherContact.mAltSortKey,
+    public int compareBySortKeyAlt(@NonNull Contact otherContact) {
+        return compareNames(mSortKeyAlt, otherContact.mSortKeyAlt,
                 mPhoneBookLabelAlt, otherContact.getPhonebookLabelAlt());
     }
 
@@ -467,8 +621,8 @@
     }
 
     /**
-     * Returns the type of the name string.
-     * Types can be {@link #TYPE_LETTER}, {@link #TYPE_DIGIT} and {@link #TYPE_OTHER}.
+     * Returns the type of the name string. Types can be {@link #TYPE_LETTER}, {@link #TYPE_DIGIT}
+     * and {@link #TYPE_OTHER}.
      */
     private static int getNameType(String label) {
         // A helper function to classify Contacts
diff --git a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
index 68cefad..db95960 100644
--- a/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
+++ b/car-telephony-common/src/com/android/car/telephony/common/InMemoryPhoneBook.java
@@ -34,25 +34,30 @@
 import java.util.concurrent.Executors;
 
 /**
- * A singleton statically accessible helper class which pre-loads contacts list into memory so
- * that they can be accessed more easily and quickly.
+ * A singleton statically accessible helper class which pre-loads contacts list into memory so that
+ * they can be accessed more easily and quickly.
  */
 public class InMemoryPhoneBook implements Observer<List<Contact>> {
     private static final String TAG = "CD.InMemoryPhoneBook";
+    private static final String KEY_FORMAT = "%s %s";
     private static InMemoryPhoneBook sInMemoryPhoneBook;
 
     private final Context mContext;
     private final AsyncQueryLiveData<List<Contact>> mContactListAsyncQueryLiveData;
-    /** A map to speed up phone number searching. */
+    /**
+     * A map to speed up phone number searching.
+     */
     private final Map<I18nPhoneNumberWrapper, Contact> mPhoneNumberContactMap = new HashMap<>();
-    /** A map to look up contact by lookup key. */
+    /**
+     * A map to look up contact by lookup key.
+     */
     private final Map<String, Contact> mLookupKeyContactMap = new HashMap<>();
     private boolean mIsLoaded = false;
 
     /**
-     * Initialize the globally accessible {@link InMemoryPhoneBook}.
-     * Returns the existing {@link InMemoryPhoneBook} if already initialized.
-     * {@link #tearDown()} must be called before init to reinitialize.
+     * Initialize the globally accessible {@link InMemoryPhoneBook}. Returns the existing {@link
+     * InMemoryPhoneBook} if already initialized. {@link #tearDown()} must be called before init to
+     * reinitialize.
      */
     public static InMemoryPhoneBook init(Context context) {
         if (sInMemoryPhoneBook == null) {
@@ -63,17 +68,18 @@
     }
 
     /**
-     * Returns if the InMemoryPhoneBook is initialized.
-     * get() won't return null or throw if this is true, but it doesn't
-     * indicate whether or not contacts are loaded yet.
-     *
+     * Returns if the InMemoryPhoneBook is initialized. get() won't return null or throw if this is
+     * true, but it doesn't indicate whether or not contacts are loaded yet.
+     * <p>
      * See also: {@link #isLoaded()}
      */
     public static boolean isInitialized() {
         return sInMemoryPhoneBook != null;
     }
 
-    /** Get the global {@link InMemoryPhoneBook} instance. */
+    /**
+     * Get the global {@link InMemoryPhoneBook} instance.
+     */
     public static InMemoryPhoneBook get() {
         if (sInMemoryPhoneBook != null) {
             return sInMemoryPhoneBook;
@@ -82,7 +88,9 @@
         }
     }
 
-    /** Tears down the globally accessible {@link InMemoryPhoneBook}. */
+    /**
+     * Tears down the globally accessible {@link InMemoryPhoneBook}.
+     */
     public static void tearDown() {
         sInMemoryPhoneBook.onTearDown();
         sInMemoryPhoneBook = null;
@@ -94,9 +102,11 @@
         QueryParam contactListQueryParam = new QueryParam(
                 ContactsContract.Data.CONTENT_URI,
                 null,
-                ContactsContract.Data.MIMETYPE + " = ?",
+                ContactsContract.Data.MIMETYPE + " = ? OR "
+                        + ContactsContract.Data.MIMETYPE + " = ?",
                 new String[]{
-                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE},
+                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE,
+                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE},
                 ContactsContract.Contacts.DISPLAY_NAME + " ASC ");
         mContactListAsyncQueryLiveData = new AsyncQueryLiveData<List<Contact>>(mContext,
                 QueryParam.of(contactListQueryParam), Executors.newSingleThreadExecutor()) {
@@ -148,11 +158,11 @@
     }
 
     /**
-     * Looks up a {@link Contact} by the given lookup key. Returns null if can't find the contact
-     * entry.
+     * Looks up a {@link Contact} by the given lookup key and account name. Returns null if can't
+     * find the contact entry.
      */
     @Nullable
-    public Contact lookupContactByKey(String lookupKey) {
+    public Contact lookupContactByKey(String lookupKey, String accountName) {
         if (!isLoaded()) {
             Log.w(TAG, "looking up a contact while loading.");
         }
@@ -160,37 +170,40 @@
             Log.w(TAG, "looking up an empty lookup key.");
             return null;
         }
+        if (TextUtils.isEmpty(accountName)) {
+            Log.w(TAG, "looking up an empty lookup account");
+            return null;
+        }
 
-        return mLookupKeyContactMap.get(lookupKey);
+        return mLookupKeyContactMap.get(getContactKey(lookupKey, accountName));
     }
 
     private List<Contact> onCursorLoaded(Cursor cursor) {
-        Map<String, Contact> result = new LinkedHashMap<>();
-        List<Contact> contacts = new ArrayList<>();
+        Map<String, Contact> contactMap = new LinkedHashMap<>();
+        List<Contact> contactList = new ArrayList<>();
 
         while (cursor.moveToNext()) {
-            Contact contact = Contact.fromCursor(mContext, cursor);
-            String lookupKey = contact.getLookupKey();
-            if (result.containsKey(lookupKey)) {
-                Contact existingContact = result.get(lookupKey);
-                existingContact.merge(contact);
-            } else {
-                result.put(lookupKey, contact);
-            }
+            int accountNameColumn = cursor.getColumnIndex(
+                    ContactsContract.RawContacts.ACCOUNT_NAME);
+            int lookupKeyColumn = cursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY);
+            String accountName = cursor.getString(accountNameColumn);
+            String lookupKey = cursor.getString(lookupKeyColumn);
+            String key = getContactKey(lookupKey, accountName);
+
+            contactMap.put(key, Contact.fromCursor(mContext, cursor, contactMap.get(key)));
         }
 
-        contacts.addAll(result.values());
+        contactList.addAll(contactMap.values());
 
         mLookupKeyContactMap.clear();
-        mLookupKeyContactMap.putAll(result);
+        mLookupKeyContactMap.putAll(contactMap);
 
-        mPhoneNumberContactMap.clear();
-        for (Contact contact : contacts) {
+        for (Contact contact : contactList) {
             for (PhoneNumber phoneNumber : contact.getNumbers()) {
                 mPhoneNumberContactMap.put(phoneNumber.getI18nPhoneNumberWrapper(), contact);
             }
         }
-        return contacts;
+        return contactList;
     }
 
     @Override
@@ -198,4 +211,13 @@
         Log.d(TAG, "Contacts loaded:" + (contacts == null ? 0 : contacts.size()));
         mIsLoaded = true;
     }
+
+    /**
+     * Formats a key to identify a contact based the lookup key and the account name.
+     */
+    private String getContactKey(String lookupKey, String accountName) {
+        String key = String.format(KEY_FORMAT, lookupKey, accountName);
+        Log.d(TAG, "Contact key is: " + key);
+        return key;
+    }
 }