Connect Huawei push notifications

This commit is contained in:
Andrew 2023-07-26 10:48:20 +09:30
parent 34990b13d3
commit 55216875ac
20 changed files with 231 additions and 215 deletions

View File

@ -133,7 +133,7 @@ android {
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
ext.websiteUpdateUrl = "null" ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false" buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "DEVICE", "\"android\"" buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.ANDROID"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
} }
@ -141,7 +141,7 @@ android {
dimension "distribution" dimension "distribution"
ext.websiteUpdateUrl = "null" ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
buildConfigField "String", "DEVICE", "\"huawei\"" buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.HUAWEI"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
} }
@ -149,7 +149,7 @@ android {
dimension "distribution" dimension "distribution"
ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases" ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
buildConfigField "String", "DEVICE", "\"android\"" buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.ANDROID"
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\"" buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
} }
} }

View File

@ -1,9 +1,29 @@
package org.thoughtcrime.securesms.notifications package org.thoughtcrime.securesms.notifications
import android.content.Context import android.content.Context
import com.huawei.hms.aaid.HmsInstanceId
import kotlinx.coroutines.Job
class HuaweiPushManager(val context: Context): PushManager { class HuaweiPushManager(val context: Context): PushManager {
override fun refresh(force: Boolean) { private var huaweiPushInstanceIdJob: Job? = null
@Synchronized
override fun refresh(force: Boolean) {
val huaweiPushInstanceIdJob = huaweiPushInstanceIdJob
huaweiPushInstanceIdJob?.apply {
if (force) cancel() else if (isActive) return
}
val hmsInstanceId = HmsInstanceId.getInstance(context)
val task = hmsInstanceId.aaid
// HuaweiPushNotificationService().start()
//
// huaweiPushInstanceIdJob = HmsInstanceId.getInstance(this) { hmsInstanceId ->
// RegisterHuaweiPushService(hmsInstanceId, this, force).start()
// Unit.INSTANCE
// }
} }
} }

View File

@ -18,42 +18,19 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class HuaweiPushNotificationService: HmsMessageService() { class HuaweiPushNotificationService: HmsMessageService() {
@Inject token @Inject lateinit var pushManager: PushManager
@Inject lateinit var pushHandler: PushHandler
override fun onNewToken(token: String?, bundle: Bundle?) { override fun onNewToken(token: String?, bundle: Bundle?) {
Log.d("Loki", "New HCM token: $token.") Log.d("Loki", "New HCM token: $token.")
pushManager.refresh(true)
if (!token.isNullOrEmpty()) {
val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return
PushManager.register(token, userPublicKey, this, false)
}
} }
override fun onMessageReceived(message: RemoteMessage?) { override fun onMessageReceived(message: RemoteMessage?) {
Log.d("Loki", "Received a push notification.") pushHandler.onPush(message?.dataOfMap)
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())
}
}
} }
} override fun onDeletedMessages() {
pushManager.refresh(true)
}
}

View File

@ -41,6 +41,7 @@ import org.session.libsession.messaging.sending_receiving.pollers.Poller;
import org.session.libsession.snode.SnodeModule; import org.session.libsession.snode.SnodeModule;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.ConfigFactoryUpdateListener; import org.session.libsession.utilities.ConfigFactoryUpdateListener;
import org.session.libsession.utilities.Device;
import org.session.libsession.utilities.ProfilePictureUtilities; import org.session.libsession.utilities.ProfilePictureUtilities;
import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.TextSecurePreferences;
@ -142,6 +143,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
@Inject LokiAPIDatabase lokiAPIDatabase; @Inject LokiAPIDatabase lokiAPIDatabase;
@Inject public Storage storage; @Inject public Storage storage;
@Inject Device device;
@Inject MessageDataProvider messageDataProvider; @Inject MessageDataProvider messageDataProvider;
@Inject TextSecurePreferences textSecurePreferences; @Inject TextSecurePreferences textSecurePreferences;
@Inject PushManager pushManager; @Inject PushManager pushManager;
@ -207,8 +209,10 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
DatabaseModule.init(this); DatabaseModule.init(this);
MessagingModuleConfiguration.configure(this); MessagingModuleConfiguration.configure(this);
super.onCreate(); super.onCreate();
messagingModuleConfiguration = new MessagingModuleConfiguration(this, messagingModuleConfiguration = new MessagingModuleConfiguration(
this,
storage, storage,
device,
messageDataProvider, messageDataProvider,
()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this), ()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this),
configFactory configFactory

View File

@ -0,0 +1,16 @@
package org.thoughtcrime.securesms
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import network.loki.messenger.BuildConfig
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DeviceModule {
@Provides
@Singleton
fun provides() = BuildConfig.DEVICE
}

View File

@ -21,6 +21,7 @@ import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.groupSizeLimit import org.session.libsession.messaging.sending_receiving.groupSizeLimit
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Device
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.contacts.SelectContactsAdapter import org.thoughtcrime.securesms.contacts.SelectContactsAdapter
@ -31,10 +32,14 @@ import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.fadeIn import org.thoughtcrime.securesms.util.fadeIn
import org.thoughtcrime.securesms.util.fadeOut import org.thoughtcrime.securesms.util.fadeOut
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class CreateGroupFragment : Fragment() { class CreateGroupFragment : Fragment() {
@Inject
lateinit var device: Device
private lateinit var binding: FragmentCreateGroupBinding private lateinit var binding: FragmentCreateGroupBinding
private val viewModel: CreateGroupViewModel by viewModels() private val viewModel: CreateGroupViewModel by viewModels()
@ -86,7 +91,7 @@ class CreateGroupFragment : Fragment() {
val userPublicKey = TextSecurePreferences.getLocalNumber(requireContext())!! val userPublicKey = TextSecurePreferences.getLocalNumber(requireContext())!!
isLoading = true isLoading = true
binding.loaderContainer.fadeIn() binding.loaderContainer.fadeIn()
MessageSender.createClosedGroup(name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID -> MessageSender.createClosedGroup(device, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
binding.loaderContainer.fadeOut() binding.loaderContainer.fadeOut()
isLoading = false isLoading = false
val threadID = DatabaseComponent.get(requireContext()).threadDatabase().getOrCreateThreadIdFor(Recipient.from(requireContext(), Address.fromSerialized(groupID), false)) val threadID = DatabaseComponent.get(requireContext()).threadDatabase().getOrCreateThreadIdFor(Recipient.from(requireContext(), Address.fromSerialized(groupID), false))

View File

@ -29,24 +29,26 @@ private const val TAG = "PushHandler"
class PushHandler @Inject constructor(@ApplicationContext val context: Context) { class PushHandler @Inject constructor(@ApplicationContext val context: Context) {
private val sodium = LazySodiumAndroid(SodiumAndroid()) private val sodium = LazySodiumAndroid(SodiumAndroid())
fun onPush(dataMap: Map<String, String>) { fun onPush(dataMap: Map<String, String>?) {
val data: ByteArray? = if (dataMap.containsKey("spns")) { onPush(dataMap?.asByteArray())
// 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() { private fun onPush(data: ByteArray?) {
if (data == null) {
onPush()
return
}
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.")
}
}
private fun onPush() {
Log.d(TAG, "Failed to decode data for message.") Log.d(TAG, "Failed to decode data for message.")
val builder = NotificationCompat.Builder(context, NotificationChannels.OTHER) val builder = NotificationCompat.Builder(context, NotificationChannels.OTHER)
.setSmallIcon(network.loki.messenger.R.drawable.ic_notification) .setSmallIcon(network.loki.messenger.R.drawable.ic_notification)
@ -58,15 +60,20 @@ class PushHandler @Inject constructor(@ApplicationContext val context: Context)
NotificationManagerCompat.from(context).notify(11111, builder.build()) NotificationManagerCompat.from(context).notify(11111, builder.build())
} }
fun onPush(data: ByteArray) { private fun Map<String, String>.asByteArray() =
try { when {
val envelopeAsData = MessageWrapper.unwrap(data).toByteArray() // this is a v2 push notification
val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null) containsKey("spns") -> {
JobQueue.shared.add(job) try {
} catch (e: Exception) { decrypt(Base64.decode(this["enc_payload"]))
Log.d(TAG, "Failed to unwrap data for message due to error: $e.") } catch (e: Exception) {
Log.e(TAG, "Invalid push notification: ${e.message}")
null
}
}
// old v1 push notification; we still need this for receiving legacy closed group notifications
else -> this["ENCRYPTED_DATA"]?.let(Base64::decode)
} }
}
fun decrypt(encPayload: ByteArray): ByteArray? { fun decrypt(encPayload: ByteArray): ByteArray? {
Log.d(TAG, "decrypt() called") Log.d(TAG, "decrypt() called")
@ -84,14 +91,18 @@ class PushHandler @Inject constructor(@ApplicationContext val context: Context)
val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata")
val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson))
val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null 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 // null content is valid only if we got a "data_too_long" flag
if (content == null) if (content == null)
check(metadata.data_too_long) { "missing message data, but no too-long flag" } check(metadata.data_too_long) { "missing message data, but no too-long flag" }
else else
check(metadata.data_len == content.size) { "wrong message data size" } 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") Log.d(
TAG,
"Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B"
)
return content return content
} }
@ -102,6 +113,11 @@ class PushHandler @Inject constructor(@ApplicationContext val context: Context)
val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF)
IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString)
} }
return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY)) return Key.fromHexString(
IdentityKeyUtil.retrieve(
context,
IdentityKeyUtil.NOTIFICATION_KEY
)
)
} }
} }

View File

@ -1,60 +1,22 @@
package org.thoughtcrime.securesms.notifications package org.thoughtcrime.securesms.notifications
import android.content.Context import android.content.Context
import com.goterl.lazysodium.LazySodiumAndroid import dagger.hilt.android.qualifiers.ApplicationContext
import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.interfaces.AEAD
import com.goterl.lazysodium.interfaces.Sign
import com.goterl.lazysodium.utils.Key
import com.goterl.lazysodium.utils.KeyPair
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.serialization.decodeFromString import org.session.libsession.utilities.Device
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.combine.and
import nl.komponents.kovenant.functional.map
import okhttp3.MediaType
import okhttp3.Request
import okhttp3.RequestBody
import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata
import org.session.libsession.messaging.sending_receiving.notifications.Response
import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest
import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse
import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse
import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.Version
import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber
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.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Namespace
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 import javax.inject.Inject
import javax.inject.Singleton
private const val TAG = "FirebasePushManager" private const val TAG = "FirebasePushManager"
class FirebasePushManager( @Singleton
private val context: Context class FirebasePushManager @Inject constructor(
): PushManager { @ApplicationContext private val context: Context
): PushManager {
@Inject lateinit var pushManagerV2: PushManagerV2 @Inject lateinit var genericPushManager: GenericPushManager
companion object {
const val maxRetryCount = 4
}
private val tokenManager = FcmTokenManager(context, ExpiryManager(context))
private var firebaseInstanceIdJob: Job? = null private var firebaseInstanceIdJob: Job? = null
@Synchronized @Synchronized
@ -67,70 +29,9 @@ class FirebasePushManager(
firebaseInstanceIdJob = getFcmInstanceId { task -> firebaseInstanceIdJob = getFcmInstanceId { task ->
when { when {
task.isSuccessful -> try { task.result?.token?.let { refresh(it, force).get() } } catch(e: Exception) { Log.e(TAG, "refresh() failed", e) } task.isSuccessful -> try { task.result?.token?.let { genericPushManager.refresh(it, force).get() } } catch(e: Exception) { Log.e(TAG, "refresh() failed", e) }
else -> Log.w(TAG, "getFcmInstanceId failed." + task.exception) else -> Log.w(TAG, "getFcmInstanceId failed." + task.exception)
} }
} }
} }
private fun refresh(token: String, force: Boolean): Promise<*, Exception> {
Log.d(TAG, "refresh() called")
val userPublicKey = getLocalNumber(context) ?: return emptyPromise()
val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise()
return when {
tokenManager.isUsingFCM -> register(force, token, userPublicKey, userEdKey)
tokenManager.requiresUnregister -> unregister(token, userPublicKey, userEdKey)
else -> emptyPromise()
}
}
/**
* Register for push notifications if:
* force is true
* there is no FCM Token
* FCM Token has expired
*/
private fun register(
force: Boolean,
token: String,
publicKey: String,
userEd25519Key: KeyPair,
namespaces: List<Int> = listOf(Namespace.DEFAULT)
): Promise<*, Exception> = if (force || tokenManager.isInvalid()) {
register(token, publicKey, userEd25519Key, namespaces)
} else emptyPromise()
/**
* Register for push notifications.
*/
private fun register(
token: String,
publicKey: String,
userEd25519Key: KeyPair,
namespaces: List<Int> = listOf(Namespace.DEFAULT)
): Promise<*, Exception> = PushManagerV1.register(
token = token,
publicKey = publicKey
) and pushManagerV2.register(
token, publicKey, userEd25519Key, namespaces
) fail {
Log.e(TAG, "registerBoth failed", it)
} success {
Log.d(TAG, "registerBoth success... saving token!!")
tokenManager.fcmToken = token
}
private fun unregister(
token: String,
userPublicKey: String,
userEdKey: KeyPair
): Promise<*, Exception> = PushManagerV1.unregister() and pushManagerV2.unregister(
token, userPublicKey, userEdKey
) fail {
Log.e(TAG, "unregisterBoth failed", it)
} success {
tokenManager.fcmToken = null
}
} }

View File

@ -1,28 +1,13 @@
package org.thoughtcrime.securesms.notifications package org.thoughtcrime.securesms.notifications
import android.content.Context
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import org.session.libsession.utilities.TextSecurePreferences
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object FirebasePushModule {
@Provides
@Singleton
fun provideFirebasePushManager(
@ApplicationContext context: Context,
) = FirebasePushManager(context)
}
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
abstract class FirebaseBindingModule { abstract class FirebaseBindingModule {
@Binds @Binds
abstract fun bindPushManager(firebasePushManager: FirebasePushManager): PushManager abstract fun bindPushManager(firebasePushManager: FirebasePushManager): PushManager
} }

View File

@ -0,0 +1,87 @@
package org.thoughtcrime.securesms.notifications
import android.content.Context
import com.goterl.lazysodium.utils.KeyPair
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.combine.and
import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1
import org.session.libsession.utilities.Device
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Namespace
import org.session.libsignal.utilities.emptyPromise
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
import javax.inject.Inject
import javax.inject.Singleton
private const val TAG = "GenericPushManager"
@Singleton
class GenericPushManager @Inject constructor(
private val context: Context,
private val device: Device,
private val tokenManager: FcmTokenManager,
private val pushManagerV2: PushManagerV2,
) {
fun refresh(token: String, force: Boolean): Promise<*, Exception> {
Log.d(TAG, "refresh() called")
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return emptyPromise()
val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise()
return when {
tokenManager.isUsingFCM -> register(force, token, userPublicKey, userEdKey)
tokenManager.requiresUnregister -> unregister(token, userPublicKey, userEdKey)
else -> emptyPromise()
}
}
/**
* Register for push notifications if:
* force is true
* there is no FCM Token
* FCM Token has expired
*/
private fun register(
force: Boolean,
token: String,
publicKey: String,
userEd25519Key: KeyPair,
namespaces: List<Int> = listOf(Namespace.DEFAULT)
): Promise<*, Exception> = if (force || tokenManager.isInvalid()) {
register(token, publicKey, userEd25519Key, namespaces)
} else emptyPromise()
/**
* Register for push notifications.
*/
private fun register(
token: String,
publicKey: String,
userEd25519Key: KeyPair,
namespaces: List<Int> = listOf(Namespace.DEFAULT)
): Promise<*, Exception> = PushManagerV1.register(
token = token,
device = device,
publicKey = publicKey
) and pushManagerV2.register(
token, publicKey, userEd25519Key, namespaces
) fail {
Log.e(TAG, "registerBoth failed", it)
} success {
Log.d(TAG, "registerBoth success... saving token!!")
tokenManager.fcmToken = token
}
private fun unregister(
token: String,
userPublicKey: String,
userEdKey: KeyPair
): Promise<*, Exception> = PushManagerV1.unregister() and pushManagerV2.unregister(
token, userPublicKey, userEdKey
) fail {
Log.e(TAG, "unregisterBoth failed", it)
} success {
tokenManager.fcmToken = null
}
}

View File

@ -1,13 +1,9 @@
package org.thoughtcrime.securesms.notifications package org.thoughtcrime.securesms.notifications
import android.content.Context
import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.LazySodiumAndroid
import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.interfaces.AEAD
import com.goterl.lazysodium.interfaces.Sign import com.goterl.lazysodium.interfaces.Sign
import com.goterl.lazysodium.utils.Key
import com.goterl.lazysodium.utils.KeyPair import com.goterl.lazysodium.utils.KeyPair
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
@ -16,30 +12,22 @@ import nl.komponents.kovenant.functional.map
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 import org.session.libsession.messaging.sending_receiving.notifications.*
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata
import org.session.libsession.messaging.sending_receiving.notifications.Response
import org.session.libsession.messaging.sending_receiving.notifications.Server
import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest
import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse
import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse
import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.Version import org.session.libsession.snode.Version
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.Base64
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Namespace import org.session.libsignal.utilities.Namespace
import org.session.libsignal.utilities.retryIfNeeded import org.session.libsignal.utilities.retryIfNeeded
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import javax.inject.Inject
import javax.inject.Singleton
private const val TAG = "PushManagerV2" private const val TAG = "PushManagerV2"
private const val maxRetryCount = 4
class PushManagerV2(private val pushHandler: PushHandler) { @Singleton
class PushManagerV2 @Inject constructor(private val pushHandler: PushHandler) {
private val sodium = LazySodiumAndroid(SodiumAndroid()) private val sodium = LazySodiumAndroid(SodiumAndroid())
fun register( fun register(
@ -98,7 +86,7 @@ class PushManagerV2(private val pushHandler: PushHandler) {
} }
private inline fun <reified T: Response> retryResponseBody(path: String, requestParameters: String): Promise<T, Exception> = private inline fun <reified T: Response> retryResponseBody(path: String, requestParameters: String): Promise<T, Exception> =
retryIfNeeded(FirebasePushManager.maxRetryCount) { getResponseBody(path, requestParameters) } retryIfNeeded(maxRetryCount) { getResponseBody(path, requestParameters) }
private inline fun <reified T: Response> getResponseBody(path: String, requestParameters: String): Promise<T, Exception> { private inline fun <reified T: Response> getResponseBody(path: String, requestParameters: String): Promise<T, Exception> {
val server = Server.LATEST val server = Server.LATEST

View File

@ -19,7 +19,7 @@ private const val TAG = "PushNotificationService"
@AndroidEntryPoint @AndroidEntryPoint
class PushNotificationService : FirebaseMessagingService() { class PushNotificationService : FirebaseMessagingService() {
@Inject lateinit var pushManager: FirebasePushManager @Inject lateinit var pushManager: PushManager
@Inject lateinit var pushHandler: PushHandler @Inject lateinit var pushHandler: PushHandler
override fun onNewToken(token: String) { override fun onNewToken(token: String) {

View File

@ -5,10 +5,12 @@ import com.goterl.lazysodium.utils.KeyPair
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
import org.session.libsession.database.StorageProtocol import org.session.libsession.database.StorageProtocol
import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.Device
class MessagingModuleConfiguration( class MessagingModuleConfiguration(
val context: Context, val context: Context,
val storage: StorageProtocol, val storage: StorageProtocol,
val device: Device,
val messageDataProvider: MessageDataProvider, val messageDataProvider: MessageDataProvider,
val getUserED25519KeyPair: () -> KeyPair?, val getUserED25519KeyPair: () -> KeyPair?,
val configFactory: ConfigFactoryProtocol val configFactory: ConfigFactoryProtocol

View File

@ -29,6 +29,7 @@ import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.SnodeMessage
import org.session.libsession.snode.SnodeModule import org.session.libsession.snode.SnodeModule
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Device
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.SSKEnvironment
import org.session.libsignal.crypto.PushTransportDetails import org.session.libsignal.crypto.PushTransportDetails
@ -454,8 +455,8 @@ object MessageSender {
} }
// Closed groups // Closed groups
fun createClosedGroup(name: String, members: Collection<String>): Promise<String, Exception> { fun createClosedGroup(device: Device, name: String, members: Collection<String>): Promise<String, Exception> {
return create(name, members) return create(device, name, members)
} }
fun explicitNameChange(groupPublicKey: String, newName: String) { fun explicitNameChange(groupPublicKey: String, newName: String) {

View File

@ -13,6 +13,7 @@ import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPol
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.Device
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
@ -33,7 +34,11 @@ const val groupSizeLimit = 100
val pendingKeyPairs = ConcurrentHashMap<String, Optional<ECKeyPair>>() val pendingKeyPairs = ConcurrentHashMap<String, Optional<ECKeyPair>>()
fun MessageSender.create(name: String, members: Collection<String>): Promise<String, Exception> { fun MessageSender.create(
device: Device,
name: String,
members: Collection<String>
): Promise<String, Exception> {
val deferred = deferred<String, Exception>() val deferred = deferred<String, Exception>()
ThreadUtils.queue { ThreadUtils.queue {
// Prepare // Prepare
@ -89,7 +94,7 @@ fun MessageSender.create(name: String, members: Collection<String>): Promise<Str
// Add the group to the config now that it was successfully created // Add the group to the config now that it was successfully created
storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), sentTime, encryptionKeyPair) storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), sentTime, encryptionKeyPair)
// Notify the PN server // Notify the PN server
PushManagerV1.register(publicKey = userPublicKey) PushManagerV1.register(publicKey = userPublicKey, device = device)
// Start polling // Start polling
ClosedGroupPollerV2.shared.startPolling(groupPublicKey) ClosedGroupPollerV2.shared.startPolling(groupPublicKey)
// Fulfill the promise // Fulfill the promise

View File

@ -556,7 +556,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli
// Set expiration timer // Set expiration timer
storage.setExpirationTimer(groupID, expireTimer) storage.setExpirationTimer(groupID, expireTimer)
// Notify the PN server // Notify the PN server
PushManagerV1.register(publicKey = userPublicKey) PushManagerV1.register(publicKey = userPublicKey, device = MessagingModuleConfiguration.shared.device)
// Notify the user // Notify the user
if (userPublicKey == sender && !groupExists) { if (userPublicKey == sender && !groupExists) {
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))

View File

@ -9,6 +9,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.OnionResponse import org.session.libsession.snode.OnionResponse
import org.session.libsession.snode.Version import org.session.libsession.snode.Version
import org.session.libsession.utilities.Device
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
@ -29,17 +30,18 @@ object PushManagerV1 {
isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context), isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context),
token: String? = TextSecurePreferences.getFCMToken(context), token: String? = TextSecurePreferences.getFCMToken(context),
publicKey: String? = TextSecurePreferences.getLocalNumber(context), publicKey: String? = TextSecurePreferences.getLocalNumber(context),
device: Device,
legacyGroupPublicKeys: Collection<String> = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() legacyGroupPublicKeys: Collection<String> = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys()
): Promise<*, Exception> = ): Promise<*, Exception> =
if (!isUsingFCM) { if (!isUsingFCM) {
emptyPromise() emptyPromise()
} else retryIfNeeded(maxRetryCount) { } else retryIfNeeded(maxRetryCount) {
doRegister(token, publicKey, legacyGroupPublicKeys) doRegister(token, publicKey, device, legacyGroupPublicKeys)
} fail { exception -> } fail { exception ->
Log.d(TAG, "Couldn't register for FCM due to error: $exception.") Log.d(TAG, "Couldn't register for FCM due to error: $exception.")
} }
private fun doRegister(token: String?, publicKey: String?, legacyGroupPublicKeys: Collection<String>): Promise<*, Exception> { private fun doRegister(token: String?, publicKey: String?, device: Device, legacyGroupPublicKeys: Collection<String>): Promise<*, Exception> {
Log.d(TAG, "registerV1 requested") Log.d(TAG, "registerV1 requested")
token ?: return emptyPromise() token ?: return emptyPromise()
@ -48,6 +50,7 @@ object PushManagerV1 {
val parameters = mapOf( val parameters = mapOf(
"token" to token, "token" to token,
"pubKey" to publicKey, "pubKey" to publicKey,
"device" to device,
"legacyGroupPublicKeys" to legacyGroupPublicKeys "legacyGroupPublicKeys" to legacyGroupPublicKeys
) )

View File

@ -0,0 +1,6 @@
package org.session.libsession.utilities
enum class Device(val value: String) {
ANDROID("android"),
HUAWEI("huawei");
}