Skip to content

Instantly share code, notes, and snippets.

@decodeandroid
Created January 23, 2025 03:33
Show Gist options
  • Save decodeandroid/2424fb53d4b942d7fc00cabc7644ce26 to your computer and use it in GitHub Desktop.
Save decodeandroid/2424fb53d4b942d7fc00cabc7644ce26 to your computer and use it in GitHub Desktop.
Read Write SMS using Content Providers in Jetpack Compose Android
import android.app.Activity;
public class ComposeSmsActivity extends Activity {
}
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
public class HeadlessSmsSendService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
setContent {
var permissionGranted by remember { mutableStateOf(false) }
RequestPermissions {
permissionGranted = true
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (permissionGranted) {
SMSApp(this@MainActivity)
} else {
Text("Permission Required to Access Call Logs")
}
}
}
<!--Read and write sms-->
<uses-permission android:name="android.permission.READ_SMS" />
<uses-feature
android:name="android.hardware.telephony"
android:required="false" />
<!-- BroadcastReceiver that listens for incoming SMS messages -->
<receiver
android:name=".smsapp.SmsReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER" />
</intent-filter>
</receiver>
<!-- BroadcastReceiver that listens for incoming MMS messages -->
<receiver
android:name=".smsapp.MmsReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<!-- Activity that allows the user to send new SMS/MMS messages -->
<activity
android:name=".smsapp.ComposeSmsActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</activity>
<!-- Service that delivers messages from the phone "quick response" -->
<service
android:name=".smsapp.HeadlessSmsSendService"
android:exported="true"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class MmsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
}
import android.Manifest
import android.app.Activity
import android.app.role.RoleManager
import android.app.role.RoleManager.ROLE_SMS
import android.content.ContentValues
import android.content.Context
import android.content.Context.ROLE_SERVICE
import android.content.Intent
import android.os.Build
import android.provider.Telephony
import android.provider.Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT
import android.provider.Telephony.Sms.Intents.EXTRA_PACKAGE_NAME
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.android.material.dialog.MaterialAlertDialogBuilder
data class SMSModel(
val address: String,
val body: String,
val date: String
)
@Composable
fun SMSApp(activity: Activity) {
var messages by remember { mutableStateOf<List<SMSModel>>(emptyList()) }
var phoneNumber by remember { mutableStateOf("") }
var messageBody by remember { mutableStateOf("") }
val context = LocalContext.current
LaunchedEffect(Unit) {
messages = getAllSMS(context)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
) {
// Input Section
OutlinedTextField(
value = phoneNumber,
onValueChange = { phoneNumber = it },
label = { Text("Phone Number") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = messageBody,
onValueChange = { messageBody = it },
label = { Text("Message") },
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(
onClick = {
insertSMS(context, phoneNumber, messageBody)
phoneNumber = ""
messageBody = ""
messages = getAllSMS(context)
}
) {
Text("Save SMS")
}
Button(
onClick = {
if (Telephony.Sms.getDefaultSmsPackage(context) != null &&
Telephony.Sms.getDefaultSmsPackage(context) == context.packageName
) {
restoreDefaultSmsProvider(activity)
} else {
MaterialAlertDialogBuilder(activity)
.setTitle("Alert!")
.setMessage("This app needs to be temporarily set as the default SMS app to restore SMS.\n * After restoring all sms backup please change your default SMS handler.")
.setCancelable(false)
.setNegativeButton(
"No"
) { dialog, which ->
dialog.dismiss()
}
.setPositiveButton(
"Yes"
) { dialog, id ->
requestDefaultSmsPackageChange(activity)
}
.show()
}
}
) {
Text("Change Default SMS handler")
}
}
Spacer(modifier = Modifier.height(16.dp))
// Messages List
LazyColumn {
items(messages) { message ->
MessageCard(message)
}
}
}
}
@Composable
fun MessageCard(message: SMSModel) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer)
) {
Column(
modifier = Modifier
.padding(7.dp)
) {
Text(
text = "From: ${message.address}",
fontSize = 15.sp,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = message.body,
fontSize = 13.sp,
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.Light
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Date: ${message.date}",
fontSize = 14.sp,
fontStyle = FontStyle.Normal,
fontWeight = FontWeight.SemiBold
)
}
}
}
@Composable
fun RequestPermissions(
onGranted: () -> Unit
) {
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val allGranted = permissions.values.all { it }
if (allGranted) {
onGranted()
}
}
LaunchedEffect(Unit) {
launcher.launch(
arrayOf(
Manifest.permission.READ_SMS,
)
)
}
}
fun getAllSMS(context: Context): List<SMSModel> {
val smsList = mutableListOf<SMSModel>()
val cursor = context.contentResolver.query(
Telephony.Sms.CONTENT_URI,
arrayOf(
Telephony.Sms.ADDRESS,
Telephony.Sms.BODY,
Telephony.Sms.DATE
),
null,
null,
Telephony.Sms.DEFAULT_SORT_ORDER
)
cursor?.use {
val addressIndex = it.getColumnIndex(Telephony.Sms.ADDRESS)
val bodyIndex = it.getColumnIndex(Telephony.Sms.BODY)
val dateIndex = it.getColumnIndex(Telephony.Sms.DATE)
while (it.moveToNext()) {
val address = it.getString(addressIndex)
val body = it.getString(bodyIndex)
val date = java.util.Date(it.getLong(dateIndex)).toString()
smsList.add(SMSModel(address, body, date))
}
}
return smsList
}
fun insertSMS(context: Context, address: String, body: String) {
val values = ContentValues().apply {
put(Telephony.Sms.ADDRESS, address)
put(Telephony.Sms.BODY, body)
put(Telephony.Sms.DATE, System.currentTimeMillis())
put(Telephony.Sms.TYPE, Telephony.Sms.MESSAGE_TYPE_INBOX)
}
context.contentResolver.insert(Telephony.Sms.CONTENT_URI, values)
}
private fun requestDefaultSmsPackageChange(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val roleManager = activity.getSystemService(ROLE_SERVICE) as RoleManager
if (!roleManager.isRoleHeld(ROLE_SMS)) {
val intent = roleManager.createRequestRoleIntent(ROLE_SMS)
activity.startActivityForResult(intent, 1)
}
} else {
val intent = Intent(ACTION_CHANGE_DEFAULT).putExtra(
EXTRA_PACKAGE_NAME, activity.packageName
)
activity.startActivityForResult(intent, 1)
}
}
fun restoreDefaultSmsProvider(activity: Activity) {
MaterialAlertDialogBuilder(activity)
.setTitle("Change Default SMS Handler")
.setMessage("If you have successfully restored your SMS then make previous SMS app as default SMS handler")
.setCancelable(false)
.setNegativeButton(
"No"
) { dialog, which ->
dialog.dismiss()
}
.setPositiveButton(
"Yes"
) { dialog, id ->
val intent = Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_APP_MESSAGING)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
activity.startActivity(intent)
}
.show()
}
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class SmsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment