Class 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.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intRequests the contact's postal addresses, which populatesContact#getAddresses().static final intEvery field a picker can be asked for.static final intRequests the contact's birthday, which populatesContact#getBirthday().static final intRequests the contact's email addresses, which populatesContact#getEmails()andContact#getPrimaryEmail().static final intThe largest value#setSelectionLimit(int)accepts.static final intRequests the contact's name, which populatesContact#getFirstName(),Contact#getFamilyName()andContact#getDisplayName().static final intRequests the contact's phone numbers, which populatesContact#getPhoneNumbers()andContact#getPrimaryPhoneNumber().static final intRequests the contact's photo, which populatesContact#getPhoto().static final intRequests the contact's web sites, which populatesContact#getUrls(). -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic Contact[]Extracts the selection from the event delivered to#pick(ActionListener).intThe fields the picker is asked for, as a bit set of the constants on this class.intThe largest number of contacts the user may pick.booleanWhether the user may pick more than one contact.booleanWhether a contact has to carry every requested field to be offered.static booleanReturns true when the platform provides a contact picker.voidpick(ActionListener<ActionEvent> response) Shows the platform's contact picker and reports the selection.voidsetMultiSelect(boolean multiSelect) Sets whether the user may pick more than one contact.voidsetRequestedFields(int requestedFields) Sets the fields the picker is asked for.voidsetRequireAllRequestedFields(boolean requireAllRequestedFields) Sets whether a contact has to carry every requested field to be offered by the picker.voidsetSelectionLimit(int selectionLimit) Sets the largest number of contacts the user may pick, which only has an effect together with#setMultiSelect(boolean).
-
Field Details
-
NAME
public static final int NAMERequests the contact's name, which populatesContact#getFirstName(),Contact#getFamilyName()andContact#getDisplayName().- See Also:
-
PHONE
public static final int PHONERequests the contact's phone numbers, which populatesContact#getPhoneNumbers()andContact#getPrimaryPhoneNumber().- See Also:
-
EMAIL
public static final int EMAILRequests the contact's email addresses, which populatesContact#getEmails()andContact#getPrimaryEmail().- See Also:
-
ADDRESS
public static final int ADDRESSRequests the contact's postal addresses, which populatesContact#getAddresses().- See Also:
-
PHOTO
public static final int PHOTORequests the contact's photo, which populatesContact#getPhoto().- See Also:
-
BIRTHDAY
public static final int BIRTHDAYRequests 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.- See Also:
-
WEBSITE
public static final int WEBSITERequests the contact's web sites, which populatesContact#getUrls().- See Also:
-
ALL_FIELDS
public static final int ALL_FIELDSEvery 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.- See Also:
-
MAXIMUM_SELECTION_LIMIT
public static final int MAXIMUM_SELECTION_LIMITThe largest value#setSelectionLimit(int)accepts. Android rejects a larger request outright.- See Also:
-
-
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
Extracts the selection from the event delivered to
#pick(ActionListener).Parameters
ev: 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 | PHONEunless it was changed -
setRequestedFields
public void setRequestedFields(int requestedFields) Sets the fields the picker is asked for.
Parameters
requestedFields: 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: 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_LIMITunless 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: a count between 1 andMAXIMUM_SELECTION_LIMITinclusive
-
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: true to require every requested field
-
pick
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: invoked with the selection once the user is done
-