Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to query all contacts that belong to an account?

Background

Contacts on the address book can have an account data that's attached to them. Each app can have an account, and then add its own information for the contact.

Apps such as Telegram, WhatsApp, Viber,... - all create an account that adds information and/or actions to contacts.

Here's an example of a contact that has both WhatsApp and Viber accounts for it:

enter image description here

The problem

I'm trying to figure out how to fetch all contacts that have a specified account.

Since WhatsApp is the most popular that I know of, my tests focus on it.

My problem is that some users claim what I did barely returns contacts, and some claim it doesn't show even a single one. It seems to usually work, and in my case it always worked, but something is probably not good on the code.

What I've tried

I got to make the next code, which to me seems to work, getting a map of phone-to-contact-info, of all WhatsApp contacts.

The idea is to get all possible information of WhatsApp contacts, vs all basic contacts data, and merge those that match the same lookup-key.

I tried to use a better query of joining, but I failed. Maybe it is possible too, and might be more efficient.

Here's the code:

/**
 * returns a map of lookup-key to contact-info, of all WhatsApp contacts
 */
@NonNull
public HashMap<String, ContactInfo> getAllWhatsAppPhones(Context context) {
    ContentResolver cr = context.getContentResolver();
    final HashMap<String, ContactInfo> phoneToContactInfoMap = new HashMap<>();
    final HashMap<String, String> whatsAppLookupKeyToPhoneMap = new HashMap<>();
    final String phoneMimeType = Phone.CONTENT_ITEM_TYPE;
    final Cursor whatsAppCursor;
    whatsAppCursor = cr.query(Data.CONTENT_URI,
            new String[]{Phone.NUMBER, Phone.LOOKUP_KEY},
            Phone.MIMETYPE + " = ?", new String[]{WhatsAppStuff.WHATS_APP_MIME_TYPE}, null);
    if (whatsAppCursor == null)
        return phoneToContactInfoMap;
    Cursor contactCursor = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            //null,
            new String[]{
                    Contacts.LOOKUP_KEY, Contacts._ID, Contacts.PHOTO_THUMBNAIL_URI,
                    ContactsContract.Contacts.DISPLAY_NAME,                        //        ContactsContract.CommonDataKinds.Phone.NUMBER,
            },
            "(" + Phone.MIMETYPE + " IS NULL OR " + Phone.MIMETYPE + " = '" + phoneMimeType + "') AND ("
                    + ContactsContract.RawContacts.ACCOUNT_TYPE + " = 'com.google' OR " + ContactsContract.RawContacts.ACCOUNT_TYPE + " IS NULL)",
            null, null);
    if (contactCursor == null) {
        whatsAppCursor.close();
        return phoneToContactInfoMap;
    }
    int progress = 0;
    final int phoneNumberIdx = whatsAppCursor.getColumnIndex(Phone.NUMBER);
    final int lookupKeyIdx = whatsAppCursor.getColumnIndex(Phone.LOOKUP_KEY);
    while (whatsAppCursor.moveToNext()) {
        final String phoneNumberValue = whatsAppCursor.getString(phoneNumberIdx);
        final int endIndex = phoneNumberValue.indexOf("@");
        if (endIndex < 0)
            continue;
        String lookupKey = whatsAppCursor.getString(lookupKeyIdx);
        final String phone = phoneNumberValue.substring(0, endIndex);
        if (!phone.isEmpty() && StringUtil.isAllDigits(phone)) {
            //Log.d("AppLog", "whatsApp phone:" + phone + " " + lookupKey);
            whatsAppLookupKeyToPhoneMap.put(lookupKey, phone);
        }
        if (markedToCancel != null && markedToCancel.get()) {
            whatsAppCursor.close();
            contactCursor.close();
            return phoneToContactInfoMap;
        }
        if (progressListener != null)
            progressListener.onProgressUpdate(progress++, maxProgress);
    }
    whatsAppCursor.close();
    if (whatsAppLookupKeyToPhoneMap.isEmpty())
        return phoneToContactInfoMap;
    //Log.d("AppLog", "getting info about whatsapp contacts");
    final int idColIdx = contactCursor.getColumnIndex(Contacts._ID);
    final int displayNameColIdx = contactCursor.getColumnIndex(Contacts.DISPLAY_NAME);
    final int lookupKeyColIdx = contactCursor.getColumnIndex(Contacts.LOOKUP_KEY);
    final int photoColIdx = contactCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI);

    while (contactCursor.moveToNext()) {
        String lookupKey = contactCursor.getString(lookupKeyColIdx);
        String phoneNumber = whatsAppLookupKeyToPhoneMap.get(lookupKey);
        if (phoneNumber == null)
            continue;
        ContactInfo contactInfo = new ContactInfo();
        contactInfo.lookupKey = lookupKey;
        contactInfo.displayName = contactCursor.getString(displayNameColIdx);
        contactInfo.photoThumbUriStr = contactCursor.getString(photoColIdx);
        contactInfo.whatsAppPhoneNumber = phoneNumber;
        contactInfo.contactId = contactCursor.getLong(idColIdx);
        phoneToContactInfoMap.put(phoneNumber, contactInfo);
        if (markedToCancel != null && markedToCancel.get()) {
            contactCursor.close();
            return phoneToContactInfoMap;
        }
        if (progressListener != null)
            progressListener.onProgressUpdate(progress++, maxProgress);
    }
    contactCursor.close();
    return phoneToContactInfoMap;
}

The question

As I wrote, the above code only usually works.

How come it only usually works? What's missing to fix it?

Should I use Contacts.getLookupUri instead of lookup key? If so, how should I change the code above to use it instead?

I tried to use a URI instead of a lookup-key, but then it didn't find any of them inside the normal contacts.

like image 474
android developer Avatar asked Oct 18 '22 07:10

android developer


1 Answers

The main issue I see that can explain why users won't see results from your code, is that you're assuming all the contacts are stored on a Google account.

While this is the default behavior in some devices, it's not the default on all devices, also, users can freely change their contacts storage to any other location (yahoo contacts, MS exchange, phone-only (unsynced), etc.)

Having that said, if your only requirement is to

fetch all contacts that have a specified account.

I think that's a much better alternative then your 2 queries (one of which runs over all contacts, not just the required ones):

// Uri to query contacts that have a RawContact in the desired account
final Uri.Builder builder = Contacts.CONTENT_URI.buildUpon();
builder.appendQueryParameter(RawContacts.ACCOUNT_NAME, whatsappAccountName);
builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, whatsappAccountType);
Uri uri = builder.build();

String[] projection = new String[]{ Contacts.LOOKUP_KEY, Contacts._ID Contacts.DISPLAY_NAME }; // add more if needed

// boo-yaa!
Cursor cur = cr.query(uri, projection, null, null, null);
like image 66
marmor Avatar answered Nov 03 '22 00:11

marmor