public class ContactPicker

  1. Object
  2. ContactPicker

Lets the user hand the application a small number of contacts they chose themselves, without the application gaining access to the address book.

This is the privacy-minimized counterpart to ContactsManager. Where ContactsManager enumerates the whole address book – and therefore needs the broad contacts permission – this class shows the platform’s own contact picker. The user selects, the platform copies just the fields that were asked for out of just the contacts that were selected, and the application never gets to see anything else.

Prefer it whenever the application needs “a phone number the user picked” rather than “the address book”. Google Play requires exactly that distinction: from 2027-01-27, an app targeting Android 17 (API level 37) or later that carries READ_CONTACTS without core-functionality justification has to pass a Play Console declaration, and an app that only ever calls this class never asks for that permission in the first place.

Requesting fields

The requested fields are a bit set of the constants on this class. Only those fields are populated on the returned contacts; everything else is left null or zero. Asking for less is not merely polite – on Android the request also decides which contacts the picker offers, and the platform refuses to return anything that was not asked for.

ContactPicker picker = new ContactPicker();
picker.setRequestedFields(ContactPicker.NAME | ContactPicker.PHONE);
picker.pick(new ActionListener<ActionEvent>() {
    public void actionPerformed(ActionEvent ev) {
        Contact[] picked = ContactPicker.getPickedContacts(ev);
        if(picked.length == 0) {
            // the user backed out
            return;
        }
        numberField.setText(picked[0].getPrimaryPhoneNumber());
    }
});

The result is a snapshot

The contacts handed to the callback are plain copies. The application has no continuing access to them: it cannot re-read them later, and on Android the temporary grant behind them is gone by the time the callback returns. Anything that has to outlive the callback must be copied out of the Contact and stored by the application.

One getter does not follow the rule that an unrequested field stays null, and cannot be made to. Contact#getDisplayName() never returns null: on a contact carrying no name it makes one up from the primary phone number, the primary email or the id, and caches it. That is how every Contact in the framework behaves, not only a picked one, so a picker that suppressed it would be the odd one out rather than the correct one. Ask Contact#getFirstName() and Contact#getFamilyName() when what you need to know is whether a name was actually supplied.

For the same reason Contact#getId() on a picked contact is only an opaque platform identifier useful for telling two picked contacts apart. Passing it to ContactsManager#getContactById(String) needs full address-book access, which is the thing this class exists to avoid.

What a platform can actually deliver

A picker returns what its platform is able to hand over without the broad permission, and that is not the same everywhere. Read every field you asked for defensively: a null one means the user’s contact did not carry it, or the platform could not supply it.

Android 17 and later, iOS and the simulator serve every field on this class. Android before 17 has no contact picker of its own, so the fallback is the contacts app’s own single-row picker: it returns one contact carrying one kind of data, #NAME plus whichever of #PHONE, #EMAIL and #ADDRESS was requested first. #PHOTO, #BIRTHDAY and #WEBSITE are best-effort there – they are read through the granted contact’s own data rows, which some devices allow and some refuse – and #setMultiSelect(boolean) and #setRequireAllRequestedFields(boolean) have no effect. None of that ever escalates into a permission prompt; the fields simply come back null.

Availability

#isSupported() reports whether the platform has a picker at all. Where it does not, #pick(ActionListener) reports an empty selection rather than quietly falling back to reading the address book, because that fallback would need the permission the caller was trying not to ask for.

Fields

public static final int NAME = 1Requests the contact’s name, which populates Contact#getFirstName(), Contact#getFamilyName() and Contact#getDisplayName().
public static final int PHONE = 2Requests the contact’s phone numbers, which populates Contact#getPhoneNumbers() and Contact#getPrimaryPhoneNumber().
public static final int EMAIL = 4Requests the contact’s email addresses, which populates Contact#getEmails() and Contact#getPrimaryEmail().
public static final int ADDRESS = 8Requests the contact’s postal addresses, which populates Contact#getAddresses().
public static final int PHOTO = 16Requests the contact’s photo, which populates Contact#getPhoto().
public static final int BIRTHDAY = 32Requests the contact’s birthday, which populates Contact#getBirthday().
public static final int WEBSITE = 64Requests the contact’s web sites, which populates Contact#getUrls().
public static final int ALL_FIELDS = 127Every field a picker can be asked for.
public static final int MAXIMUM_SELECTION_LIMIT = 100The largest value #setSelectionLimit(int) accepts.

Constructors

public ContactPicker()

Methods

public static boolean isSupported()Returns true when the platform provides a contact picker.
public static Contact[] getPickedContacts(ActionEvent ev)Extracts the selection from the event delivered to #pick(ActionListener).
public int getRequestedFields()The fields the picker is asked for, as a bit set of the constants on this class.
public void setRequestedFields(int requestedFields)Sets the fields the picker is asked for.
public boolean isMultiSelect()Whether the user may pick more than one contact.
public void setMultiSelect(boolean multiSelect)Sets whether the user may pick more than one contact.
public int getSelectionLimit()The largest number of contacts the user may pick.
public void setSelectionLimit(int selectionLimit)Sets the largest number of contacts the user may pick, which only has an effect together with #setMultiSelect(boolean).
public boolean isRequireAllRequestedFields()Whether a contact has to carry every requested field to be offered.
public void setRequireAllRequestedFields(boolean requireAllRequestedFields)Sets whether a contact has to carry every requested field to be offered by the picker.
public void pick(ActionListener<ActionEvent> response)Shows the platform’s contact picker and reports the selection.

Inherited methods

Field details

NAME

public static final int NAME = 1
Requests the contact’s name, which populates Contact#getFirstName(), Contact#getFamilyName() and Contact#getDisplayName().

PHONE

public static final int PHONE = 2
Requests the contact’s phone numbers, which populates Contact#getPhoneNumbers() and Contact#getPrimaryPhoneNumber().

EMAIL

public static final int EMAIL = 4
Requests the contact’s email addresses, which populates Contact#getEmails() and Contact#getPrimaryEmail().

ADDRESS

public static final int ADDRESS = 8
Requests the contact’s postal addresses, which populates Contact#getAddresses().

PHOTO

public static final int PHOTO = 16
Requests the contact’s photo, which populates Contact#getPhoto().

BIRTHDAY

public static final int BIRTHDAY = 32

Requests the contact’s birthday, which populates Contact#getBirthday().

The one field a picker cannot filter on exactly. Android groups birthdays with anniversaries and custom dates, so #setRequireAllRequestedFields(boolean) may still offer a contact that turns out to have an anniversary and no birthday; the contact comes back with a zero birthday rather than being withheld.

WEBSITE

public static final int WEBSITE = 64
Requests the contact’s web sites, which populates Contact#getUrls().

ALL_FIELDS

public static final int ALL_FIELDS = 127
Every field a picker can be asked for. Convenient for a one-off “import this person” flow, and the wrong choice for anything else: asking for a field the application will not read hands it data it did not need, which is what the picker exists to prevent.

MAXIMUM_SELECTION_LIMIT

public static final int MAXIMUM_SELECTION_LIMIT = 100
The largest value #setSelectionLimit(int) accepts. Android rejects a larger request outright.

Constructor details

ContactPicker

public ContactPicker()

Method details

isSupported

public static boolean isSupported()

Returns true when the platform provides a contact picker.

It answers for the platform rather than for the device. Android says yes wherever the application is running normally, because deciding otherwise would mean asking the package manager what handles the picker intent, and from Android 11 that question is filtered by package visibility – it would report no picker on ordinary devices where the picker works. A device that really has no contacts application reports an empty selection from #pick(ActionListener), which is what a cancelled pick reports, so a listener that checks the selection handles it already.

Returns

true if the platform has a picker, false if #pick(ActionListener) will report an empty selection without showing anything

getPickedContacts

public static Contact[] getPickedContacts(ActionEvent ev)
Extracts the selection from the event delivered to #pick(ActionListener).

Parameters

ev ActionEvent
the event handed to the listener, which may be null

Returns

the contacts the user picked, in the order the platform reported them, or a zero length array when the user cancelled or the platform has no picker. Never null.

getRequestedFields

public int getRequestedFields()
The fields the picker is asked for, as a bit set of the constants on this class.

Returns

the requested fields, NAME | PHONE unless it was changed

setRequestedFields

public void setRequestedFields(int requestedFields)
Sets the fields the picker is asked for.

Parameters

requestedFields int
a bit set of the constants on this class, which must name at least one field

isMultiSelect

public boolean isMultiSelect()
Whether the user may pick more than one contact.

Returns

true if the picker allows a multiple selection, false by default

setMultiSelect

public void setMultiSelect(boolean multiSelect)

Sets whether the user may pick more than one contact.

A platform whose picker is single-select ignores this and returns at most one contact, so the callback must cope with a shorter selection than it allowed for. Android before version 17 is such a platform.

Parameters

multiSelect boolean
true to allow a multiple selection

getSelectionLimit

public int getSelectionLimit()
The largest number of contacts the user may pick.

Returns

the selection limit, MAXIMUM_SELECTION_LIMIT unless it was changed

setSelectionLimit

public void setSelectionLimit(int selectionLimit)

Sets the largest number of contacts the user may pick, which only has an effect together with #setMultiSelect(boolean).

The selection handed to the listener never exceeds it. Whether the user is stopped at the cap or merely has the surplus dropped depends on the platform: Android and the simulator stop accepting the tick that would exceed it, and so does iOS for a limit of one, which it serves with its single-select picker. iOS cannot cap a larger multiple selection – its picker has no such setting – so a user who confirms more than the cap has the extras dropped, keeping the ones they chose first.

Parameters

selectionLimit int
a count between 1 and MAXIMUM_SELECTION_LIMIT inclusive

isRequireAllRequestedFields

public boolean isRequireAllRequestedFields()
Whether a contact has to carry every requested field to be offered.

Returns

true to offer only contacts holding all of the requested fields, false by default, which offers a contact holding any of them

setRequireAllRequestedFields

public void setRequireAllRequestedFields(boolean requireAllRequestedFields)

Sets whether a contact has to carry every requested field to be offered by the picker.

Use it when a partial contact is useless to the application, for instance an invitation flow that needs both a name and an email address. Leave it off when any one of the requested fields will do.

A platform applies it as far as its own picker can. Android 17 and later enforce it exactly; iOS enforces it over phone numbers, email addresses and postal addresses and cannot filter on the rest; Android before 17 has no picker predicate at all and ignores it. So the listener still has to cope with a contact that turned out to be missing one.

Parameters

requireAllRequestedFields boolean
true to require every requested field

pick

public void pick(ActionListener<ActionEvent> response)

Shows the platform’s contact picker and reports the selection.

The call returns at once; the picker runs on top of the application and the listener is invoked on the EDT when the user is done. A cancelled pick and a platform with no picker both report an empty selection, so #getPickedContacts(ActionEvent) is the only thing the listener has to check.

Parameters

response ActionListener<ActionEvent>
invoked with the selection once the user is done