diff --git a/app/build.gradle b/app/build.gradle
index 0aa3459..eea699c 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -81,7 +81,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
// Firebase, sigh ... (only Google Play)
- playImplementation 'com.google.firebase:firebase-messaging:23.0.1'
+ playImplementation 'com.google.firebase:firebase-messaging:23.0.0'
// RecyclerView
implementation "androidx.recyclerview:recyclerview:1.3.0-alpha01"
diff --git a/app/schemas/io.heckel.ntfy.db.Database/8.json b/app/schemas/io.heckel.ntfy.db.Database/8.json
index 2f25cff..5ef4e3e 100644
--- a/app/schemas/io.heckel.ntfy.db.Database/8.json
+++ b/app/schemas/io.heckel.ntfy.db.Database/8.json
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 8,
- "identityHash": "eda2cb9740c4542f24462779eb6ff81d",
+ "identityHash": "5bab75c3b41c53c9855fe3a7ef8f0669",
"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, `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, `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",
@@ -114,6 +114,12 @@
"affinity": "TEXT",
"notNull": true
},
+ {
+ "fieldPath": "encoding",
+ "columnName": "encoding",
+ "affinity": "TEXT",
+ "notNull": true
+ },
{
"fieldPath": "notificationId",
"columnName": "notificationId",
@@ -284,7 +290,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, 'eda2cb9740c4542f24462779eb6ff81d')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5bab75c3b41c53c9855fe3a7ef8f0669')"
]
}
}
\ No newline at end of file
diff --git a/app/schemas/io.heckel.ntfy.db.Database/9.json b/app/schemas/io.heckel.ntfy.db.Database/9.json
new file mode 100644
index 0000000..024364b
--- /dev/null
+++ b/app/schemas/io.heckel.ntfy.db.Database/9.json
@@ -0,0 +1,296 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 9,
+ "identityHash": "5bab75c3b41c53c9855fe3a7ef8f0669",
+ "entities": [
+ {
+ "tableName": "Subscription",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `upAppId` TEXT, `upConnectorToken` TEXT, PRIMARY KEY(`id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "baseUrl",
+ "columnName": "baseUrl",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "topic",
+ "columnName": "topic",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "instant",
+ "columnName": "instant",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mutedUntil",
+ "columnName": "mutedUntil",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "upAppId",
+ "columnName": "upAppId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "upConnectorToken",
+ "columnName": "upConnectorToken",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_Subscription_baseUrl_topic",
+ "unique": true,
+ "columnNames": [
+ "baseUrl",
+ "topic"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_baseUrl_topic` ON `${TABLE_NAME}` (`baseUrl`, `topic`)"
+ },
+ {
+ "name": "index_Subscription_upConnectorToken",
+ "unique": true,
+ "columnNames": [
+ "upConnectorToken"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_upConnectorToken` ON `${TABLE_NAME}` (`upConnectorToken`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "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`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "subscriptionId",
+ "columnName": "subscriptionId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "encoding",
+ "columnName": "encoding",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notificationId",
+ "columnName": "notificationId",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "priority",
+ "columnName": "priority",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "3"
+ },
+ {
+ "fieldPath": "tags",
+ "columnName": "tags",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "click",
+ "columnName": "click",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "deleted",
+ "columnName": "deleted",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "attachment.name",
+ "columnName": "attachment_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachment.type",
+ "columnName": "attachment_type",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachment.size",
+ "columnName": "attachment_size",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachment.expires",
+ "columnName": "attachment_expires",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachment.url",
+ "columnName": "attachment_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachment.contentUri",
+ "columnName": "attachment_contentUri",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "attachment.progress",
+ "columnName": "attachment_progress",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id",
+ "subscriptionId"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "User",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`baseUrl` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`baseUrl`))",
+ "fields": [
+ {
+ "fieldPath": "baseUrl",
+ "columnName": "baseUrl",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "username",
+ "columnName": "username",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "password",
+ "columnName": "password",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "baseUrl"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "Log",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `tag` TEXT NOT NULL, `level` INTEGER NOT NULL, `message` TEXT NOT NULL, `exception` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tag",
+ "columnName": "tag",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exception",
+ "columnName": "exception",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "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')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 066e32e..8300aad 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -108,6 +108,7 @@
+
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 8da20f5..7b05ff3 100644
--- a/app/src/main/java/io/heckel/ntfy/db/Database.kt
+++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt
@@ -50,6 +50,7 @@ data class Notification(
@ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp
@ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "message") val message: String,
+ @ColumnInfo(name = "encoding") val encoding: String, // "base64" or ""
@ColumnInfo(name = "notificationId") val notificationId: Int, // Android notification popup ID
@ColumnInfo(name = "priority", defaultValue = "3") val priority: Int, // 1=min, 3=default, 5=max
@ColumnInfo(name = "tags") val tags: String,
@@ -100,7 +101,7 @@ data class LogEntry(
this(0, timestamp, tag, level, message, exception)
}
-@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 8)
+@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 9)
abstract class Database : RoomDatabase() {
abstract fun subscriptionDao(): SubscriptionDao
abstract fun notificationDao(): NotificationDao
@@ -122,6 +123,7 @@ abstract class Database : RoomDatabase() {
.addMigrations(MIGRATION_5_6)
.addMigrations(MIGRATION_6_7)
.addMigrations(MIGRATION_7_8)
+ .addMigrations(MIGRATION_8_9)
.fallbackToDestructiveMigration()
.build()
this.instance = instance
@@ -191,6 +193,12 @@ abstract class Database : RoomDatabase() {
db.execSQL("CREATE TABLE User (baseUrl TEXT NOT NULL, username TEXT NOT NULL, password TEXT NOT NULL, PRIMARY KEY(baseUrl))")
}
}
+
+ private val MIGRATION_8_9 = object : Migration(8, 9) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ db.execSQL("ALTER TABLE Notification ADD COLUMN encoding TEXT NOT NULL DEFAULT('')")
+ }
+ }
}
}
diff --git a/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt b/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt
index 1204323..d39bb6c 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt
@@ -2,13 +2,12 @@ package io.heckel.ntfy.msg
import android.content.Context
import android.content.Intent
+import android.util.Base64
import io.heckel.ntfy.R
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
-import io.heckel.ntfy.util.Log
-import io.heckel.ntfy.util.joinTagsMap
-import io.heckel.ntfy.util.splitTags
+import io.heckel.ntfy.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -26,7 +25,9 @@ class BroadcastService(private val ctx: Context) {
intent.putExtra("topic", subscription.topic)
intent.putExtra("time", notification.timestamp.toInt())
intent.putExtra("title", notification.title)
- intent.putExtra("message", notification.message)
+ intent.putExtra("message", decodeMessage(notification))
+ intent.putExtra("message_bytes", decodeBytesMessage(notification))
+ intent.putExtra("message_encoding", notification.encoding)
intent.putExtra("tags", notification.tags)
intent.putExtra("tags_map", joinTagsMap(splitTags(notification.tags)))
intent.putExtra("priority", notification.priority)
diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt
index 8bd5f2b..bfc4f86 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt
@@ -1,11 +1,13 @@
package io.heckel.ntfy.msg
import android.content.Context
+import android.util.Base64
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.up.Distributor
+import io.heckel.ntfy.util.decodeBytesMessage
import io.heckel.ntfy.util.safeLet
/**
@@ -37,7 +39,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
}
if (distribute) {
safeLet(subscription.upAppId, subscription.upConnectorToken) { appId, connectorToken ->
- distributor.sendMessage(appId, connectorToken, notification.message)
+ distributor.sendMessage(appId, connectorToken, decodeBytesMessage(notification))
}
}
if (download) {
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 150b8ca..48b873b 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationParser.kt
@@ -20,11 +20,6 @@ class NotificationParser {
if (message.event != ApiService.EVENT_MESSAGE) {
return null
}
- val decodedMessage = if (message.encoding == MESSAGE_ENCODING_BASE64) {
- String(Base64.decode(message.message, Base64.DEFAULT))
- } else {
- message.message
- }
val attachment = if (message.attachment?.url != null) {
Attachment(
name = message.attachment.name,
@@ -39,7 +34,8 @@ class NotificationParser {
subscriptionId = subscriptionId,
timestamp = message.time,
title = message.title ?: "",
- message = decodedMessage,
+ message = message.message,
+ encoding = message.encoding ?: "",
priority = toPriority(message.priority),
tags = joinTags(message.tags),
click = message.click ?: "",
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 97fdc82..f9f758b 100644
--- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt
+++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt
@@ -39,7 +39,7 @@ class NotificationService(val context: Context) {
fun cancel(notification: Notification) {
if (notification.notificationId != 0) {
- Log.d(TAG, "Cancelling notification ${notification.id}: ${notification.message}")
+ Log.d(TAG, "Cancelling notification ${notification.id}: ${decodeMessage(notification)}")
notificationManager.cancel(notification.notificationId)
}
}
diff --git a/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt b/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt
index 7002290..9ccb93e 100644
--- a/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt
+++ b/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt
@@ -87,8 +87,8 @@ class JsonConnection(
override fun close() {
Log.d(TAG, "[$url] Cancelling connection")
- if (this::job.isInitialized) job?.cancel()
- if (this::call.isInitialized) call?.cancel()
+ if (this::job.isInitialized) job.cancel()
+ if (this::call.isInitialized) call.cancel()
}
private fun nextRetryMillis(retryMillis: Long, startTime: Long): Long {
diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt
index 29288e6..6008765 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt
@@ -7,6 +7,7 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.Html
+import android.util.Base64
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
@@ -29,6 +30,7 @@ import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.firebase.FirebaseMessenger
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService
+import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
import io.heckel.ntfy.msg.NotificationService
import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.*
@@ -514,9 +516,10 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
private fun copyToClipboard(notification: Notification) {
runOnUiThread {
- val message = notification.message + "\n\n" + Date(notification.timestamp * 1000).toString()
+ val message = decodeMessage(notification)
+ val text = message + "\n\n" + Date(notification.timestamp * 1000).toString()
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- val clip = ClipData.newPlainText("notification message", message)
+ val clip = ClipData.newPlainText("notification message", text)
clipboard.setPrimaryClip(clip)
Toast
.makeText(this, getString(R.string.detail_copied_to_clipboard_message), Toast.LENGTH_LONG)
@@ -574,7 +577,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
val content = adapter.selected.joinToString("\n\n") { notificationId ->
val notification = repository.getNotification(notificationId)
notification?.let {
- it.message + "\n" + Date(it.timestamp * 1000).toString()
+ decodeMessage(it) + "\n" + Date(it.timestamp * 1000).toString()
}.orEmpty()
}
runOnUiThread {
diff --git a/app/src/main/java/io/heckel/ntfy/up/Constants.kt b/app/src/main/java/io/heckel/ntfy/up/Constants.kt
index 6fd5d97..8c9ddaf 100644
--- a/app/src/main/java/io/heckel/ntfy/up/Constants.kt
+++ b/app/src/main/java/io/heckel/ntfy/up/Constants.kt
@@ -13,7 +13,10 @@ const val ACTION_MESSAGE = "org.unifiedpush.android.connector.MESSAGE"
const val ACTION_REGISTER = "org.unifiedpush.android.distributor.REGISTER"
const val ACTION_UNREGISTER = "org.unifiedpush.android.distributor.UNREGISTER"
+const val FEATURE_BYTES_MESSAGE = "org.unifiedpush.android.distributor.feature.BYTES_MESSAGE"
+
const val EXTRA_APPLICATION = "application"
const val EXTRA_TOKEN = "token"
const val EXTRA_ENDPOINT = "endpoint"
const val EXTRA_MESSAGE = "message"
+const val EXTRA_BYTES_MESSAGE = "bytesMessage"
diff --git a/app/src/main/java/io/heckel/ntfy/up/Distributor.kt b/app/src/main/java/io/heckel/ntfy/up/Distributor.kt
index 29b1727..1e9b7c1 100644
--- a/app/src/main/java/io/heckel/ntfy/up/Distributor.kt
+++ b/app/src/main/java/io/heckel/ntfy/up/Distributor.kt
@@ -9,13 +9,14 @@ import io.heckel.ntfy.util.Log
* See https://unifiedpush.org/spec/android/ for details.
*/
class Distributor(val context: Context) {
- fun sendMessage(app: String, connectorToken: String, message: String) {
- Log.d(TAG, "Sending MESSAGE to $app (token=$connectorToken): $message")
+ fun sendMessage(app: String, connectorToken: String, message: ByteArray) {
+ Log.d(TAG, "Sending MESSAGE to $app (token=$connectorToken): ${String(message)} (${message.size} bytes)}")
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_MESSAGE
broadcastIntent.putExtra(EXTRA_TOKEN, connectorToken)
- broadcastIntent.putExtra(EXTRA_MESSAGE, message)
+ broadcastIntent.putExtra(EXTRA_MESSAGE, String(message)) // UTF-8
+ broadcastIntent.putExtra(EXTRA_BYTES_MESSAGE, message)
context.sendBroadcast(broadcastIntent)
}
diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt
index d09a3e2..bf60641 100644
--- a/app/src/main/java/io/heckel/ntfy/util/Util.kt
+++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt
@@ -13,6 +13,7 @@ import android.os.PowerManager
import android.provider.OpenableColumns
import android.text.Editable
import android.text.TextWatcher
+import android.util.Base64
import android.util.TypedValue
import android.view.View
import android.view.Window
@@ -22,6 +23,7 @@ import io.heckel.ntfy.R
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
+import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
@@ -110,21 +112,45 @@ fun unmatchedTags(tags: List): List {
/**
* Prepend tags/emojis to message, but only if there is a non-empty title.
- * Otherwise the tags will be prepended to the title.
+ * Otherwise, the tags will be prepended to the title.
*/
fun formatMessage(notification: Notification): String {
return if (notification.title != "") {
- notification.message
+ decodeMessage(notification)
} else {
val emojis = toEmojis(splitTags(notification.tags))
if (emojis.isEmpty()) {
- notification.message
+ decodeMessage(notification)
} else {
- emojis.joinToString("") + " " + notification.message
+ emojis.joinToString("") + " " + decodeMessage(notification)
}
}
}
+fun decodeMessage(notification: Notification): String {
+ return try {
+ if (notification.encoding == MESSAGE_ENCODING_BASE64) {
+ String(Base64.decode(notification.message, Base64.DEFAULT))
+ } else {
+ notification.message
+ }
+ } catch (e: IllegalArgumentException) {
+ notification.message + "(invalid base64)"
+ }
+}
+
+fun decodeBytesMessage(notification: Notification): ByteArray {
+ return try {
+ if (notification.encoding == MESSAGE_ENCODING_BASE64) {
+ Base64.decode(notification.message, Base64.DEFAULT)
+ } else {
+ notification.message.toByteArray()
+ }
+ } catch (e: IllegalArgumentException) {
+ notification.message.toByteArray()
+ }
+}
+
/**
* See above; prepend emojis to title if the title is non-empty.
* Otherwise, they are prepended to the message.
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 705330e..2506cee 100644
--- a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt
+++ b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt
@@ -112,11 +112,6 @@ class FirebaseService : FirebaseMessagingService() {
}
// Add notification
- val decodedMessage = if (encoding == MESSAGE_ENCODING_BASE64) {
- String(Base64.decode(message, Base64.DEFAULT))
- } else {
- message
- }
val attachment = if (attachmentUrl != null) {
Attachment(
name = attachmentName,
@@ -131,7 +126,8 @@ class FirebaseService : FirebaseMessagingService() {
subscriptionId = subscription.id,
timestamp = timestamp,
title = title ?: "",
- message = decodedMessage,
+ message = message,
+ encoding = encoding ?: "",
priority = toPriority(priority),
tags = tags ?: "",
click = click ?: "",
diff --git a/fastlane/metadata/android/en-US/changelog/24.txt b/fastlane/metadata/android/en-US/changelog/24.txt
new file mode 100644
index 0000000..3e06f82
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelog/24.txt
@@ -0,0 +1,2 @@
+Features:
+* Support for UnifiedPush 2.0 specification (bytes messages, #130)