From 2f8be72c12ee6b3848903136733e05140a635c87 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Sat, 16 Apr 2022 20:12:40 -0400 Subject: [PATCH] Actions --- app/schemas/io.heckel.ntfy.db.Database/9.json | 12 ++++++-- .../java/io/heckel/ntfy/backup/Backuper.kt | 1 + .../main/java/io/heckel/ntfy/db/Database.kt | 28 ++++++++++++++++++- .../main/java/io/heckel/ntfy/msg/Message.kt | 9 ++++++ .../io/heckel/ntfy/msg/NotificationParser.kt | 7 +++++ .../io/heckel/ntfy/msg/NotificationService.kt | 24 ++++++++++++++++ .../heckel/ntfy/firebase/FirebaseService.kt | 1 + 7 files changed, 78 insertions(+), 4 deletions(-) diff --git a/app/schemas/io.heckel.ntfy.db.Database/9.json b/app/schemas/io.heckel.ntfy.db.Database/9.json index 024364b..6a470b8 100644 --- a/app/schemas/io.heckel.ntfy.db.Database/9.json +++ b/app/schemas/io.heckel.ntfy.db.Database/9.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 9, - "identityHash": "5bab75c3b41c53c9855fe3a7ef8f0669", + "identityHash": "c1b4f54d1d3111dc5c8f02e8fa960ceb", "entities": [ { "tableName": "Subscription", @@ -82,7 +82,7 @@ }, { "tableName": "Notification", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))", "fields": [ { "fieldPath": "id", @@ -145,6 +145,12 @@ "affinity": "TEXT", "notNull": true }, + { + "fieldPath": "actions", + "columnName": "actions", + "affinity": "TEXT", + "notNull": false + }, { "fieldPath": "deleted", "columnName": "deleted", @@ -290,7 +296,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5bab75c3b41c53c9855fe3a7ef8f0669')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c1b4f54d1d3111dc5c8f02e8fa960ceb')" ] } } \ No newline at end of file diff --git a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt index 52720d3..5614536 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -133,6 +133,7 @@ class Backuper(val context: Context) { priority = n.priority, tags = n.tags, click = n.click, + actions = null, // FIXME attachment = attachment, deleted = n.deleted )) diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index ed3d550..e7cd41c 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -4,8 +4,10 @@ import android.content.Context import androidx.room.* import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import io.heckel.ntfy.util.shortUrl +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import kotlinx.coroutines.flow.Flow +import java.lang.reflect.Type @Entity(indices = [Index(value = ["baseUrl", "topic"], unique = true), Index(value = ["upConnectorToken"], unique = true)]) data class Subscription( @@ -55,6 +57,7 @@ data class Notification( @ColumnInfo(name = "priority", defaultValue = "3") val priority: Int, // 1=min, 3=default, 5=max @ColumnInfo(name = "tags") val tags: String, @ColumnInfo(name = "click") val click: String, // URL/intent to open on notification click + @ColumnInfo(name = "actions") val actions: List?, @Embedded(prefix = "attachment_") val attachment: Attachment?, @ColumnInfo(name = "deleted") val deleted: Boolean, ) @@ -73,6 +76,28 @@ data class Attachment( this(name, type, size, expires, url, null, PROGRESS_NONE) } +@Entity +data class Action( + @ColumnInfo(name = "action") val action: String, + @ColumnInfo(name = "label") val label: String, + @ColumnInfo(name = "url") val url: String?, +) + +class Converters { + private val gson = Gson() + + @TypeConverter + fun toActionList(value: String?): List? { + val listType: Type = object : TypeToken?>() {}.type + return gson.fromJson(value, listType) + } + + @TypeConverter + fun fromActionList(list: List?): String { + return gson.toJson(list) + } +} + const val PROGRESS_NONE = -1 const val PROGRESS_INDETERMINATE = -2 const val PROGRESS_FAILED = -3 @@ -102,6 +127,7 @@ data class LogEntry( } @androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 9) +@TypeConverters(Converters::class) abstract class Database : RoomDatabase() { abstract fun subscriptionDao(): SubscriptionDao abstract fun notificationDao(): NotificationDao diff --git a/app/src/main/java/io/heckel/ntfy/msg/Message.kt b/app/src/main/java/io/heckel/ntfy/msg/Message.kt index 51ed150..8653fe6 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/Message.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/Message.kt @@ -1,6 +1,7 @@ package io.heckel.ntfy.msg import androidx.annotation.Keep +import io.heckel.ntfy.db.Action /* This annotation ensures that proguard still works in production builds, * see https://stackoverflow.com/a/62753300/1440785 */ @@ -13,6 +14,7 @@ data class Message( val priority: Int?, val tags: List?, val click: String?, + val actions: List?, val title: String?, val message: String, val encoding: String?, @@ -28,4 +30,11 @@ data class MessageAttachment( val url: String, ) +@Keep +data class MessageAction( + val action: String, + val label: String, + val url: String?, +) + const val MESSAGE_ENCODING_BASE64 = "base64" diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt index 48b873b..817bc9d 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt @@ -2,6 +2,7 @@ package io.heckel.ntfy.msg import android.util.Base64 import com.google.gson.Gson +import io.heckel.ntfy.db.Action import io.heckel.ntfy.db.Attachment import io.heckel.ntfy.db.Notification import io.heckel.ntfy.util.joinTags @@ -29,6 +30,11 @@ class NotificationParser { url = message.attachment.url, ) } else null + val actions = if (message.actions != null) { + message.actions.map { action -> + Action(action.action, action.label, action.url) + } + } else null val notification = Notification( id = message.id, subscriptionId = subscriptionId, @@ -39,6 +45,7 @@ class NotificationParser { priority = toPriority(message.priority), tags = joinTags(message.tags), click = message.click ?: "", + actions = actions, attachment = attachment, notificationId = notificationId, deleted = false diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt index ceafd4c..046389a 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -65,6 +65,7 @@ class NotificationService(val context: Context) { maybeAddBrowseAction(builder, notification) maybeAddDownloadAction(builder, notification) maybeAddCancelAction(builder, notification) + maybeAddCustomActions(builder, notification) maybeCreateNotificationChannel(notification.priority) notificationManager.notify(notification.notificationId, builder.build()) @@ -190,6 +191,29 @@ class NotificationService(val context: Context) { } } + private fun maybeAddCustomActions(builder: NotificationCompat.Builder, notification: Notification) { + notification.actions?.forEach { action -> + when (action.action) { + "view" -> maybeAddOpenUserAction(builder, notification, action) + } + } + } + + private fun maybeAddOpenUserAction(builder: NotificationCompat.Builder, notification: Notification, action: Action) { + Log.d(TAG, "Adding user action $action") + + val url = action.url ?: return + try { + val uri = Uri.parse(url) + val intent = Intent(Intent.ACTION_VIEW, uri) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) + builder.addAction(NotificationCompat.Action.Builder(0, action.label, pendingIntent).build()) + } catch (e: Exception) { + Log.w(TAG, "Unable to add open user action", e) + } + } + class DownloadBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val id = intent.getStringExtra("id") ?: return diff --git a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt index 2506cee..a3db0db 100644 --- a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt +++ b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt @@ -131,6 +131,7 @@ class FirebaseService : FirebaseMessagingService() { priority = toPriority(priority), tags = tags ?: "", click = click ?: "", + actions = null, // FIXME attachment = attachment, notificationId = Random.nextInt(), deleted = false