diff --git a/app/build.gradle b/app/build.gradle
index 24d7ac0877..bef6eced1f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -187,8 +187,7 @@ dependencies {
implementation libs.bundles.filemojicompat
- implementation libs.bouncycastle
- implementation libs.unified.push
+ implementation libs.bundles.unifiedpush
implementation libs.bundles.xmldiff
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index b3833ebceb..479d90efe3 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -26,10 +26,6 @@
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
-# Bouncy Castle -- Keep EC
--keep class org.bouncycastle.jcajce.provider.asymmetric.EC$* { *; }
--keep class org.bouncycastle.jcajce.provider.asymmetric.ec.KeyPairGeneratorSpi$EC
-
# Preference fragments can be referenced by name, ensure they remain
# https://github.com/tuskyapp/Tusky/issues/3161
-keep class * extends androidx.preference.PreferenceFragmentCompat
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 86db206e8a..900cd2996f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -159,19 +159,12 @@
android:name=".receiver.SendStatusBroadcastReceiver"
android:enabled="true"
android:exported="false" />
-
+
-
-
-
-
-
+
-
+
- notificationService.clearNotificationsForAccount(account)
+ notificationHelper.clearNotificationsForAccount(account)
}
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/SeveredRelationshipNotificationViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/SeveredRelationshipNotificationViewHolder.kt
index 541585ee31..96fd7a26bd 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/SeveredRelationshipNotificationViewHolder.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/SeveredRelationshipNotificationViewHolder.kt
@@ -16,7 +16,7 @@
package com.keylesspalace.tusky.components.notifications
import androidx.recyclerview.widget.RecyclerView
-import com.keylesspalace.tusky.components.systemnotifications.NotificationService
+import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
import com.keylesspalace.tusky.databinding.ItemSeveredRelationshipNotificationBinding
import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.viewdata.NotificationViewData
@@ -37,7 +37,7 @@ class SeveredRelationshipNotificationViewHolder(
val event = viewData.event!!
val context = binding.root.context
- binding.severedRelationshipText.text = NotificationService.severedRelationShipText(
+ binding.severedRelationshipText.text = NotificationHelper.severedRelationShipText(
context,
event,
instanceName
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt
index fcc42e852d..2ee11ae8e0 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt
@@ -18,7 +18,7 @@ package com.keylesspalace.tusky.components.preference
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.keylesspalace.tusky.R
-import com.keylesspalace.tusky.components.systemnotifications.NotificationService
+import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.entity.AccountEntity
import com.keylesspalace.tusky.settings.PrefKeys
@@ -36,7 +36,7 @@ class NotificationPreferencesFragment : BasePreferencesFragment() {
lateinit var accountManager: AccountManager
@Inject
- lateinit var notificationService: NotificationService
+ lateinit var notificationHelper: NotificationHelper
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
val activeAccount = accountManager.activeAccount ?: return
@@ -48,10 +48,10 @@ class NotificationPreferencesFragment : BasePreferencesFragment() {
isChecked = activeAccount.notificationsEnabled
setOnPreferenceChangeListener { _, newValue ->
updateAccount { copy(notificationsEnabled = newValue as Boolean) }
- if (notificationService.areNotificationsEnabledBySystem()) {
- notificationService.enablePullNotifications()
+ if (notificationHelper.areNotificationsEnabledBySystem()) {
+ notificationHelper.enablePullNotifications()
} else {
- notificationService.disablePullNotifications()
+ notificationHelper.disablePullNotifications()
}
true
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationFetcher.kt b/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationFetcher.kt
index d65d4381bb..ae722595a7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationFetcher.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationFetcher.kt
@@ -43,7 +43,7 @@ class NotificationFetcher @Inject constructor(
private val mastodonApi: MastodonApi,
private val accountManager: AccountManager,
private val eventHub: EventHub,
- private val notificationService: NotificationService,
+ private val notificationHelper: NotificationHelper,
) {
suspend fun fetchAndShow(accountId: Long?) {
for (account in accountManager.accounts) {
@@ -54,14 +54,14 @@ class NotificationFetcher @Inject constructor(
if (account.notificationsEnabled) {
try {
val notifications = fetchNewNotifications(account)
- .filter { notificationService.filterNotification(account, it.type) }
+ .filter { notificationHelper.filterNotification(account, it.type) }
.sortedWith(
compareBy({ it.id.length }, { it.id })
) // oldest notifications first
eventHub.dispatch(NewNotificationsEvent(account.accountId, notifications))
- notificationService.show(account, notifications)
+ notificationHelper.show(account, notifications)
} catch (e: Exception) {
Log.e(TAG, "Error while fetching notifications", e)
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationService.kt b/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationHelper.kt
similarity index 94%
rename from app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationService.kt
rename to app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationHelper.kt
index 9243ee0db1..b7cc554622 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationService.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationHelper.kt
@@ -46,6 +46,7 @@ import com.keylesspalace.tusky.MainActivity.Companion.openNotificationIntent
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.compose.ComposeActivity
import com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeOptions
+import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.entity.AccountEntity
import com.keylesspalace.tusky.di.ApplicationScope
@@ -56,7 +57,6 @@ import com.keylesspalace.tusky.entity.visibleNotificationTypes
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver
import com.keylesspalace.tusky.settings.PrefKeys
-import com.keylesspalace.tusky.util.CryptoUtil
import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.unicodeWrap
import com.keylesspalace.tusky.viewdata.buildDescription
@@ -73,16 +73,20 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.unifiedpush.android.connector.UnifiedPush
+import org.unifiedpush.android.connector.data.PushEndpoint
+import org.unifiedpush.android.connector.ui.SelectDistributorDialogsBuilder
+import org.unifiedpush.android.connector.ui.UnifiedPushFunctions
import retrofit2.HttpException
@Singleton
-class NotificationService @Inject constructor(
+class NotificationHelper @Inject constructor(
private val notificationManager: NotificationManager,
private val accountManager: AccountManager,
private val api: MastodonApi,
private val preferences: SharedPreferences,
@ApplicationContext private val context: Context,
@ApplicationScope private val applicationScope: CoroutineScope,
+ private val instanceInfoRepository: InstanceInfoRepository
) {
private var workManager: WorkManager = WorkManager.getInstance(context)
@@ -648,7 +652,7 @@ class NotificationService @Inject constructor(
.putExtra(KEY_CITED_STATUS_ID, inReplyToId)
.putExtra(KEY_VISIBILITY, replyVisibility)
.putExtra(KEY_SPOILER, contentWarning)
- .putExtra(KEY_MENTIONS, mentionedUsernames.toTypedArray())
+ .putExtra(KEY_MENTIONS, mentionedUsernames.toTypedArray())
return PendingIntent.getBroadcast(
context.applicationContext,
@@ -768,13 +772,36 @@ class NotificationService @Inject constructor(
// make sure this is done in any inconsistent case (is not too often and doesn't hurt).
unregisterPushEndpoint(account)
- UnifiedPush.registerAppWithDialog(activity, account.id.toString(), features = arrayListOf(UnifiedPush.FEATURE_BYTES_MESSAGE))
- // Will lead to call of registerPushEndpoint()
+ val vapid = instanceInfoRepository.getUpdatedInstanceInfoOrFallback().vapidKey?.replace("=", "")
+
+ val builder = SelectDistributorDialogsBuilder(
+ activity,
+ object : UnifiedPushFunctions {
+ override fun getAckDistributor(): String? =
+ UnifiedPush.getAckDistributor(activity)
+
+ override fun getDistributors(): List =
+ UnifiedPush.getDistributors(activity)
+
+ override fun register(instance: String) =
+ UnifiedPush.register(activity, instance, vapid = vapid)
+
+ override fun saveDistributor(distributor: String) =
+ UnifiedPush.saveDistributor(activity, distributor)
+
+ override fun tryUseDefaultDistributor(callback: (Boolean) -> Unit) =
+ UnifiedPush.tryUseDefaultDistributor(activity, callback)
+ }
+ )
+ builder.instances = listOf(account.id.toString())
+ builder.mayUseDefault = false
+ builder.mayUseCurrent = false
+ builder.run()
}
}
private fun resetPushWhenDistributorIsMissing() {
- val lastUsedPushProvider = preferences.getString(PrefKeys.LAST_USED_PUSH_PROVDER, null)
+ val lastUsedPushProvider = preferences.getString(PrefKeys.LAST_USED_PUSH_PROVIDER, null)
// NOTE UnifiedPush.getSavedDistributor() cannot be used here as that is already null here if the
// distributor was uninstalled.
@@ -785,7 +812,7 @@ class NotificationService @Inject constructor(
Log.w(TAG, "Previous push provider ($lastUsedPushProvider) uninstalled. Resetting all accounts.")
preferences.edit {
- remove(PrefKeys.LAST_USED_PUSH_PROVDER)
+ remove(PrefKeys.LAST_USED_PUSH_PROVIDER)
}
applicationScope.launch {
@@ -836,7 +863,7 @@ class NotificationService @Inject constructor(
unregisterPushEndpoint(account)
// this probably does nothing (distributor to handle this is missing)
- UnifiedPush.unregisterApp(context, account.id.toString())
+ UnifiedPush.unregister(context, account.id.toString())
}
fun fetchNotificationsOnPushMessage(account: AccountEntity) {
@@ -857,26 +884,25 @@ class NotificationService @Inject constructor(
private fun buildAlertSubscriptionData(account: AccountEntity): Map =
buildAlertsMap(account).mapKeys { "data[alerts][${it.key}]" }
- // Called by UnifiedPush callback in UnifiedPushBroadcastReceiver
+ // Called by UnifiedPush callback in UnifiedPushService
suspend fun registerPushEndpoint(
account: AccountEntity,
- endpoint: String
+ endpoint: PushEndpoint
) = withContext(Dispatchers.IO) {
- // Generate a prime256v1 key pair for WebPush
- // Decryption is unimplemented for now, since Mastodon uses an old WebPush
- // standard which does not send needed information for decryption in the payload
- // This makes it not directly compatible with UnifiedPush
- // As of now, we use it purely as a way to trigger a pull
- val keyPair = CryptoUtil.generateECKeyPair(CryptoUtil.CURVE_PRIME256_V1)
- val auth = CryptoUtil.secureRandomBytesEncoded(16)
+ val pubKeySet = endpoint.pubKeySet
+ if (pubKeySet == null) {
+ Log.w(TAG, "cannot register push endpoint without public key")
+ return@withContext
+ }
api.subscribePushNotifications(
- "Bearer ${account.accessToken}",
- account.domain,
- endpoint,
- keyPair.pubkey,
- auth,
- buildAlertSubscriptionData(account)
+ auth = "Bearer ${account.accessToken}",
+ domain = account.domain,
+ standard = true,
+ endpoint = endpoint.url,
+ keysP256DH = pubKeySet.pubKey,
+ keysAuth = pubKeySet.auth,
+ data = buildAlertSubscriptionData(account)
).onFailure { throwable ->
Log.w(TAG, "Error setting push endpoint for account ${account.id}", throwable)
disablePushNotificationsForAccount(account)
@@ -885,11 +911,12 @@ class NotificationService @Inject constructor(
accountManager.updateAccount(account) {
copy(
- pushPubKey = keyPair.pubkey,
- pushPrivKey = keyPair.privKey,
- pushAuth = auth,
+ pushPubKey = pubKeySet.pubKey,
+ // TODO
+ pushPrivKey = "",
+ pushAuth = pubKeySet.auth,
pushServerKey = it.serverKey,
- unifiedPushUrl = endpoint
+ unifiedPushUrl = endpoint.url
)
}
@@ -897,7 +924,7 @@ class NotificationService @Inject constructor(
Log.d(TAG, "Saving distributor to preferences: $it")
preferences.edit {
- putString(PrefKeys.LAST_USED_PUSH_PROVDER, it)
+ putString(PrefKeys.LAST_USED_PUSH_PROVIDER, it)
}
// TODO once this is selected it cannot be changed (except by wiping the application or uninstalling the provider)
@@ -949,7 +976,7 @@ class NotificationService @Inject constructor(
}
companion object {
- const val TAG = "NotificationService"
+ const val TAG = "NotificationHelper"
const val KEY_CITED_STATUS_ID: String = "KEY_CITED_STATUS_ID"
const val KEY_MENTIONS: String = "KEY_MENTIONS"
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
index 660e90743d..5042942d22 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
+++ b/app/src/main/java/com/keylesspalace/tusky/db/AppDatabase.java
@@ -65,7 +65,7 @@
},
// Note: Starting with version 54, database versions in Tusky are always even.
// This is to reserve odd version numbers for use by forks.
- version = 70,
+ version = 72,
autoMigrations = {
@AutoMigration(from = 48, to = 49),
@AutoMigration(from = 49, to = 50, spec = AppDatabase.MIGRATION_49_50.class),
diff --git a/app/src/main/java/com/keylesspalace/tusky/db/entity/InstanceEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/entity/InstanceEntity.kt
index 840bf12999..9a336827f1 100644
--- a/app/src/main/java/com/keylesspalace/tusky/db/entity/InstanceEntity.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/db/entity/InstanceEntity.kt
@@ -45,7 +45,8 @@ data class InstanceEntity(
val mastodonApiVersion: Int?,
// ToDo: Remove this again when filter v1 support is dropped
- @ColumnInfo(defaultValue = "false") val filterV2Supported: Boolean = false
+ @ColumnInfo(defaultValue = "false") val filterV2Supported: Boolean = false,
+ val vapidKey: String?
)
@TypeConverters(Converters::class)
@@ -72,4 +73,5 @@ data class InstanceInfoEntity(
val maxFieldValueLength: Int?,
val translationEnabled: Boolean?,
val mastodonApiVersion: Int?,
+ val vapidKey: String?
)
diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt
index f75ea34dcb..cde8c16264 100644
--- a/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt
@@ -42,6 +42,7 @@ data class Instance(
@JsonClass(generateAdapter = true)
data class Configuration(
val urls: Urls? = null,
+ val vapid: VapidKey? = null,
val accounts: Accounts? = null,
val statuses: Statuses? = null,
@Json(name = "media_attachments") val mediaAttachments: MediaAttachments? = null,
@@ -51,6 +52,11 @@ data class Instance(
@JsonClass(generateAdapter = true)
data class Urls(@Json(name = "streaming_api") val streamingApi: String? = null)
+ @JsonClass(generateAdapter = true)
+ data class VapidKey(
+ @Json(name = "public_key") val publicKey: String? = null
+ )
+
@JsonClass(generateAdapter = true)
data class Accounts(
@Json(name = "max_featured_tags") val maxFeaturedTags: Int,
diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
index 44e3c80bc2..48db67e4b6 100644
--- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt
@@ -675,7 +675,8 @@ interface MastodonApi {
suspend fun subscribePushNotifications(
@Header("Authorization") auth: String,
@Header(DOMAIN_HEADER) domain: String,
- @Field("subscription[endpoint]") endPoint: String,
+ @Field("subscription[standard]") standard: Boolean,
+ @Field("subscription[endpoint]") endpoint: String,
@Field("subscription[keys][p256dh]") keysP256DH: String,
@Field("subscription[keys][auth]") keysAuth: String,
// The "data[alerts][]" fields to enable / disable notifications
diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationBlockStateBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationBlockStateBroadcastReceiver.kt
index 07f63fbd9b..fe29146889 100644
--- a/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationBlockStateBroadcastReceiver.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationBlockStateBroadcastReceiver.kt
@@ -20,7 +20,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
-import com.keylesspalace.tusky.components.systemnotifications.NotificationService
+import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.ApplicationScope
import com.keylesspalace.tusky.network.MastodonApi
@@ -38,7 +38,7 @@ class NotificationBlockStateBroadcastReceiver : BroadcastReceiver() {
lateinit var accountManager: AccountManager
@Inject
- lateinit var notificationService: NotificationService
+ lateinit var notificationHelper: NotificationHelper
@Inject
@ApplicationScope
@@ -46,7 +46,7 @@ class NotificationBlockStateBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Build.VERSION.SDK_INT < 28) return
- if (!notificationService.arePushNotificationsAvailable()) return
+ if (!notificationHelper.arePushNotificationsAvailable()) return
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -64,7 +64,7 @@ class NotificationBlockStateBroadcastReceiver : BroadcastReceiver() {
accountManager.getAccountByIdentifier(accountIdentifier)?.let { account ->
if (account.isPushNotificationsEnabled()) {
externalScope.launch {
- notificationService.updatePushSubscription(account)
+ notificationHelper.updatePushSubscription(account)
}
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt
index d504cc153d..ce6d79d128 100644
--- a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt
@@ -25,7 +25,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.systemnotifications.NotificationChannelData
-import com.keylesspalace.tusky.components.systemnotifications.NotificationService
+import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.service.SendStatusService
@@ -43,20 +43,20 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
@SuppressLint("MissingPermission")
override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == NotificationService.REPLY_ACTION) {
- val serverNotificationId = intent.getStringExtra(NotificationService.KEY_SERVER_NOTIFICATION_ID)
- val senderId = intent.getLongExtra(NotificationService.KEY_SENDER_ACCOUNT_ID, -1)
+ if (intent.action == NotificationHelper.REPLY_ACTION) {
+ val serverNotificationId = intent.getStringExtra(NotificationHelper.KEY_SERVER_NOTIFICATION_ID)
+ val senderId = intent.getLongExtra(NotificationHelper.KEY_SENDER_ACCOUNT_ID, -1)
val senderIdentifier = intent.getStringExtra(
- NotificationService.KEY_SENDER_ACCOUNT_IDENTIFIER
+ NotificationHelper.KEY_SENDER_ACCOUNT_IDENTIFIER
)!!
val senderFullName = intent.getStringExtra(
- NotificationService.KEY_SENDER_ACCOUNT_FULL_NAME
+ NotificationHelper.KEY_SENDER_ACCOUNT_FULL_NAME
)
- val citedStatusId = intent.getStringExtra(NotificationService.KEY_CITED_STATUS_ID)
+ val citedStatusId = intent.getStringExtra(NotificationHelper.KEY_CITED_STATUS_ID)
val visibility =
- intent.getSerializableExtraCompat(NotificationService.KEY_VISIBILITY)!!
- val spoiler = intent.getStringExtra(NotificationService.KEY_SPOILER).orEmpty()
- val mentions = intent.getStringArrayExtra(NotificationService.KEY_MENTIONS).orEmpty()
+ intent.getSerializableExtraCompat(NotificationHelper.KEY_VISIBILITY)!!
+ val spoiler = intent.getStringExtra(NotificationHelper.KEY_SPOILER).orEmpty()
+ val mentions = intent.getStringArrayExtra(NotificationHelper.KEY_MENTIONS).orEmpty()
val account = accountManager.getAccountById(senderId)
@@ -137,7 +137,7 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() {
private fun getReplyMessage(intent: Intent): CharSequence {
val remoteInput = RemoteInput.getResultsFromIntent(intent)
- return remoteInput?.getCharSequence(NotificationService.KEY_REPLY, "") ?: ""
+ return remoteInput?.getCharSequence(NotificationHelper.KEY_REPLY, "") ?: ""
}
companion object {
diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/UnifiedPushBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/UnifiedPushService.kt
similarity index 54%
rename from app/src/main/java/com/keylesspalace/tusky/receiver/UnifiedPushBroadcastReceiver.kt
rename to app/src/main/java/com/keylesspalace/tusky/receiver/UnifiedPushService.kt
index bb8feb0436..e2776076de 100644
--- a/app/src/main/java/com/keylesspalace/tusky/receiver/UnifiedPushBroadcastReceiver.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/receiver/UnifiedPushService.kt
@@ -15,60 +15,57 @@
package com.keylesspalace.tusky.receiver
-import android.content.Context
import android.util.Log
-import com.keylesspalace.tusky.components.systemnotifications.NotificationService
+import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.di.ApplicationScope
-import com.keylesspalace.tusky.network.MastodonApi
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
-import org.unifiedpush.android.connector.MessagingReceiver
+import org.unifiedpush.android.connector.FailedReason
+import org.unifiedpush.android.connector.PushService
+import org.unifiedpush.android.connector.data.PushEndpoint
+import org.unifiedpush.android.connector.data.PushMessage
@AndroidEntryPoint
-class UnifiedPushBroadcastReceiver : MessagingReceiver() {
+class UnifiedPushService : PushService() {
@Inject
lateinit var accountManager: AccountManager
@Inject
- lateinit var mastodonApi: MastodonApi
-
- @Inject
- lateinit var notificationService: NotificationService
+ lateinit var notificationHelper: NotificationHelper
@Inject
@ApplicationScope
lateinit var applicationScope: CoroutineScope
- override fun onMessage(context: Context, message: ByteArray, instance: String) {
- Log.d(TAG, "New message received for account $instance: #${message.size}")
- val account = accountManager.getAccountById(instance.toLong())
- account?.let {
- notificationService.fetchNotificationsOnPushMessage(it)
+ override fun onMessage(message: PushMessage, instance: String) {
+ Log.d(TAG, "New message received for account $instance: $message")
+ accountManager.getAccountById(instance.toLong())?.let { account ->
+ notificationHelper.fetchNotificationsOnPushMessage(account)
}
}
- override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
+ override fun onNewEndpoint(endpoint: PushEndpoint, instance: String) {
Log.d(TAG, "Endpoint available for account $instance: $endpoint")
- accountManager.getAccountById(instance.toLong())?.let {
- applicationScope.launch { notificationService.registerPushEndpoint(it, endpoint) }
+ accountManager.getAccountById(instance.toLong())?.let { account ->
+ applicationScope.launch { notificationHelper.registerPushEndpoint(account, endpoint) }
}
}
- override fun onRegistrationFailed(context: Context, instance: String) = Unit
+ override fun onRegistrationFailed(reason: FailedReason, instance: String) = Unit
- override fun onUnregistered(context: Context, instance: String) {
+ override fun onUnregistered(instance: String) {
Log.d(TAG, "Endpoint unregistered for account $instance")
- accountManager.getAccountById(instance.toLong())?.let {
- // It's fine if the account does not exist anymore -- that means it has been logged out
- // TODO its not: this is the Mastodon side and should be done (unregistered)
- applicationScope.launch { notificationService.unregisterPushEndpoint(it) }
+ accountManager.getAccountById(instance.toLong())?.let { account ->
+ // It's fine if the account does not exist anymore -- that means it has been logged out,
+ // which removes the subscription anyway
+ applicationScope.launch { notificationHelper.unregisterPushEndpoint(account) }
}
}
companion object {
- const val TAG = "UnifiedPushBroadcastReceiver"
+ const val TAG = "UnifiedPushService"
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
index a1be2406c8..1ff08d9f84 100644
--- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsConstants.kt
@@ -55,7 +55,7 @@ object PrefKeys {
// each preference a key for it to work.
const val SCHEMA_VERSION: String = "schema_version"
- const val LAST_USED_PUSH_PROVDER = "lastUsedPushProvider"
+ const val LAST_USED_PUSH_PROVIDER = "lastUsedPushProvider"
const val APP_THEME = "appTheme"
const val LANGUAGE = "language"
diff --git a/app/src/main/java/com/keylesspalace/tusky/usecase/LogoutUsecase.kt b/app/src/main/java/com/keylesspalace/tusky/usecase/LogoutUsecase.kt
index e8e0db1e33..a946136c72 100644
--- a/app/src/main/java/com/keylesspalace/tusky/usecase/LogoutUsecase.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/usecase/LogoutUsecase.kt
@@ -1,7 +1,7 @@
package com.keylesspalace.tusky.usecase
import com.keylesspalace.tusky.components.drafts.DraftHelper
-import com.keylesspalace.tusky.components.systemnotifications.NotificationService
+import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.DatabaseCleaner
import com.keylesspalace.tusky.db.entity.AccountEntity
@@ -15,7 +15,7 @@ class LogoutUsecase @Inject constructor(
private val accountManager: AccountManager,
private val draftHelper: DraftHelper,
private val shareShortcutHelper: ShareShortcutHelper,
- private val notificationService: NotificationService,
+ private val notificationHelper: NotificationHelper,
) {
/**
@@ -35,7 +35,7 @@ class LogoutUsecase @Inject constructor(
)
}
- notificationService.disableNotificationsForAccount(account)
+ notificationHelper.disableNotificationsForAccount(account)
// remove account from local AccountManager
val otherAccountAvailable = accountManager.remove(account) != null
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CryptoUtil.kt b/app/src/main/java/com/keylesspalace/tusky/util/CryptoUtil.kt
deleted file mode 100644
index d2fe7d5706..0000000000
--- a/app/src/main/java/com/keylesspalace/tusky/util/CryptoUtil.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/* Copyright 2022 Tusky contributors
- *
- * This file is a part of Tusky.
- *
- * This program is free software; you can redistribute it and/or modify it under the terms of the
- * GNU General Public License as published by the Free Software Foundation; either version 3 of the
- * License, or (at your option) any later version.
- *
- * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
- * Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with Tusky; if not,
- * see . */
-
-package com.keylesspalace.tusky.util
-
-import android.util.Base64
-import java.security.KeyPairGenerator
-import java.security.SecureRandom
-import java.security.Security
-import org.bouncycastle.jce.ECNamedCurveTable
-import org.bouncycastle.jce.interfaces.ECPrivateKey
-import org.bouncycastle.jce.interfaces.ECPublicKey
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-
-object CryptoUtil {
- const val CURVE_PRIME256_V1 = "prime256v1"
-
- private const val BASE64_FLAGS = Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP
-
- init {
- Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
- Security.addProvider(BouncyCastleProvider())
- }
-
- private fun secureRandomBytes(len: Int): ByteArray {
- val ret = ByteArray(len)
- SecureRandom.getInstance("SHA1PRNG").nextBytes(ret)
- return ret
- }
-
- fun secureRandomBytesEncoded(len: Int): String {
- return Base64.encodeToString(secureRandomBytes(len), BASE64_FLAGS)
- }
-
- data class EncodedKeyPair(val pubkey: String, val privKey: String)
-
- fun generateECKeyPair(curve: String): EncodedKeyPair {
- val spec = ECNamedCurveTable.getParameterSpec(curve)
- val gen = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME)
- gen.initialize(spec)
- val keyPair = gen.genKeyPair()
- val pubKey = keyPair.public as ECPublicKey
- val privKey = keyPair.private as ECPrivateKey
- val encodedPubKey = Base64.encodeToString(pubKey.q.getEncoded(false), BASE64_FLAGS)
- val encodedPrivKey = Base64.encodeToString(privKey.d.toByteArray(), BASE64_FLAGS)
- return EncodedKeyPair(encodedPubKey, encodedPrivKey)
- }
-}
diff --git a/app/src/main/java/com/keylesspalace/tusky/worker/NotificationWorker.kt b/app/src/main/java/com/keylesspalace/tusky/worker/NotificationWorker.kt
index 7534b05b13..60826dfda8 100644
--- a/app/src/main/java/com/keylesspalace/tusky/worker/NotificationWorker.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/worker/NotificationWorker.kt
@@ -25,7 +25,7 @@ import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.systemnotifications.NotificationFetcher
-import com.keylesspalace.tusky.components.systemnotifications.NotificationService
+import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
@@ -34,9 +34,9 @@ class NotificationWorker @AssistedInject constructor(
@Assisted appContext: Context,
@Assisted params: WorkerParameters,
private val notificationsFetcher: NotificationFetcher,
- notificationService: NotificationService,
+ notificationHelper: NotificationHelper,
) : CoroutineWorker(appContext, params) {
- val notification: Notification = notificationService.createWorkerNotification(
+ val notification: Notification = notificationHelper.createWorkerNotification(
R.string.notification_notification_worker
)
@@ -47,7 +47,7 @@ class NotificationWorker @AssistedInject constructor(
}
override suspend fun getForegroundInfo() = ForegroundInfo(
- NotificationService.NOTIFICATION_ID_FETCH_NOTIFICATION,
+ NotificationHelper.NOTIFICATION_ID_FETCH_NOTIFICATION,
notification
)
diff --git a/app/src/main/java/com/keylesspalace/tusky/worker/PruneCacheWorker.kt b/app/src/main/java/com/keylesspalace/tusky/worker/PruneCacheWorker.kt
index 5c03bdb04b..47267a95f8 100644
--- a/app/src/main/java/com/keylesspalace/tusky/worker/PruneCacheWorker.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/worker/PruneCacheWorker.kt
@@ -25,7 +25,7 @@ import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import com.keylesspalace.tusky.R
-import com.keylesspalace.tusky.components.systemnotifications.NotificationService
+import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.DatabaseCleaner
import com.keylesspalace.tusky.util.deleteStaleCachedMedia
@@ -39,9 +39,9 @@ class PruneCacheWorker @AssistedInject constructor(
@Assisted workerParams: WorkerParameters,
private val databaseCleaner: DatabaseCleaner,
private val accountManager: AccountManager,
- val notificationService: NotificationService,
+ val notificationHelper: NotificationHelper,
) : CoroutineWorker(appContext, workerParams) {
- val notification: Notification = notificationService.createWorkerNotification(
+ val notification: Notification = notificationHelper.createWorkerNotification(
R.string.notification_prune_cache
)
@@ -57,7 +57,7 @@ class PruneCacheWorker @AssistedInject constructor(
}
override suspend fun getForegroundInfo() = ForegroundInfo(
- NotificationService.NOTIFICATION_ID_PRUNE_CACHE,
+ NotificationHelper.NOTIFICATION_ID_PRUNE_CACHE,
notification
)
diff --git a/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt
index f9058f2fb0..8f8c735bef 100644
--- a/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt
+++ b/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt
@@ -13,7 +13,7 @@ import androidx.work.testing.WorkManagerTestInitHelper
import at.connyduck.calladapter.networkresult.NetworkResult
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.components.accountlist.AccountListActivity
-import com.keylesspalace.tusky.components.systemnotifications.NotificationService
+import com.keylesspalace.tusky.components.systemnotifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.entity.AccountEntity
import com.keylesspalace.tusky.entity.Account
@@ -99,7 +99,7 @@ class MainActivityTest {
val notificationManager = context.getSystemService(NotificationManager::class.java)
val shadowNotificationManager = shadowOf(notificationManager)
- val notificationService = NotificationService(
+ val notificationHelper = NotificationHelper(
notificationManager,
mock {
on { areNotificationsEnabled() } doReturn true
@@ -110,10 +110,10 @@ class MainActivityTest {
mock(),
)
- notificationService.createNotificationChannelsForAccount(accountEntity)
+ notificationHelper.createNotificationChannelsForAccount(accountEntity)
runInBackground {
- val notification = notificationService.createBaseNotification(
+ val notification = notificationHelper.createBaseNotification(
Notification(
type = type,
id = "id",
@@ -167,7 +167,7 @@ class MainActivityTest {
eventHub = eventHub,
accountManager = accountManager,
shareShortcutHelper = mock(),
- notificationService = mock(),
+ notificationHelper = mock(),
)
val testViewModelFactory = viewModelFactory {
initializer { viewModel }
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f9e4ebd6d8..c3c52f04dc 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -47,7 +47,8 @@ robolectric = "4.14.1"
sparkbutton = "4.2.0"
touchimageview = "3.7.1"
turbine = "1.2.0"
-unified-push = "2.4.0"
+unified-push-connector = "3.0.9"
+unified-push-connector-ui = "1.1.0"
xmlwriter = "1.0.4"
[plugins]
@@ -127,7 +128,8 @@ robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectr
sparkbutton = { module = "at.connyduck.sparkbutton:sparkbutton", version.ref = "sparkbutton" }
touchimageview = { module = "com.github.MikeOrtiz:TouchImageView", version.ref = "touchimageview" }
turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
-unified-push = { module = "com.github.UnifiedPush:android-connector", version.ref = "unified-push" }
+unified-push-connector = { module = "org.unifiedpush.android:connector", version.ref = "unified-push-connector" }
+unified-push-connector-ui = { module = "org.unifiedpush.android:connector-ui", version.ref = "unified-push-connector-ui" }
xmlwriter = { module = "org.pageseeder.xmlwriter:pso-xmlwriter", version.ref = "xmlwriter" }
[bundles]
@@ -146,3 +148,4 @@ okhttp = ["okhttp-core", "okhttp-logging-interceptor"]
retrofit = ["retrofit-core", "retrofit-converter-moshi"]
room = ["androidx-room-ktx", "androidx-room-paging"]
xmldiff = ["diffx", "xmlwriter"]
+unifiedpush = ["unified-push-connector", "unified-push-connector-ui"]
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 500ae4e912..4139686ab7 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -6387,12 +6387,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -13068,6 +13068,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -14505,6 +14521,14 @@
+
+
+
+
+
+
+
+
@@ -14573,6 +14597,11 @@
+
+
+
+
+
@@ -14850,6 +14879,11 @@
+
+
+
+
+
@@ -14866,6 +14900,14 @@
+
+
+
+
+
+
+
+
@@ -14900,6 +14942,11 @@
+
+
+
+
+
@@ -20354,6 +20401,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+