Implement UnifiedPush 2.0 spec (untested, #130)
This commit is contained in:
parent
81483ff3cd
commit
bd8d61997d
16 changed files with 376 additions and 35 deletions
|
@ -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"
|
||||
|
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
296
app/schemas/io.heckel.ntfy.db.Database/9.json
Normal file
296
app/schemas/io.heckel.ntfy.db.Database/9.json
Normal file
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -108,6 +108,7 @@
|
|||
<intent-filter>
|
||||
<action android:name="org.unifiedpush.android.distributor.REGISTER"/>
|
||||
<action android:name="org.unifiedpush.android.distributor.UNREGISTER"/>
|
||||
<action android:name="org.unifiedpush.android.distributor.feature.BYTES_MESSAGE"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
|
|
@ -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('')")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 ?: "",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String>): List<String> {
|
|||
|
||||
/**
|
||||
* 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.
|
||||
|
|
|
@ -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 ?: "",
|
||||
|
|
2
fastlane/metadata/android/en-US/changelog/24.txt
Normal file
2
fastlane/metadata/android/en-US/changelog/24.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
Features:
|
||||
* Support for UnifiedPush 2.0 specification (bytes messages, #130)
|
Loading…
Reference in a new issue