You are currently viewing OTP Autofill with Google’s SMS Retrieval API [Android]
android_otp_autofill

OTP Autofill with Google’s SMS Retrieval API [Android]

Sometimes we need to automate the OTP verification process in our mobile app for better user experience. For a smooth experience for this. google introduced it’s new SMS Retrieval API. Here in this article we will learn how to implement this in our app.

If you’ve used Uber or WhatsApp, you would have noticed that their SMS verification is automatic: The user need not manually type in the verification code. This makes their on-boarding feel seamless and kind of effortless on the part of the user. And the best part of this is that they do not request the user’s SMS permission. This is done using Google’s SMS Retrieval API, and in this article, we will see how to achieve just that in our apps as well.

Before we begin, we need to import the following libraries:

  1. Google Play Services Auth API – This library contains the SMS Retrieval API classes.
  2. Apache Commons – This will be used to parse out the verification code from the SMS message.
  3. EventBus – We will be using a BroadcastReceiver to listen for the retrieved SMS from the SMS Retrieval API. EventBus is a library that uses the publisher/subscriber pattern and will be used to simplify the communication between our BroadcastReceiver and our Activity classes.

Let’s add them to our app’s build.gradle file:



    implementation 'com.google.android.gms:play-services-auth:19.0.0'
    implementation 'org.apache.commons:commons-lang3:3.11'
    implementation 'org.greenrobot:eventbus:3.2.0'


Alright, we are set.

Our XML layout file is extremely simple. It contains a TextView in the middle of the screen. This view will be used to display the verification code we extract using the SMS Retrieval API.

activity_main.xml



<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/otp_text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textColor="@android:color/black"
        android:textSize="30sp"
        android:gravity="center"
        android:text="Loading..."
        />

</RelativeLayout>


What we are going to do next is to get an instance of the SmsRetrieverClient object, call its startSmsRetriever instance method, and attach onSuccess and onFailure Listeners to the resulting Task. I’m going to wrap that portion of code in a method to easily reuse later:



private fun startSmsListener() {
    smsRetrieverClient.startSmsRetriever()
        .addOnSuccessListener {
            Toast.makeText(this@MainActivity, "Starting Sms Retriever", Toast.LENGTH_SHORT).show()
        }.addOnFailureListener { e ->
            e.printStackTrace()
            Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show()
        }
}


Then we call that method immediately after onCreate:

MainActivity.kt



class MainActivity : AppCompatActivity() {
    private lateinit var smsRetrieverClient: SmsRetrieverClient
    private lateinit var otpTextView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        otpTextView = findViewById(R.id.otp_text_view)
        smsRetrieverClient = SmsRetriever.getClient(this)
        startSmsListener()
    }
...


At this point, when an SMS message is received on our device, the Play Services library we added earlier will broadcast an SmsRetriever.SMS_RETRIEVED_ACTION intent to our app. This intent contains the status of the background processing as well as the text of the SMS message.
Let’s create a BroadcastReceiver class to handle this:

SmsBroadcastReceiver.kt



class SmsBroadcastReceiver : BroadcastReceiver() {

    companion object {
        val TAG = SmsBroadcastReceiver::class.java.simpleName
    }

    override fun onReceive(context: Context?, intent: Intent) {
        Log.d(TAG, "onReceive Called!")
        if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
            val bundle = intent.extras
            if (bundle != null) {
                val status: Status = bundle[SmsRetriever.EXTRA_STATUS] as Status
                var isTimeout = false
                var smsMessage: String? = null
                when (status.statusCode) {
                    CommonStatusCodes.SUCCESS -> {
                        val message =
                            bundle[SmsRetriever.EXTRA_SMS_MESSAGE] as String
                        Log.d(TAG, message)
                        smsMessage = message
                    }
                    CommonStatusCodes.TIMEOUT -> {
                        Log.d(TAG, "Timeout")
                        isTimeout = true
                    }
                }
                EventBus.getDefault().post(SmsRetrievedEvent(isTimeout, smsMessage))
            }
        }
    }

}


In the BroadcastReceiver’s onReceive method, we first check the Status of the background processing done by the SMS Retrieval API. We also create an instance of a class called SmsRetrievedEvent. This is our event class that will be published by EventBus to our Subscriber.The SmsRetrievedEvent class is nothing but a simple Kotlin data class.

SmsRetrievedEvent.kt



data class SmsRetrievedEvent(
    val isTimeout: Boolean,
    val smsMessage: String?
)


If the background processing was successful, we set the message property of the SmsRetrievedEvent class to the SMS message we’ve retrieved. Otherwise, we set timeout to true if a timeout occurred. We finally publish the event to any listening subscriber.

Note: Timeouts occurs if messages are not handled within 5 minutes after processing by the SMS Retreival API.

Let’s not forget to register this BroadcastReceiver in our AndroidManifest file:

AndroidManifest.xml



<application>
        ...
        <receiver
            android:name=".SmsBroadcastReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
            </intent-filter>
        </receiver>
    </application>


Next, we are going to register, unregister as well as implement our subscriber in our MainActivity class. This method will be defined with the @Subscribe annotation and will be called when an event is published/posted.

Note: Always remember to unregister your subscribers to avoid memory leak issues.

MainActivity.kt



class MainActivity : AppCompatActivity() {
    private lateinit var smsRetrieverClient: SmsRetrieverClient
    private lateinit var otpTextView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        otpTextView = findViewById(R.id.otp_text_view)
        smsRetrieverClient = SmsRetriever.getClient(this)
        startSmsListener()
    }


    override fun onStart() {
        super.onStart()
        EventBus.getDefault().register(this)
    }

    override fun onStop() {
        EventBus.getDefault().unregister(this)
        super.onStop()
    }

    @Subscribe
    fun onEvent(event: SmsRetrievedEvent) {
    }

    private fun startSmsListener() {
        smsRetrieverClient.startSmsRetriever()
            .addOnSuccessListener {
                Toast.makeText(this@MainActivity, "Starting Sms Retriever", Toast.LENGTH_SHORT).show()
            }.addOnFailureListener { e ->
                e.printStackTrace()
                Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_SHORT).show()
            }
    }
}


Finally, we parse out the 4 digit verification code from the message using the substringAfterLast method from the Apache Commons library, and display it in our TextView



...
@Subscribe
    fun onEvent(event: SmsRetrievedEvent) {
        Log.d("SMS EVENT", "RECEIVED SMS EVENT")
        Log.d("SMS EVENT - Timeout=", event.isTimeout.toString())
        Log.d("SMS EVENT - Message=", if(!event.isTimeout) event.smsMessage!! else "Timeout: No Message")

        val otp: String =
            StringUtils.substringAfterLast(event.smsMessage, "is").replace(":", "")
                .trim().substring(0, 4)
        Log.d("EVENT", otp)

        runOnUiThread {
            otpTextView.text = if (!event.isTimeout) otp else "Timeout :("
        }

        startSmsListener()
    }
...


One important thing to keep in the back of our minds:
The SMS Retreival API requires that SMS messages be in the following format:



Your verification code is 9872


FA+9qCX9VSu


The SMS message must end with an 11-character hash string that identifies your app(which is FA+9qCX9VSu in this case). Click here to see how to compute your app’s hash.

At this point we can send the SMS message to our device, extract and send the 4 digit code to our server for verification.

Leave a Reply