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 @AndroidEntryPoint
class HuaweiPushService: HmsMessageService() { class HuaweiPushService: HmsMessageService() {
init {
Log.d(TAG, "init Huawei Service")
}
@Inject lateinit var pushRegistry: PushRegistry @Inject lateinit var pushRegistry: PushRegistry
@Inject lateinit var pushReceiver: PushReceiver @Inject lateinit var pushReceiver: PushReceiver
@ -32,33 +27,13 @@ class HuaweiPushService: HmsMessageService() {
pushReceiver.onPush(message?.data?.let(Base64::decode)) pushReceiver.onPush(message?.data?.let(Base64::decode))
} }
override fun onMessageSent(p0: String?) { override fun onNewToken(token: String?) {
Log.d(TAG, "onMessageSent() called with: p0 = $p0") pushRegistry.register(token)
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?, bundle: Bundle?) { override fun onNewToken(token: String?, bundle: Bundle?) {
Log.d(TAG, "New HCM token: $token.") Log.d(TAG, "New HCM token: $token.")
onNewToken(token)
TextSecurePreferences.setPushToken(this, token)
pushRegistry.refresh(token, true)
} }
override fun onDeletedMessages() { override fun onDeletedMessages() {

View File

@ -2,26 +2,28 @@ package org.thoughtcrime.securesms.notifications
import android.content.Context import android.content.Context
import com.huawei.hms.aaid.HmsInstanceId import com.huawei.hms.aaid.HmsInstanceId
import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.session.libsignal.utilities.Log
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
private const val APP_ID = "107205081"
private const val TOKEN_SCOPE = "HCM"
@Singleton @Singleton
class HuaweiTokenFetcher @Inject constructor( class HuaweiTokenFetcher @Inject constructor(
@ApplicationContext private val context: Context @ApplicationContext private val context: Context,
private val pushRegistry: Lazy<PushRegistry>,
): TokenFetcher { ): TokenFetcher {
override fun fetch(): Job { override suspend fun fetch(): String? = HmsInstanceId.getInstance(context).run {
val hmsInstanceId = HmsInstanceId.getInstance(context) // 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.
return MainScope().launch(Dispatchers.IO) { withContext(Dispatchers.IO) { getToken(APP_ID, TOKEN_SCOPE) }
val appId = "107205081"
val tokenScope = "HCM"
// getToken returns an empty string, but triggers the service to initialize.
hmsInstanceId.getToken(appId, tokenScope)
}
} }
} }

View File

@ -427,11 +427,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
} }
private static class ProviderInitializationException extends RuntimeException { } private static class ProviderInitializationException extends RuntimeException { }
public void registerForPnIfNeeded(final Boolean force) {
pushRegistry.refresh(force);
}
private void setUpPollingIfNeeded() { private void setUpPollingIfNeeded() {
String userPublicKey = TextSecurePreferences.getLocalNumber(this); String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return; 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) else -> this["ENCRYPTED_DATA"]?.let(Base64::decode)
} }
fun decrypt(encPayload: ByteArray): ByteArray? { private fun decrypt(encPayload: ByteArray): ByteArray? {
Log.d(TAG, "decrypt() called") Log.d(TAG, "decrypt() called")
val encKey = getOrCreateNotificationKey() val encKey = getOrCreateNotificationKey()

View File

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

View File

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

View File

@ -1,43 +1,19 @@
package org.thoughtcrime.securesms.notifications package org.thoughtcrime.securesms.notifications
import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.Tasks import com.google.android.gms.tasks.Tasks
import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.iid.InstanceIdResult
import dagger.Lazy
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.withContext
import org.session.libsignal.utilities.Log
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
private val TAG = FirebaseTokenFetcher::class.java.name
@Singleton @Singleton
class FirebaseTokenFetcher @Inject constructor( class FirebaseTokenFetcher @Inject constructor(): TokenFetcher {
private val pushRegistry: Lazy<PushRegistry>, override suspend fun fetch() = withContext(Dispatchers.IO) {
): TokenFetcher {
override fun fetch(): Job = MainScope().launch(Dispatchers.IO) {
FirebaseInstanceId.getInstance().instanceId FirebaseInstanceId.getInstance().instanceId
.also(Tasks::await) .also(Tasks::await)
.also { if (!isActive) return@launch } // don't 'complete' task if we were canceled .takeIf { isActive } // don't 'complete' task if we were canceled
.process() ?.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)
} }