Libraries

Auto read OTP android with SMS User Consent API

In this blog, I will show how to auto read OTP in android with the help of SMS User Consent API. In our previous blog, we have learned Automatic SMS Verification Android using SMS Retriever API. But it has some difficulty for example

  • SMS should start with <#> tag,
  • SMS should end with application hash code, etc.

In this article, I’m going to show you the implementation auto-read OTP using SMS User Consent API. It is complements of SMS Retriever API. Using SMS User Consent API we can achieve auto read OTP by allowing an app to prompt the user to grant access to the content of a single SMS message. When the user gives consent, the app can access the entire message to automatically complete SMS verification.

Flow for Auto Read OTP with SMS User Consent API

Image source google developers website.

There are 3 steps of user flow of this SMS User Consent API
  • Start – The first thing you have to start is listening before sending the message or OTP to the server.
  • Prompt – When the user’s device receives the SMS message containing a one-time code, Google Play services display the contents of the message to the user and asks for consent to make that text available to your app.
  • Read Message – If the user consents, the entire SMS message is made available to your app.
Message Criteria

Google play services imposed the followings SMS criteria

  • It contains a one-time password– The should message contains a 4–10 character alphanumeric string with at least one number.
  • Contacts – The message was sent by a phone number that’s not in the user’s contacts.
  • Timing – The API will look for the One Time Code for a maximum time of 5 minutes.
1. Let’s create a new project in Android Studio

Open to an android studio and create a new project. That project we will implement auto read OTP features. So I’m adding few strings in strings.xml file that we will use this project.

<resources>
  <string name="app_name">SMS verification</string>
  <string name="send_otp">Send OTP</string>
  <string name="generate_otp">Generate OTP</string>
  <string name="received_message">Received Message</string>
  <string name="verify_otp">Verify OTP</string>
</resources>
2. Add SMS User Consent API libraries in your app.

We should add below dependencies in app-level build.gradle in your project.

    // add these lines in your app build.gradle
    implementation 'com.google.android.gms:play-services-auth:17.0.0'
    implementation 'com.google.android.gms:play-services-auth-api-phone:17.1.0'
3. Listen to the incoming messages

The next step is to listen to incoming messaging. we can start listening for incoming messages using below method. We can add sender phone number, SMS User Consent API will only trigger on messages from this number. I’m adding null here

  private void startSmsUserConsent() {
    SmsRetrieverClient client = SmsRetriever.getClient(this);
    //We can add sender phone number or leave it blank
    // I'm adding null here
    client.startSmsUserConsent(null).addOnSuccessListener(new OnSuccessListener<Void>() {
      @Override
      public void onSuccess(Void aVoid) {
        Toast.makeText(getApplicationContext(), "On Success", Toast.LENGTH_LONG).show();
      }
    }).addOnFailureListener(new OnFailureListener() {
      @Override
      public void onFailure(@NonNull Exception e) {
        Toast.makeText(getApplicationContext(), "On OnFailure", Toast.LENGTH_LONG).show();
      }
    });
  }
4. Create a BroadcastReceiver named is SmsBroadcastReceiver

In SmsBroadcastReceiverListener we will catch SMS and set back using SmsBroadcastReceiverListener callback methods.

package com.smsverification;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
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;

public class SmsBroadcastReceiver extends BroadcastReceiver{

  SmsBroadcastReceiverListener smsBroadcastReceiverListener;

  @Override
  public void onReceive(Context context, Intent intent) {
    if (intent.getAction() == SmsRetriever.SMS_RETRIEVED_ACTION) {
      Bundle extras = intent.getExtras();
      Status smsRetrieverStatus = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
      switch (smsRetrieverStatus.getStatusCode()) {
        case CommonStatusCodes.SUCCESS:
          Intent messageIntent = extras.getParcelable(SmsRetriever.EXTRA_CONSENT_INTENT);
          smsBroadcastReceiverListener.onSuccess(messageIntent);
          break;
        case CommonStatusCodes.TIMEOUT:
          smsBroadcastReceiverListener.onFailure();
          break;
      }
    }
  }

  public interface SmsBroadcastReceiverListener {
    void onSuccess(Intent intent);

    void onFailure();
  }
}
5. Show the permission consent

When the broadcast receiver catch any message that contains OTP, Google paly services display is contents( BottomSheet ) for asking permission. If the user allows then full messing is available to your app. So let’s register broadcast receiver.

private void registerBroadcastReceiver() {
    smsBroadcastReceiver = new SmsBroadcastReceiver();
    smsBroadcastReceiver.smsBroadcastReceiverListener =
        new SmsBroadcastReceiver.SmsBroadcastReceiverListener() {
          @Override
          public void onSuccess(Intent intent) {
            startActivityForResult(intent, REQ_USER_CONSENT);
          }

          @Override
          public void onFailure() {

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

  @Override
  protected void onStart() {
    super.onStart();
    registerBroadcastReceiver();
  }

  @Override
  protected void onStop() {
    super.onStop();
    unregisterReceiver(smsBroadcastReceiver);
  }
6. Receive message in onActivityResult()

If the user consents, the entire SMS message is made available to your app. we will catch messaging in

 @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQ_USER_CONSENT) {
      if ((resultCode == RESULT_OK) && (data != null)) {
        //That gives all message to us.
        // We need to get the code from inside with regex
        String message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE);
        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
        textViewMessage.setText(
            String.format("%s - %s", getString(R.string.received_message), message));

        getOtpFromMessage(message);
      }
    }
  }
7. Extract OTP from messaging.

Suppose our messaging is “Your OTP is 123456. Please do not share OTP with others “. Now you have to extract OTP from methods using below method.

  private void getOtpFromMessage(String message) {
    // This will match any 6 digit number in the message
    Pattern pattern = Pattern.compile("(|^)\\d{6}");
    Matcher matcher = pattern.matcher(message);
    if (matcher.find()) {
      otpText.setText(matcher.group(0));
    }
  }
8. I’m modifying main activity layout for demonstration

For creating a fully functional sample app, I’m adding TextView for showing messaging and edit text for showing OTP.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    >

  <Button
      android:id="@+id/button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/verify_otp"
      android:textAllCaps="false"
      android:textSize="18sp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
  <TextView
      android:id="@+id/textViewMessage"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="16dp"
      android:layout_marginTop="24dp"
      android:layout_marginEnd="16dp"
      android:gravity="center"
      android:textSize="16sp"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/button"
      tools:text="@string/received_message"
      />
  <ImageView
      android:id="@+id/imageView"
      android:layout_width="150dp"
      android:layout_height="150dp"
      android:layout_marginTop="32dp"
      app:layout_constraintBottom_toTopOf="@+id/editTextOTP"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:srcCompat="@drawable/ic_otp"
      />
  <EditText
      android:id="@+id/editTextOTP"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="16dp"
      android:layout_marginEnd="16dp"
      android:layout_marginBottom="16dp"
      android:ems="10"
      android:gravity="center"
      android:hint="OTP"
      android:inputType="number"
      app:layout_constraintBottom_toTopOf="@+id/button"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      />

</androidx.constraintlayout.widget.ConstraintLayout>
9. We almost done, put all java code together in activity files.
package com.smsverification;

import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.gms.auth.api.phone.SmsRetriever;
import com.google.android.gms.auth.api.phone.SmsRetrieverClient;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MainActivity extends AppCompatActivity {
  private static final int REQ_USER_CONSENT = 200;
  SmsBroadcastReceiver smsBroadcastReceiver;
  Button verifyOTP;
  TextView textViewMessage;
  EditText otpText;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // find view by ids
    verifyOTP = findViewById(R.id.button);
    textViewMessage = findViewById(R.id.textViewMessage);
    otpText = findViewById(R.id.editTextOTP);
    startSmsUserConsent();
  }

  private void startSmsUserConsent() {
    SmsRetrieverClient client = SmsRetriever.getClient(this);
    //We can add sender phone number or leave it blank
    // I'm adding null here
    client.startSmsUserConsent(null).addOnSuccessListener(new OnSuccessListener<Void>() {
      @Override
      public void onSuccess(Void aVoid) {
        Toast.makeText(getApplicationContext(), "On Success", Toast.LENGTH_LONG).show();
      }
    }).addOnFailureListener(new OnFailureListener() {
      @Override
      public void onFailure(@NonNull Exception e) {
        Toast.makeText(getApplicationContext(), "On OnFailure", Toast.LENGTH_LONG).show();
      }
    });
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQ_USER_CONSENT) {
      if ((resultCode == RESULT_OK) && (data != null)) {
        //That gives all message to us.
        // We need to get the code from inside with regex
        String message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE);
        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
        textViewMessage.setText(
            String.format("%s - %s", getString(R.string.received_message), message));

        getOtpFromMessage(message);
      }
    }
  }

  private void getOtpFromMessage(String message) {
    // This will match any 6 digit number in the message
    Pattern pattern = Pattern.compile("(|^)\\d{6}");
    Matcher matcher = pattern.matcher(message);
    if (matcher.find()) {
      otpText.setText(matcher.group(0));
    }
  }

  private void registerBroadcastReceiver() {
    smsBroadcastReceiver = new SmsBroadcastReceiver();
    smsBroadcastReceiver.smsBroadcastReceiverListener =
        new SmsBroadcastReceiver.SmsBroadcastReceiverListener() {
          @Override
          public void onSuccess(Intent intent) {
            startActivityForResult(intent, REQ_USER_CONSENT);
          }

          @Override
          public void onFailure() {

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

  @Override
  protected void onStart() {
    super.onStart();
    registerBroadcastReceiver();
  }

  @Override
  protected void onStop() {
    super.onStop();
    unregisterReceiver(smsBroadcastReceiver);
  }
}

Congrats, Let’s use the SMS content API is your application

Conclusion

In this post, we learned how to auto-read OTP android using SMS content API in our application. So let’s recap the step of implementation, basically, three major steps Start Listening messaging, show prompt and Read messaging.

 I hope it’s helpful for you, Help me by sharing this post with all your friends who learning android app development.

Get Solution Code

Keep Learning 🙂

Subscribe
Notify of
8 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments