Backup+restore, Firebase, formatting, custom intent action

This commit is contained in:
Philipp Heckel 2022-04-19 19:20:39 -04:00
parent 79c0e91e8d
commit 2625513216
11 changed files with 173 additions and 56 deletions

View file

@ -2,6 +2,7 @@ package io.heckel.ntfy.backup
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.room.ColumnInfo
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
@ -109,6 +110,25 @@ class Backuper(val context: Context) {
} }
notifications.forEach { n -> notifications.forEach { n ->
try { try {
val actions = if (n.actions != null) {
n.actions.map { a ->
io.heckel.ntfy.db.Action(
id = a.id,
action = a.action,
label = a.label,
url = a.url,
method = a.method,
headers = a.headers,
body = a.body,
intent = a.intent,
extras = a.extras,
progress = a.progress,
error = a.error
)
}
} else {
null
}
val attachment = if (n.attachment != null) { val attachment = if (n.attachment != null) {
io.heckel.ntfy.db.Attachment( io.heckel.ntfy.db.Attachment(
name = n.attachment.name, name = n.attachment.name,
@ -133,7 +153,7 @@ class Backuper(val context: Context) {
priority = n.priority, priority = n.priority,
tags = n.tags, tags = n.tags,
click = n.click, click = n.click,
actions = null, // FIXME actions = actions,
attachment = attachment, attachment = attachment,
deleted = n.deleted deleted = n.deleted
)) ))
@ -202,6 +222,25 @@ class Backuper(val context: Context) {
private suspend fun createNotificationList(): List<Notification> { private suspend fun createNotificationList(): List<Notification> {
return repository.getNotifications().map { n -> return repository.getNotifications().map { n ->
val actions = if (n.actions != null) {
n.actions.map { a ->
Action(
id = a.id,
action = a.action,
label = a.label,
url = a.url,
method = a.method,
headers = a.headers,
body = a.body,
intent = a.intent,
extras = a.extras,
progress = a.progress,
error = a.error
)
}
} else {
null
}
val attachment = if (n.attachment != null) { val attachment = if (n.attachment != null) {
Attachment( Attachment(
name = n.attachment.name, name = n.attachment.name,
@ -225,6 +264,7 @@ class Backuper(val context: Context) {
priority = n.priority, priority = n.priority,
tags = n.tags, tags = n.tags,
click = n.click, click = n.click,
actions = actions,
attachment = attachment, attachment = attachment,
deleted = n.deleted deleted = n.deleted
) )
@ -291,10 +331,25 @@ data class Notification(
val priority: Int, // 1=min, 3=default, 5=max val priority: Int, // 1=min, 3=default, 5=max
val tags: String, val tags: String,
val click: String, // URL/intent to open on notification click val click: String, // URL/intent to open on notification click
val actions: List<Action>?,
val attachment: Attachment?, val attachment: Attachment?,
val deleted: Boolean val deleted: Boolean
) )
data class Action(
val id: String, // Synthetic ID to identify result, and easily pass via Broadcast and WorkManager
val action: String, // "view", "http" or "broadcast"
val label: String,
val url: String?, // used in "view" and "http" actions
val method: String?, // used in "http" action
val headers: Map<String,String>?, // used in "http" action
val body: String?, // used in "http" action
val intent: String?, // used in "broadcast" action
val extras: Map<String,String>?, // used in "broadcast" action
val progress: Int?, // used to indicate progress in popup
val error: String? // used to indicate errors in popup
)
data class Attachment( data class Attachment(
val name: String, // Filename val name: String, // Filename
val type: String?, // MIME type val type: String?, // MIME type
@ -305,7 +360,6 @@ data class Attachment(
val progress: Int, // Progress during download, -1 if not downloaded val progress: Int, // Progress during download, -1 if not downloaded
) )
data class User( data class User(
val baseUrl: String, val baseUrl: String,
val username: String, val username: String,

View file

@ -91,6 +91,7 @@ data class Action(
@ColumnInfo(name = "method") val method: String?, // used in "http" action @ColumnInfo(name = "method") val method: String?, // used in "http" action
@ColumnInfo(name = "headers") val headers: Map<String,String>?, // used in "http" action @ColumnInfo(name = "headers") val headers: Map<String,String>?, // used in "http" action
@ColumnInfo(name = "body") val body: String?, // used in "http" action @ColumnInfo(name = "body") val body: String?, // used in "http" action
@ColumnInfo(name = "intent") val intent: String?, // used in "broadcast" action
@ColumnInfo(name = "extras") val extras: Map<String,String>?, // used in "broadcast" action @ColumnInfo(name = "extras") val extras: Map<String,String>?, // used in "broadcast" action
@ColumnInfo(name = "progress") val progress: Int?, // used to indicate progress in popup @ColumnInfo(name = "progress") val progress: Int?, // used to indicate progress in popup
@ColumnInfo(name = "error") val error: String?, // used to indicate errors in popup @ColumnInfo(name = "error") val error: String?, // used to indicate errors in popup

View file

@ -34,17 +34,17 @@ class BroadcastService(private val ctx: Context) {
intent.putExtra("muted", muted) intent.putExtra("muted", muted)
intent.putExtra("muted_str", muted.toString()) intent.putExtra("muted_str", muted.toString())
Log.d(TAG, "Sending message intent broadcast: $intent") Log.d(TAG, "Sending message intent broadcast: ${intent.action} with extras ${intent.extras}")
ctx.sendBroadcast(intent) ctx.sendBroadcast(intent)
} }
fun sendUserAction(action: Action) { fun sendUserAction(action: Action) {
val intent = Intent() val intent = Intent()
intent.action = USER_ACTION_ACTION intent.action = action.intent ?: USER_ACTION_ACTION
action.extras?.forEach { (key, value) -> action.extras?.forEach { (key, value) ->
intent.putExtra(key, value) intent.putExtra(key, value)
} }
Log.d(TAG, "Sending user action intent broadcast: $intent") Log.d(TAG, "Sending user action intent broadcast: ${intent.action} with extras ${intent.extras}")
ctx.sendBroadcast(intent) ctx.sendBroadcast(intent)
} }

View file

@ -35,11 +35,12 @@ data class MessageAction(
val id: String, val id: String,
val action: String, val action: String,
val label: String, // "view", "broadcast" or "http" val label: String, // "view", "broadcast" or "http"
val url: String?, // used in "view" and "http" val url: String?, // used in "view" and "http" actions
val method: String?, // used in "http", default is POST (!) val method: String?, // used in "http" action, default is POST (!)
val headers: Map<String,String>?, // used in "http" val headers: Map<String,String>?, // used in "http" action
val body: String?, // used in "http" val body: String?, // used in "http" action
val extras: Map<String,String>?, // used in "broadcast" val intent: String?, // used in "broadcast" action
val extras: Map<String,String>?, // used in "broadcast" action
) )
const val MESSAGE_ENCODING_BASE64 = "base64" const val MESSAGE_ENCODING_BASE64 = "base64"

View file

@ -1,13 +1,13 @@
package io.heckel.ntfy.msg package io.heckel.ntfy.msg
import android.util.Base64
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import io.heckel.ntfy.db.Action import io.heckel.ntfy.db.Action
import io.heckel.ntfy.db.Attachment import io.heckel.ntfy.db.Attachment
import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.util.joinTags import io.heckel.ntfy.util.joinTags
import io.heckel.ntfy.util.randomString
import io.heckel.ntfy.util.toPriority import io.heckel.ntfy.util.toPriority
import java.lang.reflect.Type
class NotificationParser { class NotificationParser {
private val gson = Gson() private val gson = Gson()
@ -33,7 +33,7 @@ class NotificationParser {
} else null } else null
val actions = if (message.actions != null) { val actions = if (message.actions != null) {
message.actions.map { a -> message.actions.map { a ->
Action(a.id, a.action, a.label, a.url, a.method, a.headers, a.body, a.extras, null, null) Action(a.id, a.action, a.label, a.url, a.method, a.headers, a.body, a.intent, a.extras, null, null)
} }
} else null } else null
val notification = Notification( val notification = Notification(
@ -54,5 +54,17 @@ class NotificationParser {
return NotificationWithTopic(message.topic, notification) return NotificationWithTopic(message.topic, notification)
} }
/**
* Parse JSON array to Action list. The indirection via MessageAction is probably
* not necessary, but for "good form".
*/
fun parseActions(s: String?): List<Action>? {
val listType: Type = object : TypeToken<List<MessageAction>?>() {}.type
val messageActions: List<MessageAction>? = gson.fromJson(s, listType)
return messageActions?.map { a ->
Action(a.id, a.action, a.label, a.url, a.method, a.headers, a.body, a.intent, a.extras, null, null)
}
}
data class NotificationWithTopic(val topic: String, val notification: Notification) data class NotificationWithTopic(val topic: String, val notification: Notification)
} }

View file

@ -129,18 +129,6 @@ class NotificationService(val context: Context) {
return context.getString(R.string.notification_popup_file, message, attachmentInfos) return context.getString(R.string.notification_popup_file, message, attachmentInfos)
} }
private fun maybeAppendActionErrors(message: String, notification: Notification): String {
val actionErrors = notification.actions
.orEmpty()
.mapNotNull { action -> action.error }
.joinToString("\n")
if (actionErrors.isEmpty()) {
return message
} else {
return "${message}\n\n${actionErrors}"
}
}
private fun setClickAction(builder: NotificationCompat.Builder, subscription: Subscription, notification: Notification) { private fun setClickAction(builder: NotificationCompat.Builder, subscription: Subscription, notification: Notification) {
if (notification.click == "") { if (notification.click == "") {
builder.setContentIntent(detailActivityIntent(subscription)) builder.setContentIntent(detailActivityIntent(subscription))
@ -218,6 +206,10 @@ class NotificationService(val context: Context) {
} }
private fun maybeAddViewUserAction(builder: NotificationCompat.Builder, action: Action) { private fun maybeAddViewUserAction(builder: NotificationCompat.Builder, action: Action) {
// Note that this function is (almost) duplicated in DetailAdapter, since we need to be able
// to open a link from the detail activity as well. We can't do this in the UserActionWorker,
// because the behavior is kind of weird in Android.
try { try {
val url = action.url ?: return val url = action.url ?: return
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
@ -237,12 +229,7 @@ class NotificationService(val context: Context) {
putExtra(BROADCAST_EXTRA_ACTION_ID, action.id) putExtra(BROADCAST_EXTRA_ACTION_ID, action.id)
} }
val pendingIntent = PendingIntent.getBroadcast(context, Random().nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) val pendingIntent = PendingIntent.getBroadcast(context, Random().nextInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val label = when (action.progress) { val label = formatActionLabel(action)
ACTION_PROGRESS_ONGOING -> action.label + ""
ACTION_PROGRESS_SUCCESS -> action.label + " ✔️"
ACTION_PROGRESS_FAILED -> action.label + " ❌️"
else -> action.label
}
builder.addAction(NotificationCompat.Action.Builder(0, label, pendingIntent).build()) builder.addAction(NotificationCompat.Action.Builder(0, label, pendingIntent).build())
} }
@ -322,20 +309,20 @@ class NotificationService(val context: Context) {
} }
companion object { companion object {
val ACTION_VIEW = "view" const val ACTION_VIEW = "view"
val ACTION_HTTP = "http" const val ACTION_HTTP = "http"
val ACTION_BROADCAST = "broadcast" const val ACTION_BROADCAST = "broadcast"
const val BROADCAST_EXTRA_TYPE = "type"
const val BROADCAST_EXTRA_NOTIFICATION_ID = "notificationId"
const val BROADCAST_EXTRA_ACTION_ID = "action"
const val BROADCAST_TYPE_DOWNLOAD_START = "io.heckel.ntfy.DOWNLOAD_ACTION_START"
const val BROADCAST_TYPE_DOWNLOAD_CANCEL = "io.heckel.ntfy.DOWNLOAD_ACTION_CANCEL"
const val BROADCAST_TYPE_USER_ACTION = "io.heckel.ntfy.USER_ACTION_RUN"
private const val TAG = "NtfyNotifService" private const val TAG = "NtfyNotifService"
private const val BROADCAST_EXTRA_TYPE = "type"
private const val BROADCAST_EXTRA_NOTIFICATION_ID = "notificationId"
private const val BROADCAST_EXTRA_ACTION_ID = "action"
private const val BROADCAST_TYPE_DOWNLOAD_START = "io.heckel.ntfy.DOWNLOAD_ACTION_START"
private const val BROADCAST_TYPE_DOWNLOAD_CANCEL = "io.heckel.ntfy.DOWNLOAD_ACTION_CANCEL"
private const val BROADCAST_TYPE_USER_ACTION = "io.heckel.ntfy.USER_ACTION_RUN"
private const val CHANNEL_ID_MIN = "ntfy-min" private const val CHANNEL_ID_MIN = "ntfy-min"
private const val CHANNEL_ID_LOW = "ntfy-low" private const val CHANNEL_ID_LOW = "ntfy-low"
private const val CHANNEL_ID_DEFAULT = "ntfy" private const val CHANNEL_ID_DEFAULT = "ntfy"

View file

@ -1,6 +1,8 @@
package io.heckel.ntfy.msg package io.heckel.ntfy.msg
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import io.heckel.ntfy.R import io.heckel.ntfy.R
@ -8,6 +10,7 @@ import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.* import io.heckel.ntfy.db.*
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_BROADCAST import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_BROADCAST
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_HTTP import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_HTTP
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.Log
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -43,8 +46,11 @@ class UserActionWorker(private val context: Context, params: WorkerParameters) :
Log.d(TAG, "Executing action $action for notification $notification") Log.d(TAG, "Executing action $action for notification $notification")
try { try {
when (action.action) { when (action.action) {
ACTION_HTTP -> performHttpAction(action) // ACTION_VIEW is not handled here. It has to be handled in the foreground to avoid
// weird Android behavior.
ACTION_BROADCAST -> performBroadcastAction(action) ACTION_BROADCAST -> performBroadcastAction(action)
ACTION_HTTP -> performHttpAction(action)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Error executing action: ${e.message}", e) Log.w(TAG, "Error executing action: ${e.message}", e)
@ -56,6 +62,11 @@ class UserActionWorker(private val context: Context, params: WorkerParameters) :
return Result.success() return Result.success()
} }
private fun performBroadcastAction(action: Action) {
broadcaster.sendUserAction(action)
save(action.copy(progress = ACTION_PROGRESS_SUCCESS, error = null))
}
private fun performHttpAction(action: Action) { private fun performHttpAction(action: Action) {
save(action.copy(progress = ACTION_PROGRESS_ONGOING, error = null)) save(action.copy(progress = ACTION_PROGRESS_ONGOING, error = null))
@ -81,11 +92,6 @@ class UserActionWorker(private val context: Context, params: WorkerParameters) :
} }
} }
private fun performBroadcastAction(action: Action) {
broadcaster.sendUserAction(action)
save(action.copy(progress = ACTION_PROGRESS_SUCCESS, error = null))
}
private fun save(newAction: Action) { private fun save(newAction: Action) {
Log.d(TAG, "Updating action: $newAction") Log.d(TAG, "Updating action: $newAction")
val newActions = notification.actions?.map { a -> if (a.id == newAction.id) newAction else a } val newActions = notification.actions?.map { a -> if (a.id == newAction.id) newAction else a }

View file

@ -25,6 +25,8 @@ import io.heckel.ntfy.R
import io.heckel.ntfy.db.* import io.heckel.ntfy.db.*
import io.heckel.ntfy.msg.DownloadManager import io.heckel.ntfy.msg.DownloadManager
import io.heckel.ntfy.msg.DownloadWorker import io.heckel.ntfy.msg.DownloadWorker
import io.heckel.ntfy.msg.NotificationService
import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_VIEW
import io.heckel.ntfy.util.* import io.heckel.ntfy.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -81,7 +83,7 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
val unmatchedTags = unmatchedTags(splitTags(notification.tags)) val unmatchedTags = unmatchedTags(splitTags(notification.tags))
dateView.text = formatDateShort(notification.timestamp) dateView.text = formatDateShort(notification.timestamp)
messageView.text = formatMessage(notification) messageView.text = maybeAppendActionErrors(formatMessage(notification), notification)
newDotImageView.visibility = if (notification.notificationId == 0) View.GONE else View.VISIBLE newDotImageView.visibility = if (notification.notificationId == 0) View.GONE else View.VISIBLE
itemView.setOnClickListener { onClick(notification) } itemView.setOnClickListener { onClick(notification) }
itemView.setOnLongClickListener { onLongClick(notification); true } itemView.setOnLongClickListener { onLongClick(notification); true }
@ -179,6 +181,7 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
val attachment = notification.attachment // May be null val attachment = notification.attachment // May be null
val hasAttachment = attachment != null val hasAttachment = attachment != null
val hasClickLink = notification.click != "" val hasClickLink = notification.click != ""
val hasUserActions = notification.actions?.isNotEmpty() ?: false
val downloadItem = popup.menu.findItem(R.id.detail_item_menu_download) val downloadItem = popup.menu.findItem(R.id.detail_item_menu_download)
val cancelItem = popup.menu.findItem(R.id.detail_item_menu_cancel) val cancelItem = popup.menu.findItem(R.id.detail_item_menu_cancel)
val openItem = popup.menu.findItem(R.id.detail_item_menu_open) val openItem = popup.menu.findItem(R.id.detail_item_menu_open)
@ -199,6 +202,12 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
if (hasClickLink) { if (hasClickLink) {
copyContentsItem.setOnMenuItemClickListener { copyContents(context, notification) } copyContentsItem.setOnMenuItemClickListener { copyContents(context, notification) }
} }
if (notification.actions != null && notification.actions.isNotEmpty()) {
notification.actions.forEach { action ->
val actionItem = popup.menu.add(formatActionLabel(action))
actionItem.setOnMenuItemClickListener { runAction(context, notification, action) }
}
}
openItem.isVisible = hasAttachment && exists openItem.isVisible = hasAttachment && exists
downloadItem.isVisible = hasAttachment && !exists && !expired && !inProgress downloadItem.isVisible = hasAttachment && !exists && !expired && !inProgress
deleteItem.isVisible = hasAttachment && exists deleteItem.isVisible = hasAttachment && exists
@ -208,7 +217,7 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
copyContentsItem.isVisible = notification.click != "" copyContentsItem.isVisible = notification.click != ""
val noOptions = !openItem.isVisible && !saveFileItem.isVisible && !downloadItem.isVisible val noOptions = !openItem.isVisible && !saveFileItem.isVisible && !downloadItem.isVisible
&& !copyUrlItem.isVisible && !cancelItem.isVisible && !deleteItem.isVisible && !copyUrlItem.isVisible && !cancelItem.isVisible && !deleteItem.isVisible
&& !copyContentsItem.isVisible && !copyContentsItem.isVisible && !hasUserActions
if (noOptions) { if (noOptions) {
return null return null
} }
@ -401,6 +410,31 @@ class DetailAdapter(private val activity: Activity, private val repository: Repo
copyToClipboard(context, notification) copyToClipboard(context, notification)
return true return true
} }
private fun runAction(context: Context, notification: Notification, action: Action): Boolean {
when (action.action) {
ACTION_VIEW -> runViewAction(context, action)
else -> runOtherUserAction(context, notification, action)
}
return true
}
private fun runViewAction(context: Context, action: Action) {
val url = action.url ?: return
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(intent)
}
private fun runOtherUserAction(context: Context, notification: Notification, action: Action) {
val intent = Intent(context, NotificationService.UserActionBroadcastReceiver::class.java).apply {
putExtra(NotificationService.BROADCAST_EXTRA_TYPE, NotificationService.BROADCAST_TYPE_USER_ACTION)
putExtra(NotificationService.BROADCAST_EXTRA_NOTIFICATION_ID, notification.id)
putExtra(NotificationService.BROADCAST_EXTRA_ACTION_ID, action.id)
}
context.sendBroadcast(intent)
}
} }
object TopicDiffCallback : DiffUtil.ItemCallback<Notification>() { object TopicDiffCallback : DiffUtil.ItemCallback<Notification>() {

View file

@ -23,9 +23,7 @@ import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.*
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64 import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -185,6 +183,27 @@ fun formatTitle(notification: Notification): String {
} }
} }
fun formatActionLabel(action: Action): String {
return when (action.progress) {
ACTION_PROGRESS_ONGOING -> action.label + ""
ACTION_PROGRESS_SUCCESS -> action.label + " ✔️"
ACTION_PROGRESS_FAILED -> action.label + " ❌️"
else -> action.label
}
}
fun maybeAppendActionErrors(message: String, notification: Notification): String {
val actionErrors = notification.actions
.orEmpty()
.mapNotNull { action -> action.error }
.joinToString("\n")
if (actionErrors.isEmpty()) {
return message
} else {
return "${message}\n\n${actionErrors}"
}
}
// Checks in the most horrible way if a content URI exists; I couldn't find a better way // Checks in the most horrible way if a content URI exists; I couldn't find a better way
fun fileExists(context: Context, contentUri: String?): Boolean { fun fileExists(context: Context, contentUri: String?): Boolean {
return try { return try {

View file

@ -4,7 +4,7 @@
The translatable="false" attribute is just an additional safety. --> The translatable="false" attribute is just an additional safety. -->
<!-- Main app constants --> <!-- Main app constants -->
<string name="app_name" translatable="false">Ntfy</string> <string name="app_name" translatable="false">ntfy</string>
<string name="app_base_url" translatable="false">https://ntfy.sh</string> <!-- If changed, you must also change google-services.json! --> <string name="app_base_url" translatable="false">https://ntfy.sh</string> <!-- If changed, you must also change google-services.json! -->
<!-- Main activity --> <!-- Main activity -->

View file

@ -13,6 +13,7 @@ import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64 import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
import io.heckel.ntfy.msg.NotificationDispatcher import io.heckel.ntfy.msg.NotificationDispatcher
import io.heckel.ntfy.msg.NotificationParser
import io.heckel.ntfy.service.SubscriberService import io.heckel.ntfy.service.SubscriberService
import io.heckel.ntfy.util.toPriority import io.heckel.ntfy.util.toPriority
import io.heckel.ntfy.util.topicShortUrl import io.heckel.ntfy.util.topicShortUrl
@ -27,6 +28,7 @@ class FirebaseService : FirebaseMessagingService() {
private val dispatcher by lazy { NotificationDispatcher(this, repository) } private val dispatcher by lazy { NotificationDispatcher(this, repository) }
private val job = SupervisorJob() private val job = SupervisorJob()
private val messenger = FirebaseMessenger() private val messenger = FirebaseMessenger()
private val parser = NotificationParser()
override fun onMessageReceived(remoteMessage: RemoteMessage) { override fun onMessageReceived(remoteMessage: RemoteMessage) {
// Init log (this is done in all entrypoints) // Init log (this is done in all entrypoints)
@ -88,6 +90,7 @@ class FirebaseService : FirebaseMessagingService() {
val priority = data["priority"]?.toIntOrNull() val priority = data["priority"]?.toIntOrNull()
val tags = data["tags"] val tags = data["tags"]
val click = data["click"] val click = data["click"]
val actions = data["actions"] // JSON array as string, sigh ...
val encoding = data["encoding"] val encoding = data["encoding"]
val attachmentName = data["attachment_name"] ?: "attachment.bin" val attachmentName = data["attachment_name"] ?: "attachment.bin"
val attachmentType = data["attachment_type"] val attachmentType = data["attachment_type"]
@ -131,13 +134,13 @@ class FirebaseService : FirebaseMessagingService() {
priority = toPriority(priority), priority = toPriority(priority),
tags = tags ?: "", tags = tags ?: "",
click = click ?: "", click = click ?: "",
actions = null, // FIXME actions = parser.parseActions(actions),
attachment = attachment, attachment = attachment,
notificationId = Random.nextInt(), notificationId = Random.nextInt(),
deleted = false deleted = false
) )
if (repository.addNotification(notification)) { if (repository.addNotification(notification)) {
Log.d(TAG, "Dispatching notification for message: from=${remoteMessage.from}, fcmprio=${remoteMessage.priority}, fcmprio_orig=${remoteMessage.originalPriority}, data=${data}") Log.d(TAG, "Dispatching notification: from=${remoteMessage.from}, fcmprio=${remoteMessage.priority}, fcmprio_orig=${remoteMessage.originalPriority}, data=${data}")
dispatcher.dispatch(subscription, notification) dispatcher.dispatch(subscription, notification)
} }
} }