Crack the OTP Code: Easy SMS Reading Techniques for Android Developers
In today’s mobile-first world, seamless user experiences are crucial, especially regarding authentication. One common method is using One-Time Passwords (OTPs) sent via SMS. But how can you read these SMS messages and extract the OTP effortlessly in your Android app?
Photo by Onur Binay on Unsplash |
In this blog, we’ll dive into various methods to read SMS in Android using Kotlin and show you how to implement them step-by-step.
Why Read SMS for OTP Verification?
Reading SMS messages allows you to streamline the login process by automatically retrieving the OTP sent to users. This enhances user experience by reducing manual input errors and speeding up authentication.
Method 1: Using the SMS Retriever API
The SMS Retriever API is a powerful tool provided by Google that allows you to receive SMS messages without needing SMS permissions. Let’s get started!
Credits: Google Developers |
Step 1: Set Up Your Project
First, ensure you have the necessary dependencies in your
build.gradle
file:
dependencies { implementation 'com.google.android.gms:play-services-auth-api-phone:19.2.0' }
Step 2: Start the SMS Retriever
In your activity, initialize the SMS Retriever:
import com.google.android.gms.auth.api.phone.SmsRetriever fun startSmsRetriever() { val client = SmsRetriever.getClient(this) val task = client.startSmsRetriever() task.addOnSuccessListener { // SMS Retriever started successfully }.addOnFailureListener { // Failed to start SMS Retriever } }
Step 3: Create a BroadcastReceiver
Create a BroadcastReceiver
to handle incoming SMS messages.
This receiver will extract the OTP for you.
class SmsBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (SmsRetriever.SMS_RETRIEVED_ACTION == intent?.action) { val extras: Bundle? = intent.extras val status = extras?.get(SmsRetriever.EXTRA_STATUS) as? Int if (status == SmsRetriever.RESULT_STATUS_OK) { val message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as? String val otp = extractOtp(message) Log.d("SmsReceiver", "OTP received: $otp") // Use the OTP as needed } } } private fun extractOtp(message: String?): String? { return message?.split(" ")?.find { it.length == 6 && it.all { char -> char.isDigit() } } } }
Step 4: Register the BroadcastReceiver
In your activity, register the BroadcastReceiver
to listen
for SMS:
override fun onStart() { super.onStart() val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION) registerReceiver(smsBroadcastReceiver, intentFilter) } override fun onStop() { super.onStop() unregisterReceiver(smsBroadcastReceiver) }
Step 5: Send a Properly Formatted SMS
Make sure the SMS you send is formatted correctly, like this:
Your verification code is: 123456
This will ensure that the SMS Retriever can extract the OTP easily.
Method 2: Using a BroadcastReceiver to Read Incoming SMS
This method provides a straightforward way to read SMS messages with the necessary permissions while giving you control over incoming messages as they are received.
Step 1: Declare Permissions
Add the following permission to your AndroidManifest.xml
:
<uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.READ_SMS"/>
Step 2: Create a BroadcastReceiver
Create a BroadcastReceiver
that listens for incoming SMS
messages:
class SmsReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == "android.provider.Telephony.SMS_RECEIVED") { val bundle: Bundle? = intent.extras val msgs = bundle?.get("pdus") as Array<*> for (msg in msgs) { val smsMessage = SmsMessage.createFromPdu(msg as ByteArray) val sender = smsMessage.displayOriginatingAddress val messageBody = smsMessage.messageBody Log.d("SmsReceiver", "Sender: $sender, Message: $messageBody") // Here you can extract and process the OTP if needed } } } }
Step 3: Register the Receiver in Manifest
Register your BroadcastReceiver
in the
AndroidManifest.xml
:
<receiver android:name=".SmsReceiver"> <intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver>
Step 4: Handle Permissions
Ensure you handle permissions, especially on devices running Android 6.0 (API level 23) and above:
private val REQUEST_CODE_RECEIVE_SMS = 101 private fun checkSmsPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS), REQUEST_CODE_RECEIVE_SMS) } else { // Permission granted; ready to receive SMS } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_CODE_RECEIVE_SMS && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission granted; ready to receive SMS } }
Method 3: Using Content Resolver
If you need more control and want to read SMS messages directly, you can
use a ContentResolver
. This method requires
READ_SMS
permission.
Step 1: Declare Permissions
Add the following permission to your AndroidManifest.xml
:
<uses-permission android:name="android.permission.READ_SMS"/>
Step 2: Request Permission
In your activity, check and request the permission:
private val REQUEST_CODE_READ_SMS = 100 private fun checkSmsPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_SMS), REQUEST_CODE_READ_SMS) } else { readSms() } }
Step 3: Read SMS
Now you can read SMS messages using the ContentResolver
:
private fun readSms() { val cursor = contentResolver.query( Uri.parse("content://sms/inbox"), null, null, null, null ) cursor?.use { while (it.moveToNext()) { val id = it.getString(it.getColumnIndexOrThrow("_id")) val address = it.getString(it.getColumnIndexOrThrow("address")) val body = it.getString(it.getColumnIndexOrThrow("body")) Log.d("SMS", "ID: $id, Address: $address, Body: $body") } } }
Step 4: Handle Permissions Result
Make sure to handle the permission result:
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_CODE_READ_SMS && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { readSms() } }
Now you have three powerful methods to read SMS and verify OTPs in your Android application using Kotlin!
Key Differences
Below, we'll explain the key differences between these approaches to help you decide which best suits your application's needs.
Permissions Required:
- SMS Retriever API: No permissions are needed to read SMS.
- BroadcastReceiver: Requires both RECEIVE_SMS and READ_SMS permissions.
- Content Resolver: Requires READ_SMS permission.
Privacy:
- SMS Retriever API: Enhances user privacy; does not access all SMS.
- BroadcastReceiver: Less privacy; intercepts all incoming SMS.
- Content Resolver: Less privacy; accesses all SMS messages.
Implementation Complexity:
- SMS Retriever API: Simpler for OTP retrieval.
- BroadcastReceiver: Moderate complexity; requires handling permissions and managing received messages.
- Content Resolver: More complex; needs permission handling and SMS management.
Real-Time Retrieval:
- SMS Retriever API: Retrieves SMS only when specifically formatted for the API.
- BroadcastReceiver: Captures SMS messages in real-time as they arrive.
- Content Resolver: Can read all SMS messages from the inbox at any time.
SMS Format:
- SMS Retriever API: Requires specific formatting (e.g., with a hash).
- BroadcastReceiver: No specific format required; captures all incoming messages.
- Content Resolver: No specific format required; can read any SMS message.
Use Case:
- SMS Retriever API: Best for OTP verification and temporary messages.
- BroadcastReceiver: Ideal for applications requiring immediate access to incoming SMS.
- Content Resolver: Suitable for apps needing broader SMS access (e.g., messaging apps).
User Experience:
- SMS Retriever API: Seamless; no interruptions for permission prompts.
- BroadcastReceiver: This may interrupt user experience due to permission requests.
- Content Resolver: This may interrupt user experience due to permission requests.
Message Filtering:
- SMS Retriever API: Automatically filters messages based on predefined criteria.
- BroadcastReceiver: Requires manual processing of intercepted messages.
- Content Resolver: Requires manual filtering and processing of messages.
Summary
- SMS Retriever API is ideal for OTP retrieval and authentication scenarios, providing a simple and privacy-friendly approach.
- BroadcastReceiver captures incoming SMS messages in real-time but also requires permissions and processes all messages, which may only sometimes be desirable for user privacy.
- Content Resolver offers broader access to SMS but requires more setup and user permissions, making it suitable for apps that need comprehensive SMS management.
Each method has strengths and weaknesses, so the best choice depends on your app’s specific needs and the user experience you want to provide!
References:
- SMS Retriever API: https://developers.google.com/identity/sms-retriever/overview
- BroadcastReceiver: https://developer.android.com/develop/background-work/background-tasks/broadcasts
- Content Resolver: https://developer.android.com/guide/topics/providers/content-providers
Thanks for reading this article. Hope you would have liked it!. Please share and subscribe to my blog to support.
Works fine while the app is running. Throws error when the app is closed. mListener.messageReceived(messageBody); throws Null object reference. Please help me
ReplyDeleteI have republished the Article. Now you can choose from 3 different methodsm depends on your app’s specific needs.
Delete