diff --git a/app/build.gradle b/app/build.gradle
index 9192d48c56..db9c42d43a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -133,6 +133,7 @@ android {
apply plugin: 'com.google.gms.google-services'
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
+ buildConfigField "String", "DEVICE", "\"android\""
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
}
@@ -140,6 +141,7 @@ android {
dimension "distribution"
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
+ buildConfigField "String", "DEVICE", "\"huawei\""
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
}
@@ -147,6 +149,7 @@ android {
dimension "distribution"
ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
+ buildConfigField "String", "DEVICE", "\"android\""
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
}
}
diff --git a/app/src/huawei/AndroidManifest.xml b/app/src/huawei/AndroidManifest.xml
new file mode 100644
index 0000000000..4745c454b2
--- /dev/null
+++ b/app/src/huawei/AndroidManifest.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt
index 17c40ea826..e10ed77abf 100644
--- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt
+++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt
@@ -21,7 +21,7 @@ object HuaweiPushModule {
@Module
@InstallIn(SingletonComponent::class)
-abstract class FirebaseBindingModule {
+abstract class HuaweiBindingModule {
@Binds
- abstract fun bindPushManager(firebasePushManager: HuaweiPushManager): PushManager
-}
\ No newline at end of file
+ abstract fun bindPushManager(pushManager: HuaweiPushManager): PushManager
+}
diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt
new file mode 100644
index 0000000000..dd475c8344
--- /dev/null
+++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt
@@ -0,0 +1,59 @@
+package org.thoughtcrime.securesms.notifications
+
+import android.os.Bundle
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import com.huawei.hms.push.HmsMessageService
+import com.huawei.hms.push.RemoteMessage
+import dagger.hilt.android.AndroidEntryPoint
+import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
+import org.session.libsession.messaging.jobs.JobQueue
+import org.session.libsession.messaging.jobs.MessageReceiveParameters
+import org.session.libsession.messaging.utilities.MessageWrapper
+import org.session.libsession.utilities.TextSecurePreferences
+import org.session.libsignal.utilities.Base64
+import org.session.libsignal.utilities.Log
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class HuaweiPushNotificationService: HmsMessageService() {
+
+ @Inject token
+
+ override fun onNewToken(token: String?, bundle: Bundle?) {
+ Log.d("Loki", "New HCM token: $token.")
+
+ if (!token.isNullOrEmpty()) {
+ val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return
+ PushManager.register(token, userPublicKey, this, false)
+ }
+ }
+
+ override fun onMessageReceived(message: RemoteMessage?) {
+ Log.d("Loki", "Received a push notification.")
+ val base64EncodedData = message?.data
+ val data = base64EncodedData?.let { Base64.decode(it) }
+ if (data != null) {
+ try {
+ val envelopeAsData = MessageWrapper.unwrap(data).toByteArray()
+ val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null)
+ JobQueue.shared.add(job)
+ } catch (e: Exception) {
+ Log.d("Loki", "Failed to unwrap data for message due to error: $e.")
+ }
+ } else {
+ Log.d("Loki", "Failed to decode data for message.")
+ val builder = NotificationCompat.Builder(this, NotificationChannels.OTHER)
+ .setSmallIcon(network.loki.messenger.R.drawable.ic_notification)
+ .setColor(this.getResources().getColor(network.loki.messenger.R.color.textsecure_primary))
+ .setContentTitle("Session")
+ .setContentText("You've got a new message.")
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true)
+ with(NotificationManagerCompat.from(this)) {
+ notify(11111, builder.build())
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt
new file mode 100644
index 0000000000..be4832104d
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt
@@ -0,0 +1,107 @@
+package org.thoughtcrime.securesms.notifications
+
+import android.content.Context
+import androidx.core.app.NotificationCompat
+import androidx.core.app.NotificationManagerCompat
+import com.goterl.lazysodium.LazySodiumAndroid
+import com.goterl.lazysodium.SodiumAndroid
+import com.goterl.lazysodium.interfaces.AEAD
+import com.goterl.lazysodium.utils.Key
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
+import org.session.libsession.messaging.jobs.JobQueue
+import org.session.libsession.messaging.jobs.MessageReceiveParameters
+import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata
+import org.session.libsession.messaging.utilities.MessageWrapper
+import org.session.libsession.messaging.utilities.SodiumUtilities
+import org.session.libsession.utilities.bencode.Bencode
+import org.session.libsession.utilities.bencode.BencodeList
+import org.session.libsession.utilities.bencode.BencodeString
+import org.session.libsignal.utilities.Base64
+import org.session.libsignal.utilities.Log
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
+import javax.inject.Inject
+
+private const val TAG = "PushHandler"
+
+class PushHandler @Inject constructor(@ApplicationContext val context: Context) {
+ private val sodium = LazySodiumAndroid(SodiumAndroid())
+
+ fun onPush(dataMap: Map) {
+ val data: ByteArray? = if (dataMap.containsKey("spns")) {
+ // this is a v2 push notification
+ try {
+ decrypt(Base64.decode(dataMap["enc_payload"]))
+ } catch(e: Exception) {
+ Log.e(TAG, "Invalid push notification: ${e.message}")
+ return
+ }
+ } else {
+ // old v1 push notification; we still need this for receiving legacy closed group notifications
+ dataMap.get("ENCRYPTED_DATA")?.let(Base64::decode)
+ }
+ data?.let { onPush(data) } ?: onPush()
+
+ }
+
+ fun onPush() {
+ Log.d(TAG, "Failed to decode data for message.")
+ val builder = NotificationCompat.Builder(context, NotificationChannels.OTHER)
+ .setSmallIcon(network.loki.messenger.R.drawable.ic_notification)
+ .setColor(context.getColor(network.loki.messenger.R.color.textsecure_primary))
+ .setContentTitle("Session")
+ .setContentText("You've got a new message.")
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true)
+ NotificationManagerCompat.from(context).notify(11111, builder.build())
+ }
+
+ fun onPush(data: ByteArray) {
+ try {
+ val envelopeAsData = MessageWrapper.unwrap(data).toByteArray()
+ val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null)
+ JobQueue.shared.add(job)
+ } catch (e: Exception) {
+ Log.d(TAG, "Failed to unwrap data for message due to error: $e.")
+ }
+ }
+
+ fun decrypt(encPayload: ByteArray): ByteArray? {
+ Log.d(TAG, "decrypt() called")
+
+ val encKey = getOrCreateNotificationKey()
+ val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
+ val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
+ val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce)
+ ?: error("Failed to decrypt push notification")
+ val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray()
+ val bencoded = Bencode.Decoder(decrypted)
+ val expectedList = (bencoded.decode() as? BencodeList)?.values
+ ?: error("Failed to decode bencoded list from payload")
+
+ val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata")
+ val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson))
+
+ val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null
+ // null content is valid only if we got a "data_too_long" flag
+ if (content == null)
+ check(metadata.data_too_long) { "missing message data, but no too-long flag" }
+ else
+ check(metadata.data_len == content.size) { "wrong message data size" }
+
+ Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B")
+
+ return content
+ }
+
+ fun getOrCreateNotificationKey(): Key {
+ if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) {
+ // generate the key and store it
+ val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF)
+ IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString)
+ }
+ return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY))
+ }
+}
diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt
similarity index 100%
rename from app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt
rename to app/src/main/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt
diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt
similarity index 100%
rename from app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt
rename to app/src/main/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt
diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt
index 89712c2e85..df15efe80c 100644
--- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt
+++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt
@@ -40,6 +40,7 @@ import org.session.libsignal.utilities.emptyPromise
import org.session.libsignal.utilities.retryIfNeeded
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
+import javax.inject.Inject
private const val TAG = "FirebasePushManager"
@@ -47,7 +48,7 @@ class FirebasePushManager(
private val context: Context
): PushManager {
- private val pushManagerV2 = PushManagerV2(context)
+ @Inject lateinit var pushManagerV2: PushManagerV2
companion object {
const val maxRetryCount = 4
@@ -132,6 +133,4 @@ class FirebasePushManager(
} success {
tokenManager.fcmToken = null
}
-
- fun decrypt(decode: ByteArray) = pushManagerV2.decrypt(decode)
}
diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt
index 66c7590e5f..4fe885758f 100644
--- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt
+++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt
@@ -39,7 +39,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
private const val TAG = "PushManagerV2"
-class PushManagerV2(private val context: Context) {
+class PushManagerV2(private val pushHandler: PushHandler) {
private val sodium = LazySodiumAndroid(SodiumAndroid())
fun register(
@@ -48,7 +48,7 @@ class PushManagerV2(private val context: Context) {
userEd25519Key: KeyPair,
namespaces: List
): Promise {
- val pnKey = getOrCreateNotificationKey()
+ val pnKey = pushHandler.getOrCreateNotificationKey()
val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s
// if we want to support passing namespace list, here is the place to do it
@@ -117,41 +117,4 @@ class PushManagerV2(private val context: Context) {
.also { if (it.isFailure()) throw Exception("error: ${it.message}.") }
}
}
-
- private fun getOrCreateNotificationKey(): Key {
- if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) {
- // generate the key and store it
- val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF)
- IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString)
- }
- return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY))
- }
-
- fun decrypt(encPayload: ByteArray): ByteArray? {
- Log.d(TAG, "decrypt() called")
-
- val encKey = getOrCreateNotificationKey()
- val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
- val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
- val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce)
- ?: error("Failed to decrypt push notification")
- val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray()
- val bencoded = Bencode.Decoder(decrypted)
- val expectedList = (bencoded.decode() as? BencodeList)?.values
- ?: error("Failed to decode bencoded list from payload")
-
- val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata")
- val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson))
-
- val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null
- // null content is valid only if we got a "data_too_long" flag
- if (content == null)
- check(metadata.data_too_long) { "missing message data, but no too-long flag" }
- else
- check(metadata.data_len == content.size) { "wrong message data size" }
-
- Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B")
-
- return content
- }
}
diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt
index 4c6ce9f4ea..e546053fce 100644
--- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt
+++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt
@@ -8,7 +8,6 @@ import dagger.hilt.android.AndroidEntryPoint
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveParameters
-import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1
import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Base64
@@ -21,6 +20,7 @@ private const val TAG = "PushNotificationService"
class PushNotificationService : FirebaseMessagingService() {
@Inject lateinit var pushManager: FirebasePushManager
+ @Inject lateinit var pushHandler: PushHandler
override fun onNewToken(token: String) {
super.onNewToken(token)
@@ -32,37 +32,7 @@ class PushNotificationService : FirebaseMessagingService() {
override fun onMessageReceived(message: RemoteMessage) {
Log.d(TAG, "Received a push notification.")
- val data: ByteArray? = if (message.data.containsKey("spns")) {
- // this is a v2 push notification
- try {
- pushManager.decrypt(Base64.decode(message.data["enc_payload"]))
- } catch(e: Exception) {
- Log.e(TAG, "Invalid push notification: ${e.message}")
- return
- }
- } else {
- // old v1 push notification; we still need this for receiving legacy closed group notifications
- message.data?.get("ENCRYPTED_DATA")?.let(Base64::decode)
- }
- if (data != null) {
- try {
- val envelopeAsData = MessageWrapper.unwrap(data).toByteArray()
- val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null)
- JobQueue.shared.add(job)
- } catch (e: Exception) {
- Log.d(TAG, "Failed to unwrap data for message due to error: $e.")
- }
- } else {
- Log.d(TAG, "Failed to decode data for message.")
- val builder = NotificationCompat.Builder(this, NotificationChannels.OTHER)
- .setSmallIcon(network.loki.messenger.R.drawable.ic_notification)
- .setColor(resources.getColor(network.loki.messenger.R.color.textsecure_primary))
- .setContentTitle("Session")
- .setContentText("You've got a new message.")
- .setPriority(NotificationCompat.PRIORITY_DEFAULT)
- .setAutoCancel(true)
- NotificationManagerCompat.from(this).notify(11111, builder.build())
- }
+ pushHandler.onPush(message.data)
}
override fun onDeletedMessages() {
diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt
index e112cd2682..1533002d6c 100644
--- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt
+++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt
@@ -52,10 +52,13 @@ object PushManagerV1 {
)
val url = "${server.url}/register_legacy_groups_only"
- val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
+ val body = RequestBody.create(
+ MediaType.get("application/json"),
+ JsonUtil.toJson(parameters)
+ )
val request = Request.Builder().url(url).post(body).build()
- return sendOnionRequest(request) sideEffect { response ->
+ return sendOnionRequest(request) sideEffect { response ->
when (response.code) {
null, 0 -> throw Exception("error: ${response.message}.")
}
@@ -73,7 +76,7 @@ object PushManagerV1 {
val token = TextSecurePreferences.getFCMToken(context) ?: emptyPromise()
return retryIfNeeded(maxRetryCount) {
- val parameters = mapOf( "token" to token )
+ val parameters = mapOf("token" to token)
val url = "${server.url}/unregister"
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
val request = Request.Builder().url(url).post(body).build()
@@ -110,7 +113,7 @@ object PushManagerV1 {
closedGroupPublicKey: String,
publicKey: String
): Promise<*, Exception> {
- val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey )
+ val parameters = mapOf("closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey)
val url = "${server.url}/$operation"
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
val request = Request.Builder().url(url).post(body).build()
@@ -118,7 +121,7 @@ object PushManagerV1 {
return retryIfNeeded(maxRetryCount) {
sendOnionRequest(request) sideEffect {
when (it.code) {
- 0, null -> throw Exception(it.message)
+ 0, null -> throw Exception(it.message)
}
}
}