Accessing user messages is a sensitive matter that requires a strong commitment to privacy and ethical responsibility.
Why?
As engineers, we must anticipate the questions users may have when we request sensitive permissions, such as:
- Why do you need to read my messages?
- How will my data be used?
- Will you access any sensitive information?
It’s our responsibility to address these concerns before requesting access. Before prompting users for sensitive permissions, we should critically evaluate whether the access is truly necessary. Consider:
- Are we building a messaging application where reading messages is essential for core functionality?
- Is there a legitimate, user-focused reason to access this data?
If, after thoughtful consideration, we determine that reading user messages is required, the next step is to approach this need transparently and securely, ensuring we respect user privacy at every stage.
How?
Update Manifest
Add the telephony
feature and required permissions to your app’s AndroidManifest.xml
:
<uses-feature
android:name="android.hardware.telephony"
android:required="true"
tools:ignore="UnnecessaryRequiredFeature" />
<uses-permission android:name="android.permission.READ_SMS" />
Only set required="true"
if your app cannot function without this feature
If you omit the telephony feature, you’ll encounter errors like:
Permission exists without corresponding hardware ... tag.
Request Permission
There are multiple ways to request permissions in Android. Below are two common approaches.
This method uses Android’s built-in APIs:
// create permission reference
val permission = android.Manifest.permission.READ_SMS
// get activity & context
val activity: ComponentActivity = getActivity()
val context: Context = getContext()
// check if permission is granted
val isGranted = context.checkSelfPermission(permission) == android.content.pm.PackageManager.PERMISSION_GRANTED
if (isGranted) return
// if it is not granted, check if it can show permission dialog
val canRequest = ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
if (canRequest.not()) {
// navigate the user to settings to enable permission manually
return
}
// create launcher and launch permission
with(activity){
val launcher: ActivityResultLauncher<String> =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { result ->
// check if result is true and continue
// if result is not true, the permission was not granted
}
launcher.launch(permission)
}
The launcher
can only be created from the context of a ComponentActivity
This method uses Android’s built-in APIs:
// create permission reference
val permission = android.Manifest.permission.READ_SMS
// get activity & context
val activity: ComponentActivity = getActivity()
val context: Context = getContext()
// check if permission is granted
val isGranted = context.checkSelfPermission(permission) == android.content.pm.PackageManager.PERMISSION_GRANTED
if (isGranted) return
// if it is not granted, check if it can show permission dialog
val canRequest = ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
if (canRequest.not()) {
// navigate the user to settings to enable permission manually
return
}
// create launcher and launch permission
with(activity){
val launcher: ActivityResultLauncher<String> =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { result ->
// check if result is true and continue
// if result is not true, the permission was not granted
}
launcher.launch(permission)
}
The launcher
can only be created from the context of a ComponentActivity
Accompanist provides a Compose-friendly API for handling permissions.
- locate your
build.gradle.kts
and add accompanist-permission
dependency
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
- navigate to your desired
composable
and check for permission
// get permission state
val permissionState = rememberPermissionState(android.Manifest.permission.READ_SMS)
// check if permission isGranted
val isGranted = permissionState.status.isGranted
// if permission is granted launch permission request
if(isGranted.not()) permissionState.launchPermissionRequest()
Unlike the previous example this library doesn’t handle cases where the user has clicked not to show the permission again
With that we have a way of requesting and accepting permission
Read Messages
Once permissions are granted, you can access SMS data:
// get context
val context: Context = getContext()
// get content resolver from context
val contentResolver: ContentResolver = context.contentResolver
// get cursor for
val cursor =
context.contentResolver.query("content://sms/inbox".toUri(), null, null, null, null)
// use the cursor to read contents of the column
cursor?.use {
while(it.moveToNext()){
val date = it.getLongOrNull("date") // get message date
val body = it.getStringOrNull("body") // get message body
}
}
Conclusion
Reading user messages should never be taken lightly. Use this capability only when it is indispensable to your app’s functionality.
Always follow these guidelines:
- Clearly explain to users why the permission is needed.
- Handle sensitive data responsibly, respecting user privacy at all times.
- Stay updated on Android’s permission policies to maintain compliance.
With great power comes great responsibility. Build trust with your users by prioritizing their privacy and offering a transparent experience.
Now, you’re equipped to responsibly read device messages in your Android application.