Refactor to accept Huawei token from getToken() and/or onNewToken()

This commit is contained in:
andrew 2023-08-09 10:08:42 +09:30
parent c8dcfbf32c
commit 5a5b2f593f
10 changed files with 59 additions and 148 deletions

View File

@ -14,11 +14,6 @@ private val TAG = HuaweiPushService::class.java.simpleName
@AndroidEntryPoint
class HuaweiPushService: HmsMessageService() {
init {
Log.d(TAG, "init Huawei Service")
}
@Inject lateinit var pushRegistry: PushRegistry
@Inject lateinit var pushReceiver: PushReceiver
@ -32,33 +27,13 @@ class HuaweiPushService: HmsMessageService() {
pushReceiver.onPush(message?.data?.let(Base64::decode))
}
override fun onMessageSent(p0: String?) {
Log.d(TAG, "onMessageSent() called with: p0 = $p0")
super.onMessageSent(p0)
}
override fun onSendError(p0: String?, p1: Exception?) {
Log.d(TAG, "onSendError() called with: p0 = $p0, p1 = $p1")
super.onSendError(p0, p1)
}
override fun onMessageDelivered(p0: String?, p1: Exception?) {
Log.d(TAG, "onMessageDelivered")
super.onMessageDelivered(p0, p1)
}
override fun onNewToken(p0: String?) {
Log.d(TAG, "onNewToken")
super.onNewToken(p0)
override fun onNewToken(token: String?) {
pushRegistry.register(token)
}
override fun onNewToken(token: String?, bundle: Bundle?) {
Log.d(TAG, "New HCM token: $token.")
TextSecurePreferences.setPushToken(this, token)
pushRegistry.refresh(token, true)
onNewToken(token)
}
override fun onDeletedMessages() {

View File

@ -2,26 +2,28 @@ package org.thoughtcrime.securesms.notifications
import android.content.Context
import com.huawei.hms.aaid.HmsInstanceId
import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.session.libsignal.utilities.Log
import javax.inject.Inject
import javax.inject.Singleton
private const val APP_ID = "107205081"
private const val TOKEN_SCOPE = "HCM"
@Singleton
class HuaweiTokenFetcher @Inject constructor(
@ApplicationContext private val context: Context
@ApplicationContext private val context: Context,
private val pushRegistry: Lazy<PushRegistry>,
): TokenFetcher {
override fun fetch(): Job {
val hmsInstanceId = HmsInstanceId.getInstance(context)
return MainScope().launch(Dispatchers.IO) {
val appId = "107205081"
val tokenScope = "HCM"
// getToken returns an empty string, but triggers the service to initialize.
hmsInstanceId.getToken(appId, tokenScope)
}
override suspend fun fetch(): String? = HmsInstanceId.getInstance(context).run {
// https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/push-basic-capability#h2-1576218800370
// getToken may return an empty string, if so HuaweiPushService#onNewToken will be called.
withContext(Dispatchers.IO) { getToken(APP_ID, TOKEN_SCOPE) }
}
}

View File

@ -427,11 +427,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
}
private static class ProviderInitializationException extends RuntimeException { }
public void registerForPnIfNeeded(final Boolean force) {
pushRegistry.refresh(force);
}
private void setUpPollingIfNeeded() {
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return;

View File

@ -75,7 +75,7 @@ class PushReceiver @Inject constructor(@ApplicationContext val context: Context)
else -> this["ENCRYPTED_DATA"]?.let(Base64::decode)
}
fun decrypt(encPayload: ByteArray): ByteArray? {
private fun decrypt(encPayload: ByteArray): ByteArray? {
Log.d(TAG, "decrypt() called")
val encKey = getOrCreateNotificationKey()

View File

@ -4,6 +4,8 @@ import android.content.Context
import com.goterl.lazysodium.utils.KeyPair
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.combine.and
import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1
@ -22,8 +24,10 @@ private val TAG = PushRegistry::class.java.name
class PushRegistry @Inject constructor(
@ApplicationContext private val context: Context,
private val device: Device,
private val tokenManager: PushTokenManager,
private val tokenManager: TokenManager,
private val pushRegistryV2: PushRegistryV2,
private val prefs: TextSecurePreferences,
private val tokenFetcher: TokenFetcher,
) {
private var pushRegistrationJob: Job? = null
@ -32,42 +36,33 @@ class PushRegistry @Inject constructor(
Log.d(TAG, "refresh() called with: force = $force")
pushRegistrationJob?.apply {
if (force) cancel() else if (isActive) return
if (force) cancel() else if (isActive || !tokenManager.hasValidRegistration) return
}
pushRegistrationJob = tokenManager.fetchToken()
pushRegistrationJob = MainScope().launch {
register(tokenFetcher.fetch()) fail {
Log.e(TAG, "register failed", it)
}
}
}
fun refresh(token: String?, force: Boolean): Promise<*, Exception> {
Log.d(TAG, "refresh($token, $force) called")
fun register(token: String?): Promise<*, Exception> {
Log.d(TAG, "refresh($token) called")
token ?: return emptyPromise()
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return emptyPromise()
if (token?.isNotEmpty() != true) return emptyPromise()
prefs.setPushToken(token)
val userPublicKey = prefs.getLocalNumber() ?: return emptyPromise()
val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise()
return when {
tokenManager.isPushEnabled -> register(force, token, userPublicKey, userEdKey)
tokenManager.requiresUnregister -> unregister(token, userPublicKey, userEdKey)
prefs.isPushEnabled() -> register(token, userPublicKey, userEdKey)
tokenManager.isRegistered -> 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.
*/
@ -77,7 +72,7 @@ class PushRegistry @Inject constructor(
userEd25519Key: KeyPair,
namespaces: List<Int> = listOf(Namespace.DEFAULT)
): Promise<*, Exception> {
android.util.Log.d(
Log.d(
TAG,
"register() called with: token = $token, publicKey = $publicKey, userEd25519Key = $userEd25519Key, namespaces = $namespaces"
)
@ -97,8 +92,8 @@ class PushRegistry @Inject constructor(
}
return v1 and v2 success {
Log.d(TAG, "registerBoth success... saving token!!")
tokenManager.fcmToken = token
Log.d(TAG, "register v1 & v2 success")
tokenManager.register()
}
}
@ -111,6 +106,6 @@ class PushRegistry @Inject constructor(
) fail {
Log.e(TAG, "unregisterBoth failed", it)
} success {
tokenManager.fcmToken = null
tokenManager.unregister()
}
}
}

View File

@ -1,34 +0,0 @@
package org.thoughtcrime.securesms.notifications
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Job
import org.session.libsession.utilities.TextSecurePreferences
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class PushTokenManager @Inject constructor(
@ApplicationContext private val context: Context,
private val tokenFetcher: TokenFetcher
) {
private val expiryManager = ExpiryManager(context)
val isPushEnabled get() = TextSecurePreferences.isPushEnabled(context)
var fcmToken
get() = TextSecurePreferences.getPushToken(context)
set(value) {
TextSecurePreferences.setPushToken(context, value)
if (value != null) markTime() else clearTime()
}
val requiresUnregister get() = fcmToken != null
private fun clearTime() = expiryManager.clearTime()
private fun markTime() = expiryManager.markTime()
private fun isExpired() = expiryManager.isExpired()
fun isInvalid(): Boolean = fcmToken == null || isExpired()
fun fetchToken(): Job = tokenFetcher.fetch()
}

View File

@ -1,7 +1,5 @@
package org.thoughtcrime.securesms.notifications
import kotlinx.coroutines.Job
interface TokenFetcher {
fun fetch(): Job
suspend fun fetch(): String?
}

View File

@ -6,17 +6,21 @@ import org.session.libsession.utilities.TextSecurePreferences
import javax.inject.Inject
import javax.inject.Singleton
class ExpiryManager(
private val context: Context,
private val interval: Int = 12 * 60 * 60 * 1000
) {
fun isExpired() = currentTime() > time + interval
private const val INTERVAL: Int = 12 * 60 * 60 * 1000
fun markTime() {
@Singleton
class TokenManager @Inject constructor(
@ApplicationContext private val context: Context,
) {
val hasValidRegistration get() = isRegistered && !isExpired
val isRegistered get() = time > 0
private val isExpired get() = currentTime() > time + INTERVAL
fun register() {
time = currentTime()
}
fun clearTime() {
fun unregister() {
time = 0
}

View File

@ -18,7 +18,7 @@ class FirebasePushService : FirebaseMessagingService() {
override fun onNewToken(token: String) {
if (token == prefs.getPushToken()) return
pushRegistry.refresh(token, true)
pushRegistry.register(token)
}
override fun onMessageReceived(message: RemoteMessage) {

View File

@ -1,43 +1,19 @@
package org.thoughtcrime.securesms.notifications
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.iid.InstanceIdResult
import dagger.Lazy
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.session.libsignal.utilities.Log
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
private val TAG = FirebaseTokenFetcher::class.java.name
@Singleton
class FirebaseTokenFetcher @Inject constructor(
private val pushRegistry: Lazy<PushRegistry>,
): TokenFetcher {
override fun fetch(): Job = MainScope().launch(Dispatchers.IO) {
class FirebaseTokenFetcher @Inject constructor(): TokenFetcher {
override suspend fun fetch() = withContext(Dispatchers.IO) {
FirebaseInstanceId.getInstance().instanceId
.also(Tasks::await)
.also { if (!isActive) return@launch } // don't 'complete' task if we were canceled
.process()
.takeIf { isActive } // don't 'complete' task if we were canceled
?.run { result?.token ?: throw exception!! }
}
private fun Task<InstanceIdResult>.process() = when {
isSuccessful -> try {
result?.token?.let {
pushRegistry.get().refresh(it, force = true).get()
}
} catch (e: Exception) {
onFail(e)
}
else -> exception?.let(::onFail)
}
private fun onFail(e: Exception) = Log.e(TAG, "fetch failed", e)
}