Using Android SMS User Consent API

The SMS User Consent API complements the SMS Retriever API by allowing an app to prompt the user to grant access to the content of a single SMS message. The API comes handy in situations where you don’t control the format of the SMS message and cannot support the SMS Retriever API.

This post describes how to use the SMS User Consent API to request user consent to read a single SMS verification message and fill it in automatically. If the user consents, the API returns the text of the message, from which you can get the verification code and complete the verification process.

1. Install dependencies

To start us off, include the Play Services auth component in your app’s build.gradle file:

implementation 'com.google.android.gms:play-services-auth:17.0.0'
implementation 'com.google.android.gms:play-services-auth-api-phone:17.1.0'

2. Start listening for incoming messages

Next, call the SMS User Consent API’s startSmsUserConsent() method to start listening for incoming messages. If you know the phone number from which the SMS message will originate, specify it (otherwise, pass null). This way, the SMS User Consent API will only trigger on messages from this number.

//Start listening for SMS User Consent broadcasts from senderPhoneNumber
Task<Void> task = SmsRetriever.getClient(context).startSmsUserConsent(senderPhoneNumber);

Once you’re listening for incoming SMS messages, you can have your verification system send the verification code to the user’s phone number. For testing purposes you can use your emulator to send the verification SMS.

For the next five minutes, when the device receives an SMS message that contains a one-time code, Play services will broadcast to your app an intent to prompt the user for permission to read the message. A message triggers the broadcast only if it meets these criteria:

  • The message contains a 4-10 character alphanumeric string with at least one number.
  • The message was sent by a phone number that’s not in the user’s contacts.
  • If you specified the sender’s phone number, the message was sent by that number.

As you might have noticed, unlike the SMS Retriever API no hash enclosed in angled brackets and/or an application hash is needed within the SMS message to make it valid.

Handle these broadcasts with a broadcast receiver that responds to SMS_RETRIEVED_ACTION intents. To create and register the broadcast receiver:

private static final int SMS_CONSENT_REQUEST = 2;

// Set to an unused request code
private final BroadcastReceiver smsVerificationReceiver = new BroadcastReceiver() {
 @Override
 public void onReceive(Context context, Intent intent) {
  if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
   Bundle extras = intent.getExtras();
   Status smsRetrieverStatus = (Status)extras.get(SmsRetriever.EXTRA_STATUS);
   
   switch (smsRetrieverStatus.getStatusCode()) {
    case CommonStatusCodes.SUCCESS:
     // Get consent intent
     Intent consentIntent = extras.getParcelable(SmsRetriever.EXTRA_CONSENT_INTENT);
     try {
      /*Start activity to show consent dialog to user within
       *5 minutes, otherwise you'll receive another TIMEOUT intent
       */
      startActivityForResult(consentIntent, SMS_CONSENT_REQUEST);
     } 
     catch (ActivityNotFoundException e) {
         // Handle the exception
     }
     break;
    case CommonStatusCodes.TIMEOUT:
     // Time out occurred, handle the error.
     break;
   }
  }
 }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 IntentFilter intentFilter = new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION); 
 registerReceiver(smsVerificationReceiver, intentFilter);
}

By starting an activity for EXTRA_CONSENT_INTENT, you prompt the user for one-time permission to read the contents of the message.

3. Get the verification code from a message

In the onActivityResult() method, handle the user’s response to your request for permission. If you get a result code of RESULT_OK, the user granted permission to read the contents of the message, and you can get the message text from the intent.

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
        case SMS_CONSENT_REQUEST:
            if (resultCode == RESULT_OK) {
                // Get SMS message content
                String message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE);
                // Extract one-time code from the message and complete verification
                // message contains the entire text of the SMS message, so you will need
                // to parse the string.
                String oneTimeCode = parseOneTimeCode(message);
                
            } else {
                // Consent canceled, handle the error ...
            }
            break;
    }
}

Once you have the message text, you can parse out the verification code and auto-fill the form or otherwise complete the verification flow.

4. MainActivity

Below is the complete code as it appears in MainActivity.java.

import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;

import com.google.android.gms.auth.api.phone.SmsRetriever;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.tasks.Task;

public class MainActivity extends AppCompatActivity {

    private final String TAG =  MainActivity.class.getSimpleName();
    private static final int SMS_CONSENT_REQUEST = 2;

    EditText verificationCode;
    Button startTask;

    // Set to an unused request code
    private final BroadcastReceiver smsVerificationReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
                Bundle extras = intent.getExtras();
                Status smsRetrieverStatus = (Status) extras.get(SmsRetriever.EXTRA_STATUS);

                switch (smsRetrieverStatus.getStatusCode()) {
                    case CommonStatusCodes.SUCCESS:
                        // Get consent intent
                        Intent consentIntent =
            extras.getParcelable(SmsRetriever.EXTRA_CONSENT_INTENT);
                        try {
                            /*Start activity to show consent dialog to user within
                             *5 minutes, otherwise you'll receive another TIMEOUT intent
                             */
                            startActivityForResult(consentIntent, SMS_CONSENT_REQUEST);
                        } catch (ActivityNotFoundException e) {
                            // Handle the exception
                        }
                        break;
                    case CommonStatusCodes.TIMEOUT:
                        // Time out occurred, handle the error.
                        break;
                }
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        IntentFilter intentFilter = new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION);
        registerReceiver(smsVerificationReceiver, intentFilter);

        verificationCode = findViewById(R.id.verification_code);
        startTask = findViewById(R.id.start_task);

        //In this demo we will use a button click event to trigger listening for SMS User Consent broadcasts
        startTask.setOnClickListener(v -> smsTask());
    }


    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case SMS_CONSENT_REQUEST:
                if (resultCode == RESULT_OK) {
                    // Get SMS message content
                    String message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE);
                    // Extract one-time code from the message and complete verification
                    String oneTimeCode = parseOneTimeCode(message);
                    //for this demo we will display it instead
                    verificationCode.setText(oneTimeCode);

                } else {
                    // Consent canceled, handle the error
                }
                break;
        }
    }


    private String parseOneTimeCode(String message) {
        //simple number extractor
        return message.replaceAll("[^0-9]", "");
    }


    private void smsTask() {
        //Start listening for SMS User Consent broadcasts from senderPhoneNumber
        //The sender number being used was configured in my emulator, you can use your own number
        Task<Void> task = SmsRetriever.getClient(this).startSmsUserConsent("+254700123123");

        task.addOnCompleteListener(listener -> {
            if (listener.isSuccessful()) {
                // Task completed successfully
                Log.d(TAG, "Success");

            } else {
                // Task failed with an exception
                Exception exception = listener.getException();
                exception.printStackTrace();
            }
        });

    }

}

Github link to the project.

Leave a Reply

Close Menu