Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 94e59511 authored by Philipp Heckel's avatar Philipp Heckel
Browse files

WIP: UnifiedPush

parent 2387f2ce
Loading
Loading
Loading
Loading
+150 −0
Original line number Diff line number Diff line
{
  "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
+8 −0
Original line number Diff line number Diff line
@@ -64,6 +64,14 @@
            </intent-filter>
        </receiver>

        <!-- Broadcast receiver for UnifiedPush; must match https://github.com/UnifiedPush/UP-spec/blob/main/specifications.md  -->
        <receiver android:name=".up.BroadcastReceiver" android:enabled="true" android:exported="true">
            <intent-filter>
                <action android:name="org.unifiedpush.android.distributor.REGISTER" />
                <action android:name="org.unifiedpush.android.distributor.UNREGISTER" />
            </intent-filter>
        </receiver>

        <!-- Firebase messaging (note that this is empty in the F-Droid flavor) -->
        <service
                android:name=".firebase.FirebaseService"
+19 −7
Original line number Diff line number Diff line
@@ -13,13 +13,15 @@ data class Subscription(
    @ColumnInfo(name = "topic") val topic: String,
    @ColumnInfo(name = "instant") val instant: Boolean,
    @ColumnInfo(name = "mutedUntil") val mutedUntil: Long, // TODO notificationSound, notificationSchedule
    @ColumnInfo(name = "upAppId") val upAppId: String,
    @ColumnInfo(name = "upConnectorToken") val upConnectorToken: String,
    @Ignore val totalCount: Int = 0, // Total notifications
    @Ignore val newCount: Int = 0, // New notifications
    @Ignore val lastActive: Long = 0, // Unix timestamp
    @Ignore val state: ConnectionState = ConnectionState.NOT_APPLICABLE
) {
    constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long) :
            this(id, baseUrl, topic, instant, mutedUntil, 0, 0, 0, ConnectionState.NOT_APPLICABLE)
    constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long, upAppId: String, upConnectorToken: String) :
            this(id, baseUrl, topic, instant, mutedUntil, upAppId, upConnectorToken, 0, 0, 0, ConnectionState.NOT_APPLICABLE)
}

enum class ConnectionState {
@@ -32,6 +34,8 @@ data class SubscriptionWithMetadata(
    val topic: String,
    val instant: Boolean,
    val mutedUntil: Long,
    val upAppId: String,
    val upConnectorToken: String,
    val totalCount: Int,
    val newCount: Int,
    val lastActive: Long
@@ -50,7 +54,7 @@ data class Notification(
    @ColumnInfo(name = "deleted") val deleted: Boolean,
)

@androidx.room.Database(entities = [Subscription::class, Notification::class], version = 4)
@androidx.room.Database(entities = [Subscription::class, Notification::class], version = 5)
abstract class Database : RoomDatabase() {
    abstract fun subscriptionDao(): SubscriptionDao
    abstract fun notificationDao(): NotificationDao
@@ -66,6 +70,7 @@ abstract class Database : RoomDatabase() {
                    .addMigrations(MIGRATION_1_2)
                    .addMigrations(MIGRATION_2_3)
                    .addMigrations(MIGRATION_3_4)
                    .addMigrations(MIGRATION_4_5)
                    .fallbackToDestructiveMigration()
                    .build()
                this.instance = instance
@@ -102,6 +107,13 @@ abstract class Database : RoomDatabase() {
                db.execSQL("ALTER TABLE Notification_New RENAME TO Notification")
            }
        }

        private val MIGRATION_4_5 = object : Migration(3, 4) {
            override fun migrate(db: SupportSQLiteDatabase) {
                db.execSQL("ALTER TABLE Subscription ADD COLUMN upAppId TEXT NOT NULL DEFAULT('')")
                db.execSQL("ALTER TABLE Subscription ADD COLUMN upConnectorToken TEXT NOT NULL DEFAULT('')")
            }
        }
    }
}

@@ -109,7 +121,7 @@ abstract class Database : RoomDatabase() {
interface SubscriptionDao {
    @Query("""
        SELECT 
          s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil,
          s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken,
          COUNT(n.id) totalCount, 
          COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, 
          IFNULL(MAX(n.timestamp),0) AS lastActive
@@ -122,7 +134,7 @@ interface SubscriptionDao {

    @Query("""
        SELECT 
          s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil,
          s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken,
          COUNT(n.id) totalCount, 
          COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, 
          IFNULL(MAX(n.timestamp),0) AS lastActive
@@ -135,7 +147,7 @@ interface SubscriptionDao {

    @Query("""
        SELECT 
          s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil,
          s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken,
          COUNT(n.id) totalCount, 
          COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, 
          IFNULL(MAX(n.timestamp),0) AS lastActive
@@ -148,7 +160,7 @@ interface SubscriptionDao {

    @Query("""
        SELECT 
          s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil,
          s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken,
          COUNT(n.id) totalCount, 
          COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, 
          IFNULL(MAX(n.timestamp),0) AS lastActive
+8 −2
Original line number Diff line number Diff line
@@ -92,9 +92,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
            val detailsVisible = detailViewSubscriptionId.get() == notification.subscriptionId
            val muted = isMuted(notification.subscriptionId)
            val notify = !detailsVisible && !muted
            return NotificationAddResult(notify = notify, broadcast = true, muted = muted)
            return NotificationAddResult(notification = notification, notify = notify, broadcast = true, muted = muted)
        }
        return NotificationAddResult(notify = false, broadcast = false, muted = false)
        return NotificationAddResult(notification = notification, notify = false, broadcast = false, forward = false, muted = false)
    }

    @Suppress("RedundantSuspendModifier")
@@ -177,6 +177,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
                topic = s.topic,
                instant = s.instant,
                mutedUntil = s.mutedUntil,
                upAppId = s.upAppId,
                upConnectorToken = s.upConnectorToken,
                totalCount = s.totalCount,
                newCount = s.newCount,
                lastActive = s.lastActive,
@@ -195,6 +197,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
            topic = s.topic,
            instant = s.instant,
            mutedUntil = s.mutedUntil,
            upAppId = s.upAppId,
            upConnectorToken = s.upConnectorToken,
            totalCount = s.totalCount,
            newCount = s.newCount,
            lastActive = s.lastActive,
@@ -225,8 +229,10 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
    }

    data class NotificationAddResult(
        val notification: Notification,
        val notify: Boolean,
        val broadcast: Boolean,
        val forward: Boolean, // Forward to UnifiedPush connector
        val muted: Boolean,
    )

+31 −0
Original line number Diff line number Diff line
package io.heckel.ntfy.msg

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.TaskStackBuilder
import android.content.Context
import android.content.Intent
import android.media.RingtoneManager
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import io.heckel.ntfy.R
import io.heckel.ntfy.data.Notification
import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.ui.DetailActivity
import io.heckel.ntfy.ui.MainActivity
import io.heckel.ntfy.util.formatMessage
import io.heckel.ntfy.util.formatTitle

class NotificationDispatcher(val context: Context) {
    fun dispatch(subscription: Subscription, notification: Notification) {

    }

    companion object {
        private const val TAG = "NtfyNotificationDispatcher"
    }
}
Loading