How to get sms sent confirmation for each contact/person in android?
android, android-broadcast, broadcastreceiver, sms
Solution
Attaching and retrieving extras on those `Intent`s is no different here than when passing data between Activities. The only real catch is that `PendingIntent`s might not behave as one expects.
The `get*()` methods will return a distinct `PendingIntent` only if the passed `Intent` is different than all currently active ones per the `Intent#filterEquals()` method, or if the request code is not currently in use for an equal `Intent`. Different extras on an otherwise-same `Intent` with the same request code will not cause a new `PendingIntent` to be created. Depending on the flag passed in that case, those extras might be ignored, or they might overwrite those in a currently active `PendingIntent`, possibly leading to incorrect results.
Any of the filter properties in `Intent` – i.e., the component, the action, the data URI, etc. – can be used to distinguish them, and also to relay information in some form, so there are a few slightly different ways to set up the construction of the `PendingIntent`s, and their receipt and processing on the other end. The original version of this answer showed a somewhat simplistic yet straightforward Java example in a single `Activity` class, using single-part messages with redundant extras on each result. The current version comprises relevant snippets from a complete, proper example and is therefore a bit more involved, so if you don't need anything terribly complex, that previous revision might be a preferable starting point.
Sending
This example uses only one Receiver class for both the send and delivery results, so we will use different actions to distinguish `Intent`s up to result type. If you prefer separate Receiver classes for each, you don't necessarily need the two different actions, since the components would be different in that case.
const val ACTION_SMS_SENT = "com.your.app.action.SMS_SENT"
const val ACTION_SMS_DELIVERED = "com.your.app.action.SMS_DELIVERED"
NB: The full package name prefix isn't technically necessary – just need a couple of unique strings – but it can help prevent confusion during debugging, especially if you're testing additional SMS stuff alongside this.
To track the individual message, the data URI is used to pass the ID for the message in our app's database, since this corresponds with Android's general design around `ContentProvider`s and content URIs, though we won't be using a Provider here. Also, as mentioned, the URI is a filter criterion, so now we can ensure distinct send and delivery results for each message. To illustrate, an `Intent` for a send report:
val sendIntent = Intent(
ACTION_SMS_SENT,
Uri.fromParts("app", "com.your.app", messageId.toString()),
context,
SmsResultReceiver::class.java
)
The next thing to consider is multi-part messages, which require `ArrayList`s of `PendingIntent`s, because each part is actually an individual SMS message with its own results. As far as the send results are concerned, at least, we should consider a message failed if any one part fails, so we need a distinct result for each part. This is a good use for `PendingIntent`'s `requestCode`, and passing the part number for that parameter in the `getBroadcast()` calls will ensure a new `PendingIntent` for each:
val sendPending = PendingIntent.getBroadcast(
context,
partNumber,
sendIntent,
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE
)
`FLAG_ONE_SHOT` automatically cancels the `PendingIntent` after it's sent, and since we only need these once, there's no need to have the system keep them around. If we need to resend, we'll request them again.
As for the other available flags, briefly:
`FLAG_MUTABLE` and `FLAG_IMMUTABLE` dictate whether the eventual sender of our `Intent` can modify it with `Intent#fillIn()` before sending it back to us. Both send and delivery reports involve extras on the received `Intent`, so we use `FLAG_MUTABLE` to allow those to be added. (The only possible extra on a send result is an optional error code for general failures, so we could use `FLAG_IMMUTABLE` for that one if we're not planning to check that.)
Unfortunately, since API level 31 we're required to specify one or the other, and of course they didn't always exist, so you'll need to do an ugly `Build.VERSION.SDK_INT` check, though I've omitted that above.
`FLAG_CANCEL_CURRENT` is for cases where you've previously handed out a `PendingIntent` with some data to someone, and now you need to update that data and also make sure that the first someone can't blindly send that `Intent` with data that was changed underneath it. This isn't a concern for our particular situation.
`FLAG_UPDATE_CURRENT` is for the same cases as `FLAG_CANCEL_CURRENT`, but where you don't need to prevent that first someone from sending the updated data.
`FLAG_NO_CREATE` is used when you want to check if the given `PendingIntent` currently exists. This can be handy for several different things – e.g., determining if an alarm has been set – but isn't necessary for our design here.
Lastly for the send, because we're supporting multi-part messages, we need some way to determine in the Receiver when the results are complete for a given message. Since we don't really care which specific part may fail, we don't need to track part numbers all the way through, so we'll simply include a `Boolean` extra as a flag for the final part. This actually ends up being the only extra needed for this design, but all of the previous considerations were certainly necessary to make sure that the correct values are set on each part.
const val EXTRA_IS_LAST_PART = "com.your.app.action.IS_LAST_PART"
If we put all of that together, with a little refactoring, we end up with a send function that looks like:
fun sendMessage(context: Context, id: Int, address: String, body: String) {
val manager = getSmsManager(context)
val parts = manager.divideMessage(body)
val partCount = parts.size
val send = arrayListOf<PendingIntent>()
val delivery = arrayListOf<PendingIntent?>()
for (partNumber in 1..partCount) {
val isLastPart = partNumber == partCount
send += PendingIntent.getBroadcast(
context,
partNumber,
createResultIntent(context, id)
.setAction(ACTION_SMS_SENT)
.putExtra(EXTRA_IS_LAST_PART, isLastPart),
RESULT_FLAGS
)
delivery += if (isLastPart) {
PendingIntent.getBroadcast(
context,
0,
createResultIntent(context, id)
.setAction(ACTION_SMS_DELIVERED),
RESULT_FLAGS
)
} else null
}
manager.sendMultipartTextMessage(address, null, parts, send, delivery)
}
private fun createResultIntent(context: Context, messageId: Int) = Intent(
null,
Uri.fromParts("app", "com.your.app", messageId.toString()),
context,
SmsResultReceiver::class.java
)
private val RESULT_FLAGS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_ONE_SHOT
}
Note that the delivery list has null entries for all except the last part, as we're assuming that the status of the last part will be the overall status for the message as a whole. I'm not sure that that's a valid assumption to make, but all of the AOSP messaging apps do it this way. You can, of course, rework that to get delivery results for each part just like the send results, if you'd prefer.
Results
As you might've noticed above, we're using explicit `Intent`s, so we need to register `SmsResultReceiver` in the manifest. For example:
<receiver android:name=".SmsResultReceiver" />
The outline for the Receiver is quite simple: pull the message ID from the data URI, check the action for the result type, then grab the relevant values and figure the result appropriately.
For a send, the `resultCode` indicates success if it's equal to `Activity.RESULT_OK`; otherwise, it's an error code corresponding to a constant in `SmsManager`. The full list of possible values is shown in the docs for `sendMultipartTextMessage()`.
For delivery, the result is actually the `status` of an `SmsMessage` that's attached encoded in a byte array extra. Android recognizes only three broad categories for this – the `Telephony.Sms.STATUS_*` constants shown below – but that status value can encode a specific failure mode, if you'd want to check that, for whatever reason. This answer has the details.
class SmsResultReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val messageId = intent.data?.fragment?.toIntOrNull() ?: return
when (intent.action) {
ACTION_SMS_SENT -> {
val isLastPart =
intent.getBooleanExtra(EXTRA_IS_LAST_PART, false)
if (resultCode == Activity.RESULT_OK) {
// Success
} else {
// Failed; resultCode is an error code, the
// values for which are in SmsManager constants
}
}
ACTION_SMS_DELIVERED -> {
val status =
getResultMessageFromIntent(intent)?.status ?: return
when {
status == Telephony.Sms.STATUS_COMPLETE -> { … }
status >= Telephony.Sms.STATUS_FAILED -> { … } // >=, not ==
else -> { … } // Telephony.Sms.STATUS_PENDING
}
}
}
}
}
private fun getResultMessageFromIntent(intent: Intent): SmsMessage? =
SmsMessage.createFromPdu(
intent.getByteArrayExtra("pdu"),
intent.getStringExtra("format")
)
It is important to note that not all carriers offer delivery reports. For those that don't, the delivery `PendingIntent`s just won't fire, so don't rely on that happening. Unfortunately, the emulators won't ever fire those either, so testing this particular part is not as easy as the other stuff. However, the example project linked below includes a fake report mechanism that can send the delivery `Intent`s upon receipt of the test messages if it's set up to have the device/emulator send those messages to itself.
Full Example
https://github.com/gonodono/sms-sender
The example is in Kotlin and uses coroutines, Compose, Room, Hilt, WorkManager, and a little (optional) API desugaring for `java.time`. Kotlin, coroutines, and Compose are ready out of the box in the appropriate project templates. The remainder all require additional configuration, and though the example project is already good to go, you might want to check the setup pages to see exactly what went where.
Feature Setup link
Room https://developer.android.com/training/data-storage/room#setup
Hilt https://developer.android.com/training/dependency-injection/hilt-android#setup
WorkManager https://developer.android.com/guide/background/persistent/getting-started
Hilt w/ WorkManager https://developer.android.com/training/dependency-injection/hilt-jetpack#workmanager
API desugaring https://developer.android.com/studio/write/java8-support#library-desugaring
The design is a modern update to the classic pattern that uses a `Service` to send messages queued through a `ContentProvider` or local `SQLiteDatabase`.
The Room database comprises two entities – `Message` and `SendTask` – and their corresponding DAOs. Each DAO has functions for the CRUD operations you would expect, and `MessageDao` also provides a `Flow` on a query for the most recent queued `Message`, which greatly simplifies the send routine.
That routine is executed in a `Worker` which is started immediately for our demonstration, but which could be easily modified to be scheduled for whenever, with whatever constraints are needed.
The actual work for the send is handled in `SmsSendRepository`, and Hilt lets us easily maintain a singleton of that which we can also inject into the statically-registered Receiver for the results, allowing us to keep the overall logic in one place.
The UI is done in minimal Compose, and is basically just text logs with a couple of buttons. The left image shows the middle of a successful send of six test messages. The right image shows the same send attempted again, but with the emulator's airplane mode enabled.
The last thing to note is the fake delivery report mechanism. This only works when the test device is sending to itself. It's nothing more than a regular `BroadcastReceiver` registered for `SMS_RECEIVED` that checks each incoming message against those sent, and upon a match, sends the same kind of broadcast you would get in a real run, complete with valid result PDU attached as an extra.
This was mainly meant to make testing on the emulators more convenient, but it'll work on a real device too, should your particular carrier not provide delivery reports. As is, the app assumes that it'll be running on a single default emulator; i.e., it sends messages to hard-coded port number 5554.
Problem
I want to send sms to multiple people and verify whether sms sent or not. I checked multiple links (mentioned here) and got the idea of using `PendingIntent` and `broadCast Receiver` for confirmation. Practical way to find out if SMS has been sent Sending text messages programmatically in android http://mobiforge.com/design-development/sms-messaging-android But the key problem is that, I have different 50 contacts number in an `arrayList` and their different msgs in another `arrayList`. I use this code : ``` for (Condition) { sms = SmsManager.getDefault(); try { . . . sms.sendTextMessage(phoneNumbers[i], null, messages[i], sentPI, deliveredPI); } catch(IllegalArgumentException e) { } } ``` Now, I can't identify how many people do get their msg and how many don't. Because as shown in post(mentioned link above), every time we just get one msg, "SMS delivered". So please let me know, how can I put "extras" in `Intent`, when I send msg and get the extras from `broadcast Receiver` to get the detail of specific contact/person. One More thing : There are four different option for flag value in `PendingIntent` (`FLAG_ONE_SHOT`, `FLAG_NO_CREATE`, `FLAG_CANCEL_CURRENT`,`FLAG_UPDATE_CURRENT`). Which one should I use when I send messages in for loop for correct result?