IncomingCallRetrieverClient

@DoNotMock(value = "Use canonical fakes instead.")
public interface IncomingCallRetrieverClient extends HasApiKey


The interface for interacting with the Incoming Call Retriever API.

Note: To get access to the Incoming Retriever API, please fill this form.

The privacy preserving Incoming Call Retriever API is designed to streamline phone number verification for your Android apps. This API leverages the ubiquity of phone calls to provide a seamless and privacy preserving verification solution. The API allows apps to verify phone numbers by intercepting an incoming call matching a specific phone number range without the need for broad READ_CALL_LOG permissions.

Alternative Verification Methods

In order to allow your app to securely verify a user’s phone number, the recommended solution is the Digital Credentials API. Accessing this API directly, or using one of a wide range of compatible aggregators, allows you to reliably verify user account information without requiring sensitive app permissions. For a Firebase solution, see Firebase Phone Number Verification.

Alternatively, you can verify a user’s phone number using a one-time passcode, accessed via the SMS Retriever API.

Incoming Call Retriever API Flow

  1. Call IncomingCallRetrieverClient.startIncomingCallRetriever to begin call interception (requests consent if not granted), or IncomingCallRetrieverClient.startUserConsent to request consent only. Both return a Task of PendingIntent.

  2. Attach OnSuccessListener and OnFailureListener to the returned Task.

  3. On Success: Launch the PendingIntent using startIntentSenderForResult from your Activity to display the user consent screen.

  4. On Failure: The OnFailureListener will provide an Exception. While ApiException is common, note that other exception types may be provided as well. For ApiException, handle status codes like IncomingCallRetrieverStatusCodes.API_UNAVAILABLE, which can occur if the API is not available on the device, the client is unauthorized, or the device doesn't have SIM card support.

  5. Handling Result: The result of the user's interaction with the consent dialog (grant, denial, cancellation, or rate limited) is delivered to your Activity's onActivityResult method. Check the resultCode and the status code from the intent extra IncomingCallRetriever.ACTIVITY_RESULT_INTENT_EXTRA_STATUS_CODE.

  6. Broadcast Receiver: Implement a BroadcastReceiver filtering on IncomingCallRetriever.PHONE_VERIFICATION_STATUS_INTENT_ACTION and secured with the com.google.android.gms.auth.api.phone.permission.SEND permission to receive the verification status.

private static final int USER_CONSENT_REQUEST_CODE = 1001;

IncomingCallRetriever.getClient(this).startUserConsent()
.addOnSuccessListener(new OnSuccessListener<PendingIntent>() {
@Override
public void onSuccess(PendingIntent pendingIntent) {
try {
startIntentSenderForResult(
pendingIntent.getIntentSender(), USER_CONSENT_REQUEST_CODE, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
if (e instanceof ApiException
&& ((ApiException) e).getStatusCode()
== IncomingCallRetrieverStatusCodes.API_UNAVAILABLE) {
// API unavailable or unauthorized
}
}
});

Calling startIncomingCallRetriever API

IncomingCallRetrieverRequest request =
new IncomingCallRetrieverRequest("1", "6576", "000000", "999999");
private static final int VERIFICATION_REQUEST_CODE = 1002;

IncomingCallRetriever.getClient(this).startIncomingCallRetriever(request)
.addOnSuccessListener(new OnSuccessListener<PendingIntent>() {
@Override
public void onSuccess(PendingIntent pendingIntent) {
try {
startIntentSenderForResult(
pendingIntent.getIntentSender(), VERIFICATION_REQUEST_CODE, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
if (e instanceof ApiException
&& ((ApiException) e).getStatusCode()
== IncomingCallRetrieverStatusCodes.API_UNAVAILABLE) {
// API unavailable or unauthorized
}
}
});

Handling Result in onActivityResult

@Override
protected
void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != USER_CONSENT_REQUEST_CODE) return;

if (resultCode == Activity.RESULT_OK) {
int statusCode =
data != null
? data.getIntExtra(IncomingCallRetriever.ACTIVITY_RESULT_INTENT_EXTRA_STATUS_CODE, -1)
: -1;
if (statusCode == CommonStatusCodes.SUCCESS
|| statusCode == CommonStatusCodes.SUCCESS_CACHE) {
// User consent granted or skipped due to recent consent. Proceed with verification.
}
} else if (resultCode == Activity.RESULT_CANCELED) {
// Handle denial, rate-limiting, or user dismissal. Optionally check
// data.getIntExtra(IncomingCallRetriever.ACTIVITY_RESULT_INTENT_EXTRA_STATUS_CODE, -1)
// for more details.
}
}

Broadcast Receiver Configuration

  • To receive the phone number verification status after the user consents and the call is placed:

  • Implement a BroadcastReceiver filtering on the IncomingCallRetriever.PHONE_VERIFICATION_STATUS_INTENT_ACTION intent.

  • You must secure your receiver by adding the com.google.android.gms.auth.api.phone.permission.SEND permission. This ensures the broadcast is coming from Google Play services. This can be done in the manifest using android:permission.

  • Caution: Do NOT add this permission to your application's AndroidManifest.xml via <uses-permission>.

  • The receiver will handle outcomes like success (with the phone number) or failure (for example, timeout).

private final BroadcastReceiver incomingCallRetrieverReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!IncomingCallRetriever.PHONE_VERIFICATION_STATUS_INTENT_ACTION.equals(
intent.getAction())) {
return;
}

IncomingCallRetrieverResponse response =
intent.getParcelableExtra(IncomingCallRetriever.PHONE_VERIFICATION_STATUS_INTENT_EXTRA);
if (response == null) return;

if (CommonStatusCodes.getStatusCodeString(CommonStatusCodes.SUCCESS)
.equals(response.getIncomingCallVerificationStatusCode())) {
String phoneNumber = response.getPhoneNumber();
// Success: use phoneNumber for further processing
} else {
// Failure: handle error details or timeout
String status = response.getIncomingCallVerificationStatusCode();
}
}
}
;

Manifest Declaration:

Register receiver in your app's AndroidManifest.xml as

<receiver
android:name=".IncomingCallRetrieverBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.gms.auth.api.phone.permission.SEND">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.PHONE_VERIFICATION_STATUS_INTENT_ACTION" />
</intent-filter>
</receiver>

Best Practices

  1. Verify Telephony Service Availability: Before using this client, verify that the device supports telephony services by checking that context.getSystemService(Context.TELEPHONY_SERVICE) is not null.

  2. Safely Handle Request Codes in onActivityResult: In android.app.Activity.onActivityResult, always check that requestCode matches the one used with startIntentSenderForResult to start the consent activity before processing the result. Ignore other request codes.

  3. Handle Dialog Cancellations in onActivityResult: Treat a resultCode of Activity.RESULT_CANCELED in onActivityResult as a standard User Consent Denied signal, regardless of whether the intent data is null. This indicates the user dismissed or back pressed the consent dialog.

  4. Use Statically Registered Receivers: For maximum reliability, register your BroadcastReceiver statically in the AndroidManifest.xml. This ensures the phone verification status is received even if your app is not currently running.

Summary

Public methods

abstract @NonNull Task<@NonNull PendingIntent>
@RequiresApi(value = 29)
startIncomingCallRetriever(
    @NonNull IncomingCallRetrieverRequest incomingCallRetrieverRequest
)

Starts the incoming call retriever API, which rejects the first incoming call within the phone range provided in the IncomingCallRetrieverRequest within a timeout.

abstract @NonNull Task<@NonNull PendingIntent>

Initiates only the user consent flow for the Incoming Call Retriever feature.

Public methods

startIncomingCallRetriever

@RequiresApi(value = 29)
abstract @NonNull Task<@NonNull PendingIntentstartIncomingCallRetriever(
    @NonNull IncomingCallRetrieverRequest incomingCallRetrieverRequest
)

Starts the incoming call retriever API, which rejects the first incoming call within the phone range provided in the IncomingCallRetrieverRequest within a timeout.

This method first seeks user consent (if not recently granted) and then prepares to intercept an incoming call matching the provided request.

Upon receiving consent (or cache success in onActivityResult), your backend must initiate the call within the certain time limit from a number conforming to the IncomingCallRetrieverRequest ranges.

Incoming Call Retriever will reject the call and broadcast the result via the receiver configured.

Parameters
@NonNull IncomingCallRetrieverRequest incomingCallRetrieverRequest

calling client needs to pass a corresponding IncomingCallRetrieverRequest instance. This contains a phone number range from which the incoming call is expected for verification.

Returns
@NonNull Task<@NonNull PendingIntent>

a Task which resolves with a PendingIntent to launch the user consent screen.

startUserConsent

@RequiresApi(value = 29)
abstract @NonNull Task<@NonNull PendingIntentstartUserConsent()

Initiates only the user consent flow for the Incoming Call Retriever feature.

Use this method to initiate the user consent before you intend to start the actual call interception.

After obtaining consent, you would typically call startIncomingCallRetriever to begin the call verification process.

Returns
@NonNull Task<@NonNull PendingIntent>

a Task which resolves with a PendingIntent to launch the user consent screen.