public class ContactPicker
- Object
- 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 = 1 | Requests the contact’s name, which populates Contact#getFirstName(), Contact#getFamilyName() and Contact#getDisplayName(). |
public static final int PHONE = 2 | Requests the contact’s phone numbers, which populates Contact#getPhoneNumbers() and Contact#getPrimaryPhoneNumber(). |
public static final int EMAIL = 4 | Requests the contact’s email addresses, which populates Contact#getEmails() and Contact#getPrimaryEmail(). |
public static final int ADDRESS = 8 | Requests the contact’s postal addresses, which populates Contact#getAddresses(). |
public static final int PHOTO = 16 | Requests the contact’s photo, which populates Contact#getPhoto(). |
public static final int BIRTHDAY = 32 | Requests the contact’s birthday, which populates Contact#getBirthday(). |
public static final int WEBSITE = 64 | Requests the contact’s web sites, which populates Contact#getUrls(). |
public static final int ALL_FIELDS = 127 | Every field a picker can be asked for. |
public static final int MAXIMUM_SELECTION_LIMIT = 100 | The 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 = 1Contact#getFirstName(),
Contact#getFamilyName() and Contact#getDisplayName().PHONE
public static final int PHONE = 2Contact#getPhoneNumbers() and Contact#getPrimaryPhoneNumber().public static final int EMAIL = 4Contact#getEmails() and Contact#getPrimaryEmail().ADDRESS
public static final int ADDRESS = 8Contact#getAddresses().PHOTO
public static final int PHOTO = 16Contact#getPhoto().BIRTHDAY
public static final int BIRTHDAY = 32Requests 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 = 64Contact#getUrls().ALL_FIELDS
public static final int ALL_FIELDS = 127MAXIMUM_SELECTION_LIMIT
public static final int MAXIMUM_SELECTION_LIMIT = 100#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
#pick(ActionListener)
will report an empty selection without showing anythinggetPickedContacts
public static Contact[] getPickedContacts(ActionEvent ev)#pick(ActionListener).Parameters
evActionEvent- the event handed to the listener, which may be null
Returns
getRequestedFields
public int getRequestedFields()Returns
NAME | PHONE unless it was changedsetRequestedFields
public void setRequestedFields(int requestedFields)Parameters
requestedFieldsint- a bit set of the constants on this class, which must name at least one field
isMultiSelect
public boolean isMultiSelect()Returns
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
multiSelectboolean- true to allow a multiple selection
getSelectionLimit
public int getSelectionLimit()Returns
MAXIMUM_SELECTION_LIMIT unless it was changedsetSelectionLimit
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
selectionLimitint- a count between 1 and
MAXIMUM_SELECTION_LIMITinclusive
isRequireAllRequestedFields
public boolean isRequireAllRequestedFields()Returns
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
requireAllRequestedFieldsboolean- 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
responseActionListener<ActionEvent>- invoked with the selection once the user is done