Android
SMS
Programmatic Access
Read Messages
App Development

How can I read SMS messages from the device programmatically in Android?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Reading SMS messages programmatically on Android involves querying the system SMS content provider using the ContentResolver API. This requires proper runtime permissions and careful handling of the cursor-based query results. Since SMS data is considered sensitive, Google Play enforces strict policies on which apps may declare SMS permissions, so understanding both the technical implementation and the policy requirements is essential.

Declaring Permissions in the Manifest

Before your app can read SMS messages, you must declare the READ_SMS permission in your AndroidManifest.xml. Without this declaration, any attempt to query the SMS content provider will fail silently or throw a security exception.

xml
1<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2    package="com.example.smsreader">
3
4    <uses-permission android:name="android.permission.READ_SMS" />
5    <uses-permission android:name="android.permission.RECEIVE_SMS" />
6
7    <application
8        android:allowBackup="true"
9        android:label="@string/app_name">
10        <!-- activities and other components -->
11    </application>
12</manifest>

The RECEIVE_SMS permission is only needed if you also want to listen for incoming messages in real time via a BroadcastReceiver.

Requesting Runtime Permissions

Starting with Android 6.0 (API 23), dangerous permissions like READ_SMS must be requested at runtime in addition to being declared in the manifest. The user must explicitly grant the permission before your code can access SMS data.

kotlin
1// Kotlin - Requesting SMS permission at runtime
2import android.Manifest
3import android.content.pm.PackageManager
4import androidx.core.app.ActivityCompat
5import androidx.core.content.ContextCompat
6
7class MainActivity : AppCompatActivity() {
8
9    private val SMS_PERMISSION_CODE = 101
10
11    override fun onCreate(savedInstanceState: Bundle?) {
12        super.onCreate(savedInstanceState)
13        setContentView(R.layout.activity_main)
14
15        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS)
16            != PackageManager.PERMISSION_GRANTED) {
17            ActivityCompat.requestPermissions(
18                this,
19                arrayOf(Manifest.permission.READ_SMS),
20                SMS_PERMISSION_CODE
21            )
22        } else {
23            readSmsMessages()
24        }
25    }
26
27    override fun onRequestPermissionsResult(
28        requestCode: Int, permissions: Array<out String>, grantResults: IntArray
29    ) {
30        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
31        if (requestCode == SMS_PERMISSION_CODE &&
32            grantResults.isNotEmpty() &&
33            grantResults[0] == PackageManager.PERMISSION_GRANTED) {
34            readSmsMessages()
35        }
36    }
37}

Reading SMS Messages with ContentResolver in Kotlin

The SMS content provider exposes messages through the URI content://sms/inbox for received messages and content://sms/sent for sent messages. You query it using ContentResolver and iterate through the returned Cursor.

kotlin
1import android.database.Cursor
2import android.net.Uri
3
4fun readSmsMessages() {
5    val uri = Uri.parse("content://sms/inbox")
6    val cursor: Cursor? = contentResolver.query(
7        uri,
8        arrayOf("_id", "address", "body", "date", "read"),
9        null,  // selection (null = all messages)
10        null,  // selectionArgs
11        "date DESC"  // sort by most recent first
12    )
13
14    cursor?.use {
15        val addressIndex = it.getColumnIndexOrThrow("address")
16        val bodyIndex = it.getColumnIndexOrThrow("body")
17        val dateIndex = it.getColumnIndexOrThrow("date")
18
19        while (it.moveToNext()) {
20            val sender = it.getString(addressIndex)
21            val body = it.getString(bodyIndex)
22            val timestamp = it.getLong(dateIndex)
23            Log.d("SMS", "From: $sender, Body: $body, Date: $timestamp")
24        }
25    }
26}

The cursor?.use {} block ensures the cursor is closed automatically when processing is complete, preventing resource leaks.

Reading SMS Messages in Java

The same logic in Java uses a try-finally pattern to ensure the cursor is properly closed.

java
1import android.database.Cursor;
2import android.net.Uri;
3
4public void readSmsMessages() {
5    Uri uri = Uri.parse("content://sms/inbox");
6    Cursor cursor = getContentResolver().query(
7        uri,
8        new String[]{"_id", "address", "body", "date", "read"},
9        null,
10        null,
11        "date DESC"
12    );
13
14    if (cursor != null) {
15        try {
16            int addressIdx = cursor.getColumnIndexOrThrow("address");
17            int bodyIdx = cursor.getColumnIndexOrThrow("body");
18            int dateIdx = cursor.getColumnIndexOrThrow("date");
19
20            while (cursor.moveToNext()) {
21                String sender = cursor.getString(addressIdx);
22                String body = cursor.getString(bodyIdx);
23                long timestamp = cursor.getLong(dateIdx);
24                Log.d("SMS", "From: " + sender +
25                      ", Body: " + body +
26                      ", Date: " + timestamp);
27            }
28        } finally {
29            cursor.close();
30        }
31    }
32}

Querying Specific SMS Types

The SMS content provider supports several URI paths for different message categories. You can also use selection filters to narrow results.

kotlin
1// Read sent messages
2val sentUri = Uri.parse("content://sms/sent")
3
4// Read all messages (inbox + sent + draft)
5val allUri = Uri.parse("content://sms/")
6
7// Filter by sender number
8val cursor = contentResolver.query(
9    Uri.parse("content://sms/inbox"),
10    arrayOf("address", "body", "date"),
11    "address = ?",
12    arrayOf("+1234567890"),
13    "date DESC LIMIT 10"
14)
15
16// Filter unread messages only
17val unreadCursor = contentResolver.query(
18    Uri.parse("content://sms/inbox"),
19    arrayOf("address", "body", "date"),
20    "read = ?",
21    arrayOf("0"),  // 0 = unread, 1 = read
22    "date DESC"
23)

Google Play Policy Restrictions

Google Play restricts the use of SMS and call log permissions to apps that require them as core functionality. Since January 2019, only apps set as the default SMS handler or those with an approved use case (such as call forwarding or backup apps) may use these permissions in production apps distributed through the Play Store.

If your app reads SMS solely for verification codes, use the SMS Retriever API or SMS User Consent API instead. These APIs do not require the READ_SMS permission and are compliant with Play Store policies.

kotlin
1// SMS Retriever API for one-time verification codes (no permission needed)
2val client = SmsRetriever.getClient(this)
3val task = client.startSmsRetriever()
4task.addOnSuccessListener {
5    // Successfully started retriever, listen for SMS via BroadcastReceiver
6}

Common Pitfalls

  • Forgetting runtime permission checks: Declaring READ_SMS in the manifest is not enough on Android 6.0 and above. You must also request it at runtime or the query returns null.
  • Not closing the cursor: Failing to close the Cursor after querying causes memory leaks. Always use cursor.use {} in Kotlin or a try-finally block in Java.
  • Hardcoding SMS URI strings: Using "content://sms/inbox" directly is fragile. While it works on most devices, the Telephony.Sms contract class provides constants that are more reliable across manufacturers.
  • Ignoring Play Store policy: Publishing an app with READ_SMS permission without a qualifying use case will result in your app being rejected or removed from Google Play.
  • Assuming consistent column names: Some device manufacturers modify the SMS content provider schema. Always use getColumnIndexOrThrow() rather than hardcoded column indices to handle variations gracefully.

Summary

  • Declare READ_SMS in AndroidManifest.xml and request it at runtime for Android 6.0 and above.
  • Query the SMS content provider using ContentResolver.query() with URIs like content://sms/inbox or content://sms/sent.
  • Always close the cursor after use to prevent memory leaks.
  • Use selection parameters to filter by sender, read status, or date range.
  • For verification codes, prefer the SMS Retriever API to avoid needing the READ_SMS permission entirely.
  • Review Google Play policies before publishing, as SMS permissions are restricted to apps with core SMS functionality.

Course illustration
Course illustration

All Rights Reserved.