mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Updated notifications manager to allow mark as read & reply on chat messages notifications
This commit is contained in:
parent
686503c83c
commit
c314c4cba3
5 changed files with 231 additions and 3 deletions
|
|
@ -19,6 +19,8 @@
|
|||
*/
|
||||
package org.linphone.notifications
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.app.RemoteInput
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
|
@ -39,6 +41,8 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
|||
|
||||
if (intent.action == NotificationsManager.INTENT_ANSWER_CALL_NOTIF_ACTION || intent.action == NotificationsManager.INTENT_HANGUP_CALL_NOTIF_ACTION) {
|
||||
handleCallIntent(intent)
|
||||
} else if (intent.action == NotificationsManager.INTENT_REPLY_MESSAGE_NOTIF_ACTION || intent.action == NotificationsManager.INTENT_MARK_MESSAGE_AS_READ_NOTIF_ACTION) {
|
||||
handleChatIntent(context, intent, notificationId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,4 +72,79 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleChatIntent(context: Context, intent: Intent, notificationId: Int) {
|
||||
val remoteSipAddress = intent.getStringExtra(NotificationsManager.INTENT_REMOTE_ADDRESS)
|
||||
if (remoteSipAddress == null) {
|
||||
Log.e(
|
||||
"$TAG Remote SIP address is null for notification id $notificationId"
|
||||
)
|
||||
return
|
||||
}
|
||||
val localIdentity = intent.getStringExtra(NotificationsManager.INTENT_LOCAL_IDENTITY)
|
||||
if (localIdentity == null) {
|
||||
Log.e(
|
||||
"$TAG Local identity is null for notification id $notificationId"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val reply = getMessageText(intent)?.toString()
|
||||
if (intent.action == NotificationsManager.INTENT_REPLY_MESSAGE_NOTIF_ACTION) {
|
||||
if (reply == null) {
|
||||
Log.e("$TAG Couldn't get reply text")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
coreContext.postOnCoreThread { core ->
|
||||
val remoteAddress = core.interpretUrl(remoteSipAddress, false)
|
||||
if (remoteAddress == null) {
|
||||
Log.e(
|
||||
"$TAG Couldn't interpret remote address $remoteSipAddress"
|
||||
)
|
||||
return@postOnCoreThread
|
||||
}
|
||||
|
||||
val localAddress = core.interpretUrl(localIdentity, false)
|
||||
if (localAddress == null) {
|
||||
Log.e(
|
||||
"$TAG Couldn't interpret local address $localIdentity"
|
||||
)
|
||||
return@postOnCoreThread
|
||||
}
|
||||
|
||||
val room = core.searchChatRoom(null, localAddress, remoteAddress, arrayOfNulls(0))
|
||||
if (room == null) {
|
||||
Log.e(
|
||||
"$TAG Couldn't find chat room for remote address $remoteSipAddress and local address $localIdentity"
|
||||
)
|
||||
return@postOnCoreThread
|
||||
}
|
||||
|
||||
if (intent.action == NotificationsManager.INTENT_REPLY_MESSAGE_NOTIF_ACTION) {
|
||||
val msg = room.createMessageFromUtf8(reply)
|
||||
msg.userData = notificationId
|
||||
msg.addListener(coreContext.notificationsManager.chatListener)
|
||||
msg.send()
|
||||
Log.i("$TAG Reply sent for notif id $notificationId")
|
||||
} else if (intent.action == NotificationsManager.INTENT_MARK_MESSAGE_AS_READ_NOTIF_ACTION) {
|
||||
room.markAsRead()
|
||||
if (!coreContext.notificationsManager.dismissChatNotification(room)) {
|
||||
Log.w(
|
||||
"$TAG Notifications Manager failed to cancel notification"
|
||||
)
|
||||
val notificationManager = context.getSystemService(
|
||||
NotificationManager::class.java
|
||||
)
|
||||
notificationManager.cancel(NotificationsManager.CHAT_TAG, notificationId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMessageText(intent: Intent): CharSequence? {
|
||||
val remoteInput = RemoteInput.getResultsFromIntent(intent)
|
||||
return remoteInput?.getCharSequence(NotificationsManager.KEY_TEXT_REPLY)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import androidx.core.app.ActivityCompat
|
|||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.Person
|
||||
import androidx.core.app.RemoteInput
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.LocusIdCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
|
|
@ -48,6 +49,8 @@ import org.linphone.contacts.getPerson
|
|||
import org.linphone.core.Address
|
||||
import org.linphone.core.Call
|
||||
import org.linphone.core.ChatMessage
|
||||
import org.linphone.core.ChatMessageListener
|
||||
import org.linphone.core.ChatMessageListenerStub
|
||||
import org.linphone.core.ChatMessageReaction
|
||||
import org.linphone.core.ChatRoom
|
||||
import org.linphone.core.Core
|
||||
|
|
@ -66,10 +69,16 @@ class NotificationsManager @MainThread constructor(private val context: Context)
|
|||
|
||||
const val INTENT_HANGUP_CALL_NOTIF_ACTION = "org.linphone.HANGUP_CALL_ACTION"
|
||||
const val INTENT_ANSWER_CALL_NOTIF_ACTION = "org.linphone.ANSWER_CALL_ACTION"
|
||||
const val INTENT_REPLY_MESSAGE_NOTIF_ACTION = "org.linphone.REPLY_ACTION"
|
||||
const val INTENT_MARK_MESSAGE_AS_READ_NOTIF_ACTION = "org.linphone.MARK_AS_READ_ACTION"
|
||||
|
||||
const val INTENT_CALL_ID = "CALL_ID"
|
||||
const val INTENT_NOTIF_ID = "NOTIFICATION_ID"
|
||||
|
||||
const val KEY_TEXT_REPLY = "key_text_reply"
|
||||
const val INTENT_LOCAL_IDENTITY = "LOCAL_IDENTITY"
|
||||
const val INTENT_REMOTE_ADDRESS = "REMOTE_ADDRESS"
|
||||
|
||||
const val CHAT_TAG = "Chat"
|
||||
const val CHAT_NOTIFICATIONS_GROUP = "CHAT_NOTIF_GROUP"
|
||||
}
|
||||
|
|
@ -220,6 +229,37 @@ class NotificationsManager @MainThread constructor(private val context: Context)
|
|||
}
|
||||
}
|
||||
|
||||
val chatListener: ChatMessageListener = object : ChatMessageListenerStub() {
|
||||
@WorkerThread
|
||||
override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State) {
|
||||
message.userData ?: return
|
||||
val id = message.userData as Int
|
||||
Log.i("$TAG Reply message state changed [$state] for id $id")
|
||||
|
||||
if (state != ChatMessage.State.InProgress) {
|
||||
// No need to be called here twice
|
||||
message.removeListener(this)
|
||||
}
|
||||
|
||||
if (state == ChatMessage.State.Delivered || state == ChatMessage.State.Displayed) {
|
||||
val address = message.chatRoom.peerAddress.asStringUriOnly()
|
||||
val notifiable = chatNotificationsMap[address]
|
||||
if (notifiable != null) {
|
||||
if (notifiable.notificationId != id) {
|
||||
Log.w("$TAG ID doesn't match: ${notifiable.notificationId} != $id")
|
||||
}
|
||||
displayReplyMessageNotification(message, notifiable)
|
||||
} else {
|
||||
Log.e("$TAG Couldn't find notification for chat room $address")
|
||||
cancelNotification(id, CHAT_TAG)
|
||||
}
|
||||
} else if (state == ChatMessage.State.NotDelivered) {
|
||||
Log.e("$TAG Reply wasn't delivered")
|
||||
cancelNotification(id, CHAT_TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var coreService: CoreForegroundService? = null
|
||||
|
||||
private val callNotificationsMap: HashMap<String, Notifiable> = HashMap()
|
||||
|
|
@ -448,7 +488,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
|
|||
val displayName = contact?.name ?: LinphoneUtils.getDisplayName(address)
|
||||
|
||||
val originalMessage = getTextDescribingMessage(message)
|
||||
val text = AppUtils.getString(R.string.chat_message_reaction_received).format(
|
||||
val text = AppUtils.getString(R.string.notification_chat_message_reaction_received).format(
|
||||
displayName,
|
||||
reaction,
|
||||
originalMessage
|
||||
|
|
@ -737,6 +777,8 @@ class NotificationsManager @MainThread constructor(private val context: Context)
|
|||
.setWhen(System.currentTimeMillis())
|
||||
.setShowWhen(true)
|
||||
.setStyle(style)
|
||||
.addAction(getReplyMessageAction(notifiable))
|
||||
.addAction(getMarkMessageAsReadAction(notifiable))
|
||||
.setShortcutId(id)
|
||||
.setLocusId(LocusIdCompat(id))
|
||||
|
||||
|
|
@ -815,6 +857,94 @@ class NotificationsManager @MainThread constructor(private val context: Context)
|
|||
)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun displayReplyMessageNotification(message: ChatMessage, notifiable: Notifiable) {
|
||||
Log.i(
|
||||
"$TAG Updating message notification with reply for notification ${notifiable.notificationId}"
|
||||
)
|
||||
|
||||
val text = message.contents.find { content -> content.isText }?.utf8Text ?: ""
|
||||
val senderAddress = message.fromAddress
|
||||
val reply = NotifiableMessage(
|
||||
text,
|
||||
null,
|
||||
notifiable.myself ?: LinphoneUtils.getDisplayName(senderAddress),
|
||||
System.currentTimeMillis(),
|
||||
isOutgoing = true
|
||||
)
|
||||
notifiable.messages.add(reply)
|
||||
|
||||
val chatRoom = message.chatRoom
|
||||
val me = coreContext.contactsManager.getMePerson(chatRoom.localAddress)
|
||||
val notification = createMessageNotification(
|
||||
notifiable,
|
||||
LinphoneUtils.getChatRoomId(chatRoom),
|
||||
me
|
||||
)
|
||||
notify(notifiable.notificationId, notification, CHAT_TAG)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
private fun getReplyMessageAction(notifiable: Notifiable): NotificationCompat.Action {
|
||||
val replyLabel =
|
||||
context.resources.getString(R.string.notification_reply_to_message)
|
||||
val remoteInput =
|
||||
RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(replyLabel).build()
|
||||
|
||||
val replyIntent = Intent(context, NotificationBroadcastReceiver::class.java)
|
||||
replyIntent.action = INTENT_REPLY_MESSAGE_NOTIF_ACTION
|
||||
replyIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId)
|
||||
replyIntent.putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity)
|
||||
replyIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress)
|
||||
|
||||
// PendingIntents attached to actions with remote inputs must be mutable
|
||||
val replyPendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
notifiable.notificationId,
|
||||
replyIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
return NotificationCompat.Action.Builder(
|
||||
R.drawable.paper_plane_tilt,
|
||||
context.getString(R.string.notification_reply_to_message),
|
||||
replyPendingIntent
|
||||
)
|
||||
.addRemoteInput(remoteInput)
|
||||
.setAllowGeneratedReplies(true)
|
||||
.setShowsUserInterface(false)
|
||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||
.build()
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
private fun getMarkMessageAsReadPendingIntent(notifiable: Notifiable): PendingIntent {
|
||||
val markAsReadIntent = Intent(context, NotificationBroadcastReceiver::class.java)
|
||||
markAsReadIntent.action = INTENT_MARK_MESSAGE_AS_READ_NOTIF_ACTION
|
||||
markAsReadIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId)
|
||||
markAsReadIntent.putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity)
|
||||
markAsReadIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress)
|
||||
|
||||
return PendingIntent.getBroadcast(
|
||||
context,
|
||||
notifiable.notificationId,
|
||||
markAsReadIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
private fun getMarkMessageAsReadAction(notifiable: Notifiable): NotificationCompat.Action {
|
||||
val markAsReadPendingIntent = getMarkMessageAsReadPendingIntent(notifiable)
|
||||
return NotificationCompat.Action.Builder(
|
||||
R.drawable.envelope_simple_open,
|
||||
context.getString(R.string.notification_mark_message_as_read),
|
||||
markAsReadPendingIntent
|
||||
)
|
||||
.setShowsUserInterface(false)
|
||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
|
||||
.build()
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getPerson(friend: Friend?, displayName: String, picture: Bitmap?): Person {
|
||||
return friend?.getPerson()
|
||||
|
|
|
|||
9
app/src/main/res/drawable/envelope_simple_open.xml
Normal file
9
app/src/main/res/drawable/envelope_simple_open.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path
|
||||
android:pathData="M228.44,89.34l-96,-64a8,8 0,0 0,-8.88 0l-96,64A8,8 0,0 0,24 96V200a16,16 0,0 0,16 16H216a16,16 0,0 0,16 -16V96A8,8 0,0 0,228.44 89.34ZM128,41.61l81.91,54.61 -67,47.78H113.11l-67,-47.78ZM40,200V111.53l65.9,47a8,8 0,0 0,4.65 1.49h34.9a8,8 0,0 0,4.65 -1.49l65.9,-47V200Z"
|
||||
android:fillColor="#4e6074"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/paper_plane_tilt.xml
Normal file
9
app/src/main/res/drawable/paper_plane_tilt.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path
|
||||
android:pathData="M227.32,28.68a16,16 0,0 0,-15.66 -4.08l-0.15,0L19.57,82.84a16,16 0,0 0,-2.42 29.84l85.62,40.55 40.55,85.62A15.86,15.86 0,0 0,157.74 248q0.69,0 1.38,-0.06a15.88,15.88 0,0 0,14 -11.51l58.2,-191.94c0,-0.05 0,-0.1 0,-0.15A16,16 0,0 0,227.32 28.68ZM157.83,231.85l-0.05,0.14L118.42,148.9l47.24,-47.25a8,8 0,0 0,-11.31 -11.31L107.1,137.58 24,98.22l0.14,0L216,40Z"
|
||||
android:fillColor="#4e6074"/>
|
||||
</vector>
|
||||
|
|
@ -43,6 +43,9 @@
|
|||
<string name="notification_channel_incoming_call_name">&appName; incoming calls notifications</string>
|
||||
<string name="notification_channel_service_name">&appName; service notification</string>
|
||||
<string name="notification_channel_chat_name">&appName; instant messages notifications</string>
|
||||
<string name="notification_chat_message_reaction_received">%s has reacted by %s to: %s</string>
|
||||
<string name="notification_mark_message_as_read">Mark as read</string>
|
||||
<string name="notification_reply_to_message">Reply</string>
|
||||
|
||||
<string name="bottom_navigation_contacts_label">Contacts</string>
|
||||
<string name="bottom_navigation_calls_label">Calls</string>
|
||||
|
|
@ -286,8 +289,6 @@
|
|||
<string name="voip_call_state_connected">Active</string>
|
||||
<string name="voip_call_state_paused">Paused</string>
|
||||
<string name="voip_call_state_ended">Ended</string>
|
||||
|
||||
<string name="chat_message_reaction_received">%s has reacted by %s to: %s</string>
|
||||
|
||||
<!-- Keep <u></u> in following strings translations! -->
|
||||
<string name="welcome_carousel_skip"><u>Skip</u></string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue