chromium/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactDetails.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.browser_ui.contacts_picker;

import android.content.res.Resources;
import android.graphics.drawable.Drawable;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.chromium.blink.mojom.ContactIconBlob;
import org.chromium.payments.mojom.PaymentAddress;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/** A class to keep track of the metadata associated with a contact. */
public class ContactDetails implements Comparable<ContactDetails> {
    // The identifier for the information from the signed in user. Must not be a valid id in the
    // context of the Android Contacts list.
    public static final String SELF_CONTACT_ID = "-1";

    /**
     * A container class for delivering contact details in abbreviated form (where only the first
     * email and phone numbers are returned and the rest is indicated with "+n more" strings).
     */
    public static class AbbreviatedContactDetails {
        public String primaryEmail;
        public String overflowEmailCount;
        public String primaryTelephoneNumber;
        public String overflowTelephoneNumberCount;
        public String primaryAddress;
        public String overflowAddressCount;
    }

    // The unique id for the contact.
    private final String mId;

    // The display name for this contact.
    private final String mDisplayName;

    // The list of emails registered for this contact.
    private final List<String> mEmails;

    // The list of phone numbers registered for this contact.
    private final List<String> mPhoneNumbers;

    // The list of addresses registered for this contact.
    private final List<PaymentAddress> mAddresses;

    // The list of icons registered for this contact.
    private final List<ContactIconBlob> mIcons;

    // Keeps track of whether this is the contact detail for the owner of the device.
    private boolean mIsSelf;

    // The avatar icon for the owner of the device. Non-null only if the ContactDetails representing
    // the owner were synthesized (not when a pre-existing contact tile was moved to the top).
    @Nullable private Drawable mSelfIcon;

    /**
     * The ContactDetails constructor.
     *
     * @param id The unique identifier of this contact.
     * @param displayName The display name of this contact.
     * @param emails The emails registered for this contact.
     * @param phoneNumbers The phone numbers registered for this contact.
     * @param addresses The addresses registered for this contact.
     */
    public ContactDetails(
            String id,
            String displayName,
            List<String> emails,
            List<String> phoneNumbers,
            List<PaymentAddress> addresses) {
        mDisplayName = displayName != null ? displayName : "";
        mEmails = emails != null ? emails : new ArrayList<String>();
        mPhoneNumbers = phoneNumbers != null ? phoneNumbers : new ArrayList<String>();
        mAddresses = addresses != null ? addresses : new ArrayList<PaymentAddress>();
        mIcons = new ArrayList<>();

        mId = id;
    }

    public List<String> getDisplayNames() {
        return Arrays.asList(mDisplayName);
    }

    public List<String> getEmails() {
        return mEmails;
    }

    public List<String> getPhoneNumbers() {
        return mPhoneNumbers;
    }

    public List<PaymentAddress> getAddresses() {
        return mAddresses;
    }

    public List<ContactIconBlob> getIcons() {
        return mIcons;
    }

    public String getDisplayName() {
        return mDisplayName;
    }

    public String getId() {
        return mId;
    }

    public void setIcon(ContactIconBlob icon) {
        assert mIcons.isEmpty();
        mIcons.add(icon);
    }

    /**
     * Marks whether object is representing the owner of the device.
     *
     * @param value True if this is the contact details for the owner. False otherwise.
     */
    public void setIsSelf(boolean value) {
        mIsSelf = value;
    }

    /** Returns true if this contact detail is representing the owner of the device. */
    public boolean isSelf() {
        return mIsSelf;
    }

    /** Sets the icon representing the owner of the device. */
    public void setSelfIcon(Drawable icon) {
        mSelfIcon = icon;
    }

    /**
     * Fetch the cached icon for this contact. Returns null if this is not the 'self' contact, all
     * other contact avatars should be retrieved through the {@link FetchIconWorkerTask}.
     */
    @Nullable
    public Drawable getSelfIcon() {
        return mSelfIcon;
    }

    /**
     * Accessor for the abbreviated display name (first letter of first name and first letter of
     * last name).
     *
     * @return The display name, abbreviated to two characters.
     */
    public String getDisplayNameAbbreviation() {
        // Display the two letter abbreviation of the display name.
        String displayChars = "";
        if (mDisplayName.length() > 0) {
            displayChars += mDisplayName.charAt(0);
            String[] parts = mDisplayName.split(" ");
            if (parts.length > 1) {
                displayChars += parts[parts.length - 1].charAt(0);
            }
        }

        return displayChars;
    }

    private String ensureSingleLine(String address) {
        String returnValue = address.replaceAll("\n\n", "\n");
        // The string might have multiple consecutive new-lines, which means \n\n\n -> \n\n, so
        // we'll perform the conversion until we've caught them all.
        while (returnValue.length() < address.length()) {
            address = returnValue;
            returnValue = address.replaceAll("\n\n", "\n");
        }

        return returnValue.replaceAll("\n", ", ");
    }

    /**
     * Accessor for the list of contact details (emails and phone numbers). Returned as strings
     * separated by newline).
     *
     * @param includeAddresses Whether to include addresses in the returned results.
     * @param includeEmails Whether to include emails in the returned results.
     * @param includeTels Whether to include telephones in the returned results.
     * @return A string containing all the contact details registered for this contact.
     */
    public String getContactDetailsAsString(
            boolean includeAddresses, boolean includeEmails, boolean includeTels) {
        int count = 0;
        StringBuilder builder = new StringBuilder();
        if (includeAddresses) {
            for (PaymentAddress address : mAddresses) {
                if (count++ > 0) {
                    builder.append("\n");
                }
                builder.append(ensureSingleLine(address.addressLine[0]));
            }
        }
        if (includeEmails) {
            for (String email : mEmails) {
                if (count++ > 0) {
                    builder.append("\n");
                }
                builder.append(email);
            }
        }
        if (includeTels) {
            for (String phoneNumber : mPhoneNumbers) {
                if (count++ > 0) {
                    builder.append("\n");
                }
                builder.append(phoneNumber);
            }
        }

        return builder.toString();
    }

    /**
     * Accessor for the list of contact details (emails and phone numbers).
     *
     * @param includeAddresses Whether to include addresses in the returned results.
     * @param includeEmails Whether to include emails in the returned results.
     * @param includeTels Whether to include telephones in the returned results.
     * @param resources The resources to use for fetching the string. Must be provided.
     * @return The contact details registered for this contact.
     */
    public AbbreviatedContactDetails getAbbreviatedContactDetails(
            boolean includeAddresses,
            boolean includeEmails,
            boolean includeTels,
            @NonNull Resources resources) {
        AbbreviatedContactDetails results = new AbbreviatedContactDetails();

        results.overflowAddressCount = "";
        if (!includeAddresses || mAddresses.size() == 0) {
            results.primaryAddress = "";
        } else {
            results.primaryAddress = ensureSingleLine(mAddresses.get(0).addressLine[0]);
            int totalAddresses = mAddresses.size();
            if (totalAddresses > 1) {
                int hiddenAddresses = totalAddresses - 1;
                results.overflowAddressCount =
                        resources.getQuantityString(
                                R.plurals.contacts_picker_more_details,
                                hiddenAddresses,
                                hiddenAddresses);
            }
        }

        results.overflowEmailCount = "";
        if (!includeEmails || mEmails.size() == 0) {
            results.primaryEmail = "";
        } else {
            results.primaryEmail = mEmails.get(0);
            int totalAddresses = mEmails.size();
            if (totalAddresses > 1) {
                int hiddenAddresses = totalAddresses - 1;
                results.overflowEmailCount =
                        resources.getQuantityString(
                                R.plurals.contacts_picker_more_details,
                                hiddenAddresses,
                                hiddenAddresses);
            }
        }

        results.overflowTelephoneNumberCount = "";
        if (!includeTels || mPhoneNumbers.size() == 0) {
            results.primaryTelephoneNumber = "";
        } else {
            results.primaryTelephoneNumber = mPhoneNumbers.get(0);
            int totalNumbers = mPhoneNumbers.size();
            if (totalNumbers > 1) {
                int hiddenNumbers = totalNumbers - 1;
                results.overflowTelephoneNumberCount =
                        resources.getQuantityString(
                                R.plurals.contacts_picker_more_details,
                                hiddenNumbers,
                                hiddenNumbers);
            }
        }

        return results;
    }

    /**
     * A comparison function (results in a full name ascending sorting).
     *
     * @param other The other ContactDetails object to compare it with.
     * @return 0, 1, or -1, depending on which is bigger.
     */
    @Override
    public int compareTo(ContactDetails other) {
        return other.mDisplayName.compareTo(mDisplayName);
    }

    @Override
    public int hashCode() {
        Object[] values = {mId, mDisplayName};
        return Arrays.hashCode(values);
    }

    @Override
    public boolean equals(@Nullable Object object) {
        if (object == null) return false;
        if (object == this) return true;
        if (!(object instanceof ContactDetails)) return false;

        ContactDetails otherInfo = (ContactDetails) object;
        return mId.equals(otherInfo.mId);
    }
}