diff --git a/app/schemas/io.heckel.ntfy.data.Database/5.json b/app/schemas/io.heckel.ntfy.data.Database/5.json new file mode 100644 index 0000000..0620854 --- /dev/null +++ b/app/schemas/io.heckel.ntfy.data.Database/5.json @@ -0,0 +1,150 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "d72d045ad4ad20db887b4c6aed3da27b", + "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 NOT NULL, `upConnectorToken` TEXT NOT NULL, 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": true + }, + { + "fieldPath": "upConnectorToken", + "columnName": "upConnectorToken", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_Subscription_baseUrl_topic", + "unique": true, + "columnNames": [ + "baseUrl", + "topic" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_baseUrl_topic` ON `${TABLE_NAME}` (`baseUrl`, `topic`)" + } + ], + "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, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `deleted` INTEGER NOT NULL, 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": "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": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id", + "subscriptionId" + ], + "autoGenerate": false + }, + "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, 'd72d045ad4ad20db887b4c6aed3da27b')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 931bab1..1beaca1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -64,6 +64,14 @@ + + + + + + + + { + val appId = intent.getStringExtra(EXTRA_APPLICATION) ?: "" + val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: "" + Log.d(TAG, "Register: app=$appId, connectorToken=$connectorToken") + if (appId.isBlank()) { + Log.w(TAG, "Trying to register an app without packageName") + return + } + + val baseUrl = context!!.getString(R.string.app_base_url) // FIXME + val topic = connectorToken // FIXME + val app = context!!.applicationContext as Application + val repository = app.repository + val subscription = Subscription( + id = Random.nextLong(), + baseUrl = baseUrl, + topic = topic, + instant = true, + mutedUntil = 0, + upAppId = appId, + upConnectorToken = connectorToken, + totalCount = 0, + newCount = 0, + lastActive = Date().time/1000 + ) + GlobalScope.launch(Dispatchers.IO) { + repository.addSubscription(subscription) + } + + sendEndpoint(context!!, appId, connectorToken) + // XXXXXXXXX + } + ACTION_UNREGISTER -> { + val connectorToken = intent.getStringExtra(EXTRA_TOKEN) ?: "" + Log.d(TAG, "Unregister: connectorToken=$connectorToken") + // XXXXXXX + sendUnregistered(context!!, "org.unifiedpush.example", connectorToken) + } + } + } + + companion object { + private const val TAG = "NtfyUpBroadcastRecv" + } +} diff --git a/app/src/main/java/io/heckel/ntfy/up/Constants.kt b/app/src/main/java/io/heckel/ntfy/up/Constants.kt new file mode 100644 index 0000000..6ee8b41 --- /dev/null +++ b/app/src/main/java/io/heckel/ntfy/up/Constants.kt @@ -0,0 +1,22 @@ +package io.heckel.ntfy.up + +/** + * Constants as defined on the specs + * https://github.com/UnifiedPush/UP-spec/blob/main/specifications.md + */ + +const val ACTION_NEW_ENDPOINT = "org.unifiedpush.android.connector.NEW_ENDPOINT" +const val ACTION_REGISTRATION_FAILED = "org.unifiedpush.android.connector.REGISTRATION_FAILED" +const val ACTION_REGISTRATION_REFUSED = "org.unifiedpush.android.connector.REGISTRATION_REFUSED" +const val ACTION_UNREGISTERED = "org.unifiedpush.android.connector.UNREGISTERED" +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 ACTION_MESSAGE_ACK = "org.unifiedpush.android.distributor.MESSAGE_ACK" + +const val EXTRA_APPLICATION = "application" +const val EXTRA_TOKEN = "token" +const val EXTRA_ENDPOINT = "endpoint" +const val EXTRA_MESSAGE = "message" +const val EXTRA_MESSAGE_ID = "id" diff --git a/app/src/main/java/io/heckel/ntfy/up/DistributorUtils.kt b/app/src/main/java/io/heckel/ntfy/up/DistributorUtils.kt new file mode 100644 index 0000000..a7ba475 --- /dev/null +++ b/app/src/main/java/io/heckel/ntfy/up/DistributorUtils.kt @@ -0,0 +1,34 @@ +package io.heckel.ntfy.up + +import android.content.Context +import android.content.Intent +import io.heckel.ntfy.R +import io.heckel.ntfy.util.topicUrlUp + +fun sendMessage(context: Context, app: String, token: String, message: String) { + val broadcastIntent = Intent() + broadcastIntent.`package` = app + broadcastIntent.action = ACTION_MESSAGE + broadcastIntent.putExtra(EXTRA_TOKEN, token) + broadcastIntent.putExtra(EXTRA_MESSAGE, message) + context.sendBroadcast(broadcastIntent) +} + +fun sendEndpoint(context: Context, app: String, token: String) { + val appBaseUrl = context.getString(R.string.app_base_url) + val broadcastIntent = Intent() + broadcastIntent.`package` = app + broadcastIntent.action = ACTION_NEW_ENDPOINT + broadcastIntent.putExtra(EXTRA_TOKEN, token) + broadcastIntent.putExtra(EXTRA_ENDPOINT, topicUrlUp(appBaseUrl, token)) + context.sendBroadcast(broadcastIntent) +} + +fun sendUnregistered(context: Context, app: String, token: String) { + val broadcastIntent = Intent() + broadcastIntent.`package` = app + broadcastIntent.action = ACTION_UNREGISTERED + broadcastIntent.putExtra(EXTRA_TOKEN, token) + 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 b5f4a08..1d8dcff 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -9,6 +9,7 @@ import java.text.DateFormat import java.util.* fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}" +fun topicUrlUp(baseUrl: String, topic: String) = "${baseUrl}/${topic}?up=1" // UnifiedPush fun topicUrlJson(baseUrl: String, topic: String, since: String) = "${topicUrl(baseUrl, topic)}/json?since=$since" fun topicUrlJsonPoll(baseUrl: String, topic: String) = "${topicUrl(baseUrl, topic)}/json?poll=1" fun topicShortUrl(baseUrl: String, topic: String) =