Extract code which create notification actions into ActionFactory and disable quick reply and mark as read actions.
This commit is contained in:
parent
65717364ca
commit
cb8d206ed4
7 changed files with 326 additions and 130 deletions
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl
|
||||||
|
|
||||||
|
object NotificationConfig {
|
||||||
|
// TODO EAx Implement and set to true at some point
|
||||||
|
const val supportMarkAsReadAction = false
|
||||||
|
|
||||||
|
// TODO EAx Implement and set to true at some point
|
||||||
|
const val supportQuickReplyAction = false
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,6 @@ import androidx.annotation.DrawableRes
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.RemoteInput
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
|
@ -51,6 +50,10 @@ import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||||
import io.element.android.libraries.push.impl.R
|
import io.element.android.libraries.push.impl.R
|
||||||
import io.element.android.libraries.push.impl.intent.IntentProvider
|
import io.element.android.libraries.push.impl.intent.IntentProvider
|
||||||
|
import io.element.android.libraries.push.impl.notifications.actions.AcceptInvitationActionFactory
|
||||||
|
import io.element.android.libraries.push.impl.notifications.actions.MarkAsReadActionFactory
|
||||||
|
import io.element.android.libraries.push.impl.notifications.actions.QuickReplyActionFactory
|
||||||
|
import io.element.android.libraries.push.impl.notifications.actions.RejectInvitationActionFactory
|
||||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
|
||||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
|
@ -68,6 +71,10 @@ class NotificationUtils @Inject constructor(
|
||||||
private val actionIds: NotificationActionIds,
|
private val actionIds: NotificationActionIds,
|
||||||
private val intentProvider: IntentProvider,
|
private val intentProvider: IntentProvider,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val markAsReadActionFactory: MarkAsReadActionFactory,
|
||||||
|
private val quickReplyActionFactory: QuickReplyActionFactory,
|
||||||
|
private val rejectInvitationActionFactory: RejectInvitationActionFactory,
|
||||||
|
private val acceptInvitationActionFactory: AcceptInvitationActionFactory,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -222,7 +229,6 @@ class NotificationUtils @Inject constructor(
|
||||||
threadId: ThreadId?,
|
threadId: ThreadId?,
|
||||||
largeIcon: Bitmap?,
|
largeIcon: Bitmap?,
|
||||||
lastMessageTimestamp: Long,
|
lastMessageTimestamp: Long,
|
||||||
senderDisplayNameForReplyCompat: String?,
|
|
||||||
tickerText: String
|
tickerText: String
|
||||||
): Notification {
|
): Notification {
|
||||||
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
|
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
|
||||||
|
|
@ -232,6 +238,7 @@ class NotificationUtils @Inject constructor(
|
||||||
true
|
true
|
||||||
/** TODO EAx vectorPreferences.areThreadMessagesEnabled() */
|
/** TODO EAx vectorPreferences.areThreadMessagesEnabled() */
|
||||||
-> buildOpenThreadIntent(roomInfo, threadId)
|
-> buildOpenThreadIntent(roomInfo, threadId)
|
||||||
|
|
||||||
else -> buildOpenRoomIntent(roomInfo.sessionId, roomInfo.roomId)
|
else -> buildOpenRoomIntent(roomInfo.sessionId, roomInfo.roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,43 +295,10 @@ class NotificationUtils @Inject constructor(
|
||||||
|
|
||||||
// Add actions and notification intents
|
// Add actions and notification intents
|
||||||
// Mark room as read
|
// Mark room as read
|
||||||
val markRoomReadIntent = Intent(context, NotificationBroadcastReceiver::class.java)
|
addAction(markAsReadActionFactory.create(roomInfo))
|
||||||
markRoomReadIntent.action = actionIds.markRoomRead
|
|
||||||
markRoomReadIntent.data = createIgnoredUri("markRead?${roomInfo.sessionId}&$${roomInfo.roomId}")
|
|
||||||
markRoomReadIntent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, roomInfo.sessionId)
|
|
||||||
markRoomReadIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomInfo.roomId)
|
|
||||||
val markRoomReadPendingIntent = PendingIntent.getBroadcast(
|
|
||||||
context,
|
|
||||||
clock.epochMillis().toInt(),
|
|
||||||
markRoomReadIntent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
|
|
||||||
NotificationCompat.Action.Builder(
|
|
||||||
R.drawable.ic_material_done_all_white,
|
|
||||||
stringProvider.getString(R.string.notification_room_action_mark_as_read), markRoomReadPendingIntent
|
|
||||||
)
|
|
||||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
|
|
||||||
.setShowsUserInterface(false)
|
|
||||||
.build()
|
|
||||||
.let { addAction(it) }
|
|
||||||
|
|
||||||
// Quick reply
|
// Quick reply
|
||||||
if (!roomInfo.hasSmartReplyError) {
|
if (!roomInfo.hasSmartReplyError) {
|
||||||
buildQuickReplyIntent(roomInfo.sessionId, roomInfo.roomId, threadId, senderDisplayNameForReplyCompat)?.let { replyPendingIntent ->
|
addAction(quickReplyActionFactory.create(roomInfo, threadId))
|
||||||
val remoteInput = RemoteInput.Builder(NotificationBroadcastReceiver.KEY_TEXT_REPLY)
|
|
||||||
.setLabel(stringProvider.getString(R.string.notification_room_action_quick_reply))
|
|
||||||
.build()
|
|
||||||
NotificationCompat.Action.Builder(
|
|
||||||
R.drawable.vector_notification_quick_reply,
|
|
||||||
stringProvider.getString(R.string.notification_room_action_quick_reply), replyPendingIntent
|
|
||||||
)
|
|
||||||
.addRemoteInput(remoteInput)
|
|
||||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
|
||||||
.setShowsUserInterface(false)
|
|
||||||
.build()
|
|
||||||
.let { addAction(it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openIntent != null) {
|
if (openIntent != null) {
|
||||||
|
|
@ -366,45 +340,9 @@ class NotificationUtils @Inject constructor(
|
||||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
|
||||||
.setSmallIcon(smallIcon)
|
.setSmallIcon(smallIcon)
|
||||||
.setColor(accentColor)
|
.setColor(accentColor)
|
||||||
|
.addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent))
|
||||||
|
.addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent))
|
||||||
.apply {
|
.apply {
|
||||||
val roomId = inviteNotifiableEvent.roomId
|
|
||||||
// offer to type a quick reject button
|
|
||||||
val rejectIntent = Intent(context, NotificationBroadcastReceiver::class.java)
|
|
||||||
rejectIntent.action = actionIds.reject
|
|
||||||
rejectIntent.data = createIgnoredUri("rejectInvite?${inviteNotifiableEvent.sessionId}&$roomId")
|
|
||||||
rejectIntent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, inviteNotifiableEvent.sessionId)
|
|
||||||
rejectIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
|
|
||||||
val rejectIntentPendingIntent = PendingIntent.getBroadcast(
|
|
||||||
context,
|
|
||||||
clock.epochMillis().toInt(),
|
|
||||||
rejectIntent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
|
|
||||||
addAction(
|
|
||||||
R.drawable.vector_notification_reject_invitation,
|
|
||||||
stringProvider.getString(R.string.notification_invitation_action_reject),
|
|
||||||
rejectIntentPendingIntent
|
|
||||||
)
|
|
||||||
|
|
||||||
// offer to type a quick accept button
|
|
||||||
val joinIntent = Intent(context, NotificationBroadcastReceiver::class.java)
|
|
||||||
joinIntent.action = actionIds.join
|
|
||||||
joinIntent.data = createIgnoredUri("acceptInvite?${inviteNotifiableEvent.sessionId}&$roomId")
|
|
||||||
joinIntent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, inviteNotifiableEvent.sessionId)
|
|
||||||
joinIntent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
|
|
||||||
val joinIntentPendingIntent = PendingIntent.getBroadcast(
|
|
||||||
context,
|
|
||||||
clock.epochMillis().toInt(),
|
|
||||||
joinIntent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
addAction(
|
|
||||||
R.drawable.vector_notification_accept_invitation,
|
|
||||||
stringProvider.getString(R.string.notification_invitation_action_join),
|
|
||||||
joinIntentPendingIntent
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// Build the pending intent for when the notification is clicked
|
// Build the pending intent for when the notification is clicked
|
||||||
val contentIntent = HomeActivity.newIntent(
|
val contentIntent = HomeActivity.newIntent(
|
||||||
|
|
@ -496,60 +434,6 @@ class NotificationUtils @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Direct reply is new in Android N, and Android already handles the UI, so the right pending intent
|
|
||||||
here will ideally be a Service/IntentService (for a long running background task) or a BroadcastReceiver,
|
|
||||||
which runs on the UI thread. It also works without unlocking, making the process really fluid for the user.
|
|
||||||
However, for Android devices running Marshmallow and below (API level 23 and below),
|
|
||||||
it will be more appropriate to use an activity. Since you have to provide your own UI.
|
|
||||||
*/
|
|
||||||
private fun buildQuickReplyIntent(
|
|
||||||
sessionId: SessionId,
|
|
||||||
roomId: RoomId,
|
|
||||||
threadId: ThreadId?,
|
|
||||||
senderName: String?
|
|
||||||
): PendingIntent? {
|
|
||||||
val intent: Intent
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
intent = Intent(context, NotificationBroadcastReceiver::class.java)
|
|
||||||
intent.action = actionIds.smartReply
|
|
||||||
intent.data = createIgnoredUri("quickReply?$sessionId&$roomId")
|
|
||||||
intent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, sessionId)
|
|
||||||
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
|
|
||||||
threadId?.let {
|
|
||||||
intent.putExtra(NotificationBroadcastReceiver.KEY_THREAD_ID, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
return PendingIntent.getBroadcast(
|
|
||||||
context,
|
|
||||||
clock.epochMillis().toInt(),
|
|
||||||
intent,
|
|
||||||
// PendingIntents attached to actions with remote inputs must be mutable
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
PendingIntent.FLAG_MUTABLE
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
TODO
|
|
||||||
if (!LockScreenActivity.isDisplayingALockScreenActivity()) {
|
|
||||||
// start your activity for Android M and below
|
|
||||||
val quickReplyIntent = Intent(context, LockScreenActivity::class.java)
|
|
||||||
quickReplyIntent.putExtra(LockScreenActivity.EXTRA_ROOM_ID, roomId)
|
|
||||||
quickReplyIntent.putExtra(LockScreenActivity.EXTRA_SENDER_NAME, senderName ?: "")
|
|
||||||
|
|
||||||
// the action must be unique else the parameters are ignored
|
|
||||||
quickReplyIntent.action = QUICK_LAUNCH_ACTION
|
|
||||||
quickReplyIntent.data = createIgnoredUri($roomId")
|
|
||||||
return PendingIntent.getActivity(context, 0, quickReplyIntent, PendingIntentCompat.FLAG_IMMUTABLE)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the summary notification.
|
* Build the summary notification.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,6 @@ class RoomGroupMessageCreator @Inject constructor(
|
||||||
threadId = lastKnownRoomEvent.threadId,
|
threadId = lastKnownRoomEvent.threadId,
|
||||||
largeIcon = largeBitmap,
|
largeIcon = largeBitmap,
|
||||||
lastMessageTimestamp,
|
lastMessageTimestamp,
|
||||||
userDisplayName,
|
|
||||||
tickerText
|
tickerText
|
||||||
),
|
),
|
||||||
meta
|
meta
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.notifications.actions
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import io.element.android.libraries.androidutils.uri.createIgnoredUri
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.push.impl.R
|
||||||
|
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
|
||||||
|
import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver
|
||||||
|
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AcceptInvitationActionFactory @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val actionIds: NotificationActionIds,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val clock: SystemClock,
|
||||||
|
) {
|
||||||
|
// offer to type a quick accept button
|
||||||
|
fun create(inviteNotifiableEvent: InviteNotifiableEvent): NotificationCompat.Action {
|
||||||
|
val sessionId = inviteNotifiableEvent.sessionId
|
||||||
|
val roomId = inviteNotifiableEvent.roomId
|
||||||
|
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
|
||||||
|
intent.action = actionIds.join
|
||||||
|
intent.data = createIgnoredUri("acceptInvite?${sessionId.value}&${roomId.value}")
|
||||||
|
intent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, sessionId)
|
||||||
|
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
clock.epochMillis().toInt(),
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
return NotificationCompat.Action.Builder(
|
||||||
|
R.drawable.vector_notification_accept_invitation,
|
||||||
|
stringProvider.getString(R.string.notification_invitation_action_join),
|
||||||
|
pendingIntent
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.notifications.actions
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import io.element.android.libraries.androidutils.uri.createIgnoredUri
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.push.impl.NotificationConfig
|
||||||
|
import io.element.android.libraries.push.impl.R
|
||||||
|
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
|
||||||
|
import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver
|
||||||
|
import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MarkAsReadActionFactory @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val actionIds: NotificationActionIds,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val clock: SystemClock,
|
||||||
|
) {
|
||||||
|
fun create(roomInfo: RoomEventGroupInfo): NotificationCompat.Action? {
|
||||||
|
if (!NotificationConfig.supportMarkAsReadAction) return null
|
||||||
|
val sessionId = roomInfo.sessionId
|
||||||
|
val roomId = roomInfo.roomId
|
||||||
|
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
|
||||||
|
intent.action = actionIds.markRoomRead
|
||||||
|
intent.data = createIgnoredUri("markRead?${sessionId.value}&$${roomId.value}")
|
||||||
|
intent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, sessionId)
|
||||||
|
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
clock.epochMillis().toInt(),
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
return NotificationCompat.Action.Builder(
|
||||||
|
R.drawable.ic_material_done_all_white,
|
||||||
|
stringProvider.getString(R.string.notification_room_action_mark_as_read),
|
||||||
|
pendingIntent
|
||||||
|
)
|
||||||
|
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
|
||||||
|
.setShowsUserInterface(false)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.notifications.actions
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.RemoteInput
|
||||||
|
import io.element.android.libraries.androidutils.uri.createIgnoredUri
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||||
|
import io.element.android.libraries.push.impl.NotificationConfig
|
||||||
|
import io.element.android.libraries.push.impl.R
|
||||||
|
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
|
||||||
|
import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver
|
||||||
|
import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class QuickReplyActionFactory @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val actionIds: NotificationActionIds,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val clock: SystemClock,
|
||||||
|
) {
|
||||||
|
fun create(roomInfo: RoomEventGroupInfo, threadId: ThreadId?): NotificationCompat.Action? {
|
||||||
|
if (!NotificationConfig.supportQuickReplyAction) return null
|
||||||
|
val sessionId = roomInfo.sessionId
|
||||||
|
val roomId = roomInfo.roomId
|
||||||
|
return buildQuickReplyIntent(sessionId, roomId, threadId)?.let { replyPendingIntent ->
|
||||||
|
val remoteInput = RemoteInput.Builder(NotificationBroadcastReceiver.KEY_TEXT_REPLY)
|
||||||
|
.setLabel(stringProvider.getString(R.string.notification_room_action_quick_reply))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
NotificationCompat.Action.Builder(
|
||||||
|
R.drawable.vector_notification_quick_reply,
|
||||||
|
stringProvider.getString(R.string.notification_room_action_quick_reply),
|
||||||
|
replyPendingIntent
|
||||||
|
)
|
||||||
|
.addRemoteInput(remoteInput)
|
||||||
|
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||||
|
.setShowsUserInterface(false)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Direct reply is new in Android N, and Android already handles the UI, so the right pending intent
|
||||||
|
* here will ideally be a Service/IntentService (for a long running background task) or a BroadcastReceiver,
|
||||||
|
* which runs on the UI thread. It also works without unlocking, making the process really fluid for the user.
|
||||||
|
* However, for Android devices running Marshmallow and below (API level 23 and below),
|
||||||
|
* it will be more appropriate to use an activity. Since you have to provide your own UI.
|
||||||
|
*/
|
||||||
|
private fun buildQuickReplyIntent(
|
||||||
|
sessionId: SessionId,
|
||||||
|
roomId: RoomId,
|
||||||
|
threadId: ThreadId?,
|
||||||
|
): PendingIntent? {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
|
||||||
|
intent.action = actionIds.smartReply
|
||||||
|
intent.data = createIgnoredUri("quickReply?${sessionId.value}&${roomId.value}" + threadId?.let { "&${it.value}" }.orEmpty())
|
||||||
|
intent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, sessionId)
|
||||||
|
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
|
||||||
|
threadId?.let {
|
||||||
|
intent.putExtra(NotificationBroadcastReceiver.KEY_THREAD_ID, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
clock.epochMillis().toInt(),
|
||||||
|
intent,
|
||||||
|
// PendingIntents attached to actions with remote inputs must be mutable
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
PendingIntent.FLAG_MUTABLE
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.notifications.actions
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import io.element.android.libraries.androidutils.uri.createIgnoredUri
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.push.impl.R
|
||||||
|
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
|
||||||
|
import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver
|
||||||
|
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class RejectInvitationActionFactory @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val actionIds: NotificationActionIds,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val clock: SystemClock,
|
||||||
|
) {
|
||||||
|
fun create(inviteNotifiableEvent: InviteNotifiableEvent): NotificationCompat.Action? {
|
||||||
|
val sessionId = inviteNotifiableEvent.sessionId
|
||||||
|
val roomId = inviteNotifiableEvent.roomId
|
||||||
|
val intent = Intent(context, NotificationBroadcastReceiver::class.java)
|
||||||
|
intent.action = actionIds.reject
|
||||||
|
intent.data = createIgnoredUri("rejectInvite?${sessionId.value}&${roomId.value}")
|
||||||
|
intent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, sessionId)
|
||||||
|
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
clock.epochMillis().toInt(),
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
return NotificationCompat.Action.Builder(
|
||||||
|
R.drawable.vector_notification_reject_invitation,
|
||||||
|
stringProvider.getString(R.string.notification_invitation_action_reject),
|
||||||
|
pendingIntent
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue