1

The situation: I've added a custom action to a contact in Android following the instructions in this question and the related sample app on GitHub. When pressed I want to dial that contact in my application. I am able to successfully retrieve the Contact in the Activity that is opened when the custom action is pressed.

I execute this:

Cursor cursor = context.getContentResolver().query(data, null, null, null, null);
if (cursor != null) {
    newIntent = true;
    contact = LocalContactAsync.getContacts(cursor, context.getContentResolver()).get(0);
    cursor.close();
} 

And the data I retrieve from Android is:

content://com.android.contacts/data/2849

Notice the number 2849 at the end, this is not the native ID of the contact. The native ID of the contact is 459. I am able to successfully retrieve the contact executing this query, the following data returns:

cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID); 

-returns '2849'

cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)) ; 

-returns 'sample samplee' wich is correct

But although this is true:

cursor.getInt(cur.getColumnIndex(
            ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0)

The following function returns an empty cursor:

Cursor pCur = cr.query(
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                null,
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
                new String[]{id}, null); 

-id = 2849 in this case but if I fill in 459 I retrieve the right amount of telephone numbers

The real contact has 3 numbers so it should return 3 numbers.

How am I able to fix this?

Edited:

This is how I retrieve numbers, to be clear: I get the correct name, but the following query returns null while the contact has numbers.

    ArrayList<Number> numbers = new ArrayList<>();
    if (cur.getInt(cur.getColumnIndex(
            ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
        Cursor pCur = cr.query(
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                null,
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
                new String[]{id}, null);
        while (pCur.moveToNext()) {
            numbers.add(new nl.coffeeit.clearvoxnexxt.objects.dto.Number(pCur.getString(pCur.getColumnIndex(
                    ContactsContract.CommonDataKinds.Phone.NUMBER))));
        }
        pCur.close();
    }
    return numbers;

Please note that I do not request an intent, I receive it through a custom action that is added to a native contact, like Viber and WhatsApp do:

custom action android contact

Full code LocalContacts Async:

private static final String TAG = "LocalContactAsync";
private static List<Contact> contacts;

private Context context;
private boolean refreshOtherFragments;
private boolean renew;    

private synchronized List<Contact> getContacts(Context context) {
    if (!renew && contacts != null) {
        return contacts;
    }
    ContentResolver cr = context.getContentResolver();
    Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI,
            null, null, null, null);

    if (cur != null && cur.getCount() > 0) {
        contacts = getContacts(cur, cr);
        cur.close();
        return contacts;
    }
    if (cur != null) {
        cur.close();
    }
    return new ArrayList<>();
}

public static List<Contact> getContacts(Cursor cur, ContentResolver cr) {
    List<Contact> contacts = new ArrayList<>();
    while (cur.moveToNext()) {
        String id =  getId(cur);
        String name = getName(cur);
        ArrayList<Number> numbers = getNumbers(cur, cr, id);
        if (name != null) {
            contacts.add(new Contact(id, name, numbers));
        }
    }
    Log.d(TAG, "amount of contacts" + contacts.size());
    return contacts;
}

private static ArrayList<Number> getNumbers(Cursor cur, ContentResolver cr, String id) {
    ArrayList<Number> numbers = new ArrayList<>();
    if (cur.getInt(cur.getColumnIndex(
            ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
        Cursor pCur = cr.query(
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                null,
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
                new String[]{id}, null);
        while (pCur.moveToNext()) {
            numbers.add(getNumber(pCur));
        }
        pCur.close();
    }
    return numbers;
}

private static Number getNumber(Cursor pCur) {
    return new nl.coffeeit.clearvoxnexxt.objects.dto.Number(pCur.getString(pCur.getColumnIndex(
            ContactsContract.CommonDataKinds.Phone.NUMBER)));
}

private static String getId(Cursor cur) {
    return cur.getString(
            cur.getColumnIndex(ContactsContract.Contacts._ID));
}

private static String getName(Cursor cur) {
    return cur.getString(cur.getColumnIndex(
            ContactsContract.Contacts.DISPLAY_NAME));
}

Code for Number DTO:

public class Number implements Parcelable, Serializable {

    @SerializedName("number")
    @Expose
    public String number;
    @SerializedName("type")
    @Expose
    public String type = "";
    @SerializedName("inherited")
    @Expose
    public Boolean inherited = false;

    public Number(String number) {
        this.number = number;
    }

    protected Number(Parcel in) {
        number = in.readString();
        type = in.readString();
        byte inheritedVal = in.readByte();
        inherited = inheritedVal == 0x02 ? null : inheritedVal != 0x00;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(number);
        dest.writeString(type);
        if (inherited == null) {
            dest.writeByte((byte) (0x02));
        } else {
            dest.writeByte((byte) (inherited ? 0x01 : 0x00));
        }
    }

    @SuppressWarnings("unused")
    public static final Parcelable.Creator<Number> CREATOR = new Parcelable.Creator<Number>() {
        @Override
        public Number createFromParcel(Parcel in) {
            return new Number(in);
        }

        @Override
        public Number[] newArray(int size) {
            return new Number[size];
        }
    };

    public Number setNumber(String number) {
        this.number = number;
        return this;
    }
}
David Rawson
  • 20,912
  • 7
  • 88
  • 124
jobbert
  • 3,297
  • 27
  • 43

1 Answers1

1

The first thing to notice is that a call to the contacts picker like this:

Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);

will return a Uri like this:

content://com.android.contacts/contacts/lookup/3163r328-4D2941473F314D2941473F3131/328

The second to last path (3163r....) is the lookup key, while 328 is the NAME_RAW_ID.

Compare this with the Intent you get from the sample application. This contains an Uri that looks like this:

content://com.android.contacts/data/2849

As you have said, calling the content resolver with this Uri is not sufficient to retrieve phone numbers, although it may be used to retrieve the name of the contact and the id. So we will use the incomplete Intent Uri to construct a new Lookup Uri that we can use to get the phone numbers.

Let's add the following methods to your LocalContactAsync (I won't refactor anything you have done so far, I'll just add in the style you have used):

public static Uri getLookupUri(Cursor cur) {
    return getContentUri(getLookupKey(cur), getNameRawId(cur));
}

private static String getLookupKey(Cursor cur)  {
    return cur.getString(
            cur.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
}

private static String getNameRawId(Cursor cur) {
    return cur.getString(cur.getColumnIndex(ContactsContract.Contacts.NAME_RAW_CONTACT_ID));
}

private static Uri getContentUri(String lookupKey, String nameRawId) {
    return new Uri.Builder()
            .scheme("content")
            .authority("com.android.contacts")
            .appendPath("contacts")
            .appendPath("lookup")
            .appendPath(lookupKey)
            .appendPath(nameRawId)
            .build();
}

Let's alter the ViewingActivity inside the sample application so that it actually retrieves the contact details. We can now do that with the following code inside onResume():

@Override
protected void onResume() {
    super.onResume();
    Uri uri = getIntent().getData();
    Cursor intentCursor = this.getContentResolver().query(uri, null, null, null, null);
    Contact contact = null;
    if (intentCursor != null) {
        intentCursor.moveToFirst();
        Uri lookupUri = LocalContactAsync.getLookupUri(intentCursor);
        Cursor lookupCursor = this.getContentResolver().query(lookupUri, null, null, null, null);
        contact = LocalContactAsync.getContacts(lookupCursor, this.getContentResolver()).get(0);
        intentCursor.close();
        lookupCursor.close();
    }
}

The contact will now contain the phone numbers as required.

David Rawson
  • 20,912
  • 7
  • 88
  • 124
  • Thanks for the info, but I do not start an activity for a result. I add a custom event to the native contact which opens my app directly from the contact application of Android. How do I manage those extras? – jobbert Dec 25 '16 at 21:25
  • I've added extra information to the question. The code you provided worked the same as mine. I get the name and the ID but no phone numbers. While the Contact has 3 telephone numbers. Also if I just retrieve all the contacts I am able to get the telephone numbers. – jobbert Dec 28 '16 at 08:39
  • Then the number 2849 returns. It's bad naming. It is not async at all. I can post full code if you want. – jobbert Dec 28 '16 at 09:03
  • No they are regular home, work and mobile numbers. To be sure, I've added 2 of them in the contacts application. – jobbert Dec 28 '16 at 09:06
  • Changed, but didn't change the result. – jobbert Dec 28 '16 at 09:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/131675/discussion-between-david-rawson-and-jobbert). – David Rawson Dec 28 '16 at 09:34
  • This doesn't work, the cursor remains null, and also the ID is not the contact ID notice that I also use this code to get all contacts and that works. Only with retrieving the contact from a custom action field this code doesn't work. – jobbert Dec 28 '16 at 09:34
  • To test, execute the other method: getContacts(Context context); – jobbert Dec 28 '16 at 09:35