Created
January 23, 2025 03:33
-
-
Save decodeandroid/2424fb53d4b942d7fc00cabc7644ce26 to your computer and use it in GitHub Desktop.
Read Write SMS using Content Providers in Jetpack Compose Android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.app.Activity; | |
public class ComposeSmsActivity extends Activity { | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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