mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-22 16:07:30 +00:00
...
This commit is contained in:
parent
01e9d15872
commit
34990b13d3
@ -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\""
|
||||
}
|
||||
}
|
||||
|
16
app/src/huawei/AndroidManifest.xml
Normal file
16
app/src/huawei/AndroidManifest.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application tools:node="merge">
|
||||
<service
|
||||
android:name="org.thoughtcrime.securesms.notifications.HuaweiPushNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -21,7 +21,7 @@ object HuaweiPushModule {
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class FirebaseBindingModule {
|
||||
abstract class HuaweiBindingModule {
|
||||
@Binds
|
||||
abstract fun bindPushManager(firebasePushManager: HuaweiPushManager): PushManager
|
||||
abstract fun bindPushManager(pushManager: HuaweiPushManager): PushManager
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<String, String>) {
|
||||
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))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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<Int>
|
||||
): Promise<SubscriptionResponse, Exception> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -52,7 +52,10 @@ 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 ->
|
||||
|
Loading…
x
Reference in New Issue
Block a user