diff options
| author | Albin <albin@mullvad.net> | 2022-04-27 17:36:56 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2022-05-17 15:06:04 +0200 |
| commit | 0b34a1d957d9635ff532fa31ebb6f54d9c4bf792 (patch) | |
| tree | b8035c5f8574a27f08edc90f18ede79e17bf8150 /android | |
| parent | f2135182cea41664fdee736511035761b60140d1 (diff) | |
| download | mullvadvpn-0b34a1d957d9635ff532fa31ebb6f54d9c4bf792.tar.xz mullvadvpn-0b34a1d957d9635ff532fa31ebb6f54d9c4bf792.zip | |
Refactor account expiry
Diffstat (limited to 'android')
13 files changed, 148 insertions, 191 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt index 7338c6bd3a..f8790d126a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt @@ -4,6 +4,7 @@ import android.os.Message as RawMessage import android.os.Messenger import kotlinx.parcelize.Parcelize import net.mullvad.mullvadvpn.model.AccountCreationResult +import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.model.AccountHistory import net.mullvad.mullvadvpn.model.AppVersionInfo as AppVersionInfoData import net.mullvad.mullvadvpn.model.DeviceState @@ -24,6 +25,9 @@ sealed class Event : Message.EventMessage() { data class AccountCreationEvent(val result: AccountCreationResult) : Event() @Parcelize + data class AccountExpiryEvent(val expiry: AccountExpiry) : Event() + + @Parcelize data class AccountHistoryEvent(val history: AccountHistory) : Event() @Parcelize diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt new file mode 100644 index 0000000000..b057308192 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt @@ -0,0 +1,17 @@ +package net.mullvad.mullvadvpn.model + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import org.joda.time.DateTime + +sealed class AccountExpiry : Parcelable { + @Parcelize + data class Available(val expiryDateTime: DateTime) : AccountExpiry() + + @Parcelize + object Missing : AccountExpiry() + + fun date(): DateTime? { + return (this as? Available)?.expiryDateTime + } +} diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/LoginStatus.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/LoginStatus.kt index e143cc630c..e1a1f485e8 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/LoginStatus.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/LoginStatus.kt @@ -2,14 +2,8 @@ package net.mullvad.mullvadvpn.model import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.joda.time.DateTime @Parcelize data class LoginStatus( - val account: String, - val expiry: DateTime?, - val isNewAccount: Boolean -) : Parcelable { - val isExpired: Boolean - get() = expiry != null && expiry.isAfterNow() -} + val account: String +) : Parcelable diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt index 8073f2220f..0ebb900d75 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt @@ -6,15 +6,14 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ClosedReceiveChannelException import kotlinx.coroutines.channels.actor import kotlinx.coroutines.channels.sendBlocking -import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect import net.mullvad.mullvadvpn.ipc.Event import net.mullvad.mullvadvpn.ipc.Request import net.mullvad.mullvadvpn.model.AccountCreationResult +import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.model.AccountHistory import net.mullvad.mullvadvpn.model.GetAccountDataResult -import net.mullvad.mullvadvpn.model.LoginResult import net.mullvad.mullvadvpn.model.LoginStatus -import net.mullvad.mullvadvpn.util.ExponentialBackoff import net.mullvad.mullvadvpn.util.JobTracker import net.mullvad.talpid.util.EventNotifier import org.joda.time.DateTime @@ -22,14 +21,7 @@ import org.joda.time.format.DateTimeFormat class AccountCache(private val endpoint: ServiceEndpoint) { companion object { - public val EXPIRY_FORMAT = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss z") - - // Number of retry attempts to check for a changed expiry before giving up. - // Current value will force the cache to keep fetching for about four minutes or until a new - // expiry value is received. - // This is only used if the expiry was invalidated and fetching a new expiry returns the - // same value as before the invalidation. - private const val MAX_INVALIDATED_RETRIES = 7 + private val EXPIRY_FORMAT = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss z") private sealed class Command { object CreateAccount : Command() @@ -44,7 +36,7 @@ class AccountCache(private val endpoint: ServiceEndpoint) { get() = endpoint.intermittentDaemon val onAccountNumberChange = EventNotifier<String?>(null) - val onAccountExpiryChange = EventNotifier<DateTime?>(null) + val onAccountExpiryChange = EventNotifier<AccountExpiry>(AccountExpiry.Missing) val onAccountHistoryChange = EventNotifier<AccountHistory>(AccountHistory.Missing) val onLoginStatusChange = EventNotifier<LoginStatus?>(null) @@ -53,19 +45,18 @@ class AccountCache(private val endpoint: ServiceEndpoint) { private val jobTracker = JobTracker() - private var accountNumber by onAccountNumberChange.notifiable() private var accountExpiry by onAccountExpiryChange.notifiable() private var accountHistory by onAccountHistoryChange.notifiable() - private var createdAccountExpiry: DateTime? = null - private var oldAccountExpiry: DateTime? = null - var loginStatus by onLoginStatusChange.notifiable() private set init { - endpoint.settingsListener.accountNumberNotifier.subscribe(this) { accountNumber -> - handleNewAccountNumber(accountNumber) + jobTracker.newBackgroundJob("autoFetchAccountExpiry") { + daemon.await().deviceStateUpdates.collect { deviceState -> + accountExpiry = deviceState.token() + ?.let { fetchAccountExpiry(it) } ?: AccountExpiry.Missing + } } onAccountHistoryChange.subscribe(this) { history -> @@ -76,6 +67,10 @@ class AccountCache(private val endpoint: ServiceEndpoint) { endpoint.sendEvent(Event.LoginStatus(status)) } + onAccountExpiryChange.subscribe(this) { + endpoint.sendEvent(Event.AccountExpiryEvent(it)) + } + endpoint.dispatcher.apply { registerHandler(Request.CreateAccount::class) { _ -> commandChannel.sendBlocking(Command.CreateAccount) @@ -92,7 +87,10 @@ class AccountCache(private val endpoint: ServiceEndpoint) { } registerHandler(Request.FetchAccountExpiry::class) { _ -> - fetchAccountExpiry() + jobTracker.newBackgroundJob("fetchAccountExpiry") { + accountExpiry = + accountToken()?.let { fetchAccountExpiry(it) } ?: AccountExpiry.Missing + } } registerHandler(Request.FetchAccountHistory::class) { _ -> @@ -102,7 +100,7 @@ class AccountCache(private val endpoint: ServiceEndpoint) { } registerHandler(Request.InvalidateAccountExpiry::class) { request -> - invalidateAccountExpiry(request.expiry) + // TODO: Implement account expiry invalidation if still required. } registerHandler(Request.ClearAccountHistory::class) { _ -> @@ -123,44 +121,11 @@ class AccountCache(private val endpoint: ServiceEndpoint) { commandChannel.close() } - private fun fetchAccountExpiry() { - synchronized(this) { - accountNumber?.let { account -> - jobTracker.newBackgroundJob("fetch") { - val delays = ExponentialBackoff().apply { - cap = 2 /* h */ * 60 /* min */ * 60 /* s */ * 1000 /* ms */ - } - - do { - val result = daemon.await().getAccountData(account) - - if (result is GetAccountDataResult.Ok) { - val expiry = result.accountData.expiry - val retryAttempt = delays.iteration - - if (handleNewExpiry(account, expiry, retryAttempt)) { - break - } - } else if (result is GetAccountDataResult.InvalidAccount) { - break - } - - delay(delays.next()) - } while (onAccountExpiryChange.hasListeners()) - } - } - } - } - - private fun invalidateAccountExpiry(accountExpiryToInvalidate: DateTime) { - synchronized(this) { - if (accountExpiry == accountExpiryToInvalidate) { - oldAccountExpiry = accountExpiryToInvalidate - fetchAccountExpiry() - } - } + private suspend fun accountToken(): String? { + return daemon.await().deviceStateUpdates.value.token() } + // TODO: Refactor in later commit. private fun clearAccountHistory() { jobTracker.newBackgroundJob("clearAccountHistory") { daemon.await().clearAccountHistory() @@ -183,9 +148,6 @@ class AccountCache(private val endpoint: ServiceEndpoint) { } private suspend fun doCreateAccount() { - newlyCreatedAccount = true - createdAccountExpiry = null - daemon.await().createNewAccount() .let { newAccountNumber -> if (newAccountNumber != null) { @@ -200,25 +162,12 @@ class AccountCache(private val endpoint: ServiceEndpoint) { } private suspend fun doLogin(account: String) { - val loginResult = daemon.await().loginAccount(account).also { result -> + daemon.await().loginAccount(account).also { result -> endpoint.sendEvent(Event.LoginEvent(result)) } - val accountExpiryDate = loginResult - .takeIf { it == LoginResult.Ok } - .let { daemon.await().getAccountData(account) as? GetAccountDataResult.Ok } - ?.let { DateTime.parse(it.accountData.expiry, EXPIRY_FORMAT) } - synchronized(this) { - markAccountAsNotNew() - accountNumber = account - accountExpiry = accountExpiryDate - - loginStatus = LoginStatus( - account = account, - expiry = accountExpiryDate, - isNewAccount = newlyCreatedAccount - ) + loginStatus = LoginStatus(account) } } @@ -228,6 +177,7 @@ class AccountCache(private val endpoint: ServiceEndpoint) { accountHistory = fetchAccountHistory() } + // TODO: Refactor in later commit. private fun fetchAccountHistory() { jobTracker.newBackgroundJob("fetchHistory") { daemon.await().getAccountHistory().let { history -> @@ -240,61 +190,21 @@ class AccountCache(private val endpoint: ServiceEndpoint) { } } - private fun markAccountAsNotNew() { - newlyCreatedAccount = false - createdAccountExpiry = null - } - - private fun handleNewAccountNumber(newAccountNumber: String?) { - synchronized(this) { - accountExpiry = null - accountNumber = newAccountNumber - - loginStatus = newAccountNumber?.let { account -> - LoginStatus(account, null, newlyCreatedAccount) + private suspend fun fetchAccountExpiry(accountToken: String): AccountExpiry { + return fetchAccountData(accountToken).let { result -> + if (result is GetAccountDataResult.Ok) { + AccountExpiry.Available(result.parseExpiryDate()) + } else { + AccountExpiry.Missing } - - fetchAccountExpiry() - fetchAccountHistory() } } - private fun handleNewExpiry( - accountNumberUsedForFetch: String, - expiryString: String, - retryAttempt: Int - ): Boolean { - synchronized(this) { - if (accountNumber !== accountNumberUsedForFetch) { - return true - } - - val newAccountExpiry = DateTime.parse(expiryString, EXPIRY_FORMAT) - - if (newAccountExpiry != oldAccountExpiry || retryAttempt >= MAX_INVALIDATED_RETRIES) { - accountExpiry = newAccountExpiry - oldAccountExpiry = null - - loginStatus = loginStatus?.let { currentStatus -> - LoginStatus( - currentStatus.account, - newAccountExpiry, - currentStatus.isNewAccount - ) - } - - if (accountExpiry != null && newlyCreatedAccount) { - if (createdAccountExpiry == null) { - createdAccountExpiry = accountExpiry - } else if (accountExpiry != createdAccountExpiry) { - markAccountAsNotNew() - } - } - - return true - } + private suspend fun fetchAccountData(accountToken: String): GetAccountDataResult { + return daemon.await().getAccountData(accountToken) + } - return false - } + private fun GetAccountDataResult.Ok.parseExpiryDate(): DateTime { + return DateTime.parse(this.accountData.expiry, EXPIRY_FORMAT) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt index 7b835bccdc..5720c2eee1 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt @@ -10,7 +10,7 @@ import androidx.core.app.NotificationCompat import kotlin.properties.Delegates.observable import kotlinx.coroutines.delay import net.mullvad.mullvadvpn.R -import net.mullvad.mullvadvpn.model.LoginStatus +import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.service.MullvadDaemon import net.mullvad.mullvadvpn.service.endpoint.AccountCache import net.mullvad.mullvadvpn.util.Intermittent @@ -44,33 +44,31 @@ class AccountExpiryNotification( true ) - var loginStatus by observable<LoginStatus?>(null) { _, oldValue, newValue -> + var accountExpiry by observable<AccountExpiry>( + AccountExpiry.Missing + ) { _, oldValue, newValue -> if (oldValue != newValue) { jobTracker.newUiJob("update") { update(newValue) } } } init { - accountCache.onLoginStatusChange.subscribe(this) { newStatus -> - loginStatus = newStatus + accountCache.onAccountExpiryChange.subscribe(this) { expiry -> + accountExpiry = expiry } } fun onDestroy() { accountCache.onAccountNumberChange.unsubscribe(this) - loginStatus = null } - private suspend fun update(loginStatus: LoginStatus?) { - val remainingTime = loginStatus?.expiry?.let { expiry -> Duration(DateTime.now(), expiry) } - val closeToExpire = remainingTime?.isShorterThan(REMAINING_TIME_FOR_REMINDERS) ?: false - val accountIsNew = loginStatus?.isNewAccount ?: false - - if (closeToExpire && !accountIsNew) { - val notification = build(loginStatus!!.expiry!!, remainingTime!!) + private suspend fun update(expiry: AccountExpiry) { + val expiryDate = expiry.date() + val durationUntilExpiry = expiryDate?.remainingTime() + if (durationUntilExpiry?.isCloseToExpiry() == true) { + val notification = build(expiryDate, durationUntilExpiry) channel.notificationManager.notify(NOTIFICATION_ID, notification) - jobTracker.newUiJob("scheduleUpdate") { scheduleUpdate() } } else { channel.notificationManager.cancel(NOTIFICATION_ID) @@ -78,9 +76,17 @@ class AccountExpiryNotification( } } + private fun DateTime.remainingTime(): Duration { + return Duration(DateTime.now(), this) + } + + private fun Duration.isCloseToExpiry(): Boolean { + return isShorterThan(REMAINING_TIME_FOR_REMINDERS) + } + private suspend fun scheduleUpdate() { delay(TIME_BETWEEN_CHECKS) - update(loginStatus) + update(accountExpiry) } private suspend fun build(expiry: DateTime, remainingTime: Duration): Notification { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt index 4b76bb2b6a..98611b7e64 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup import androidx.fragment.app.FragmentManager import java.text.DateFormat import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.model.TunnelState @@ -117,11 +118,13 @@ class AccountFragment : ServiceDependentFragment(OnNoService.GoBack) { } } - accountCache.onAccountExpiryChange.subscribe(this) { accountExpiry -> - jobTracker.newUiJob("updateAccountExpiry") { - currentAccountExpiry = accountExpiry - updateAccountExpiry(accountExpiry) - } + jobTracker.newUiJob("updateAccountExpiry") { + accountCache.accountExpiryState + .map { state -> state.date() } + .collect { expiryDate -> + currentAccountExpiry = expiryDate + updateAccountExpiry(expiryDate) + } } connectionProxy.onUiStateChange.subscribe(this) { uiState -> @@ -139,10 +142,10 @@ class AccountFragment : ServiceDependentFragment(OnNoService.GoBack) { } sitePaymentButton.updateAuthTokenCache(authTokenCache) + accountCache.fetchAccountExpiry() } override fun onSafelyStop() { - accountCache.onAccountExpiryChange.unsubscribe(this) jobTracker.cancelAllJobs() } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt index 6f701c9ba7..629c5942c3 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt @@ -7,6 +7,8 @@ import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.model.TunnelState import net.mullvad.mullvadvpn.ui.notification.AccountExpiryNotification @@ -101,20 +103,24 @@ class ConnectFragment : } } - accountCache.onAccountExpiryChange.subscribe(this) { expiry -> - if (expiry?.isBeforeNow() ?: false) { - openOutOfTimeScreen() - } else if (expiry != null) { - scheduleNextAccountExpiryCheck(expiry) - } + jobTracker.newUiJob("updateAccountExpiry") { + accountCache.accountExpiryState + .map { state -> state.date() } + .collect { expiryDate -> + if (expiryDate?.isBeforeNow == true) { + openOutOfTimeScreen() + } else if (expiryDate != null) + scheduleNextAccountExpiryCheck(expiryDate) + } } } override fun onSafelyStop() { + jobTracker.cancelJob("updateAccountExpiry") + locationInfoCache.onNewLocation = null relayListListener.onRelayListChange = null - accountCache.onAccountExpiryChange.unsubscribe(this) keyStatusListener.onKeyStatusChange.unsubscribe(this) connectionProxy.onUiStateChange.unsubscribe(this) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/OutOfTimeFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/OutOfTimeFragment.kt index 658315773e..bb65aa2135 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/OutOfTimeFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/OutOfTimeFragment.kt @@ -7,6 +7,8 @@ import android.view.ViewGroup import android.widget.TextView import kotlin.properties.Delegates.observable import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.model.TunnelState import net.mullvad.mullvadvpn.ui.widget.Button @@ -72,8 +74,12 @@ class OutOfTimeFragment : ServiceDependentFragment(OnNoService.GoToLaunchScreen) } override fun onSafelyStart() { - accountCache.onAccountExpiryChange.subscribe(this) { expiry -> - checkExpiry(expiry) + jobTracker.newUiJob("updateAccountExpiry") { + accountCache.accountExpiryState + .map { state -> state.date() } + .collect { expiryDate -> + checkExpiry(expiryDate) + } } jobTracker.newBackgroundJob("pollAccountData") { @@ -87,7 +93,7 @@ class OutOfTimeFragment : ServiceDependentFragment(OnNoService.GoToLaunchScreen) } override fun onSafelyStop() { - accountCache.onAccountExpiryChange.unsubscribe(this) + jobTracker.cancelJob("updateAccountExpiry") jobTracker.cancelJob("pollAccountData") } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt index a25ac6a1d8..6e6fc2c8b2 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt @@ -13,6 +13,7 @@ import android.view.ViewGroup.LayoutParams import android.widget.EditText import android.widget.TextView import androidx.fragment.app.DialogFragment +import kotlinx.coroutines.flow.collect import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.model.VoucherSubmissionError import net.mullvad.mullvadvpn.model.VoucherSubmissionResult @@ -49,19 +50,19 @@ class RedeemVoucherDialogFragment : DialogFragment() { parentActivity = context as MainActivity parentActivity.serviceNotifier.subscribe(this) { connection -> - accountCache?.onAccountExpiryChange?.unsubscribe(this@RedeemVoucherDialogFragment) - - accountCache = connection?.accountCache?.apply { - onAccountExpiryChange - .subscribe(this@RedeemVoucherDialogFragment) { newAccountExpiry -> - accountExpiry = newAccountExpiry - } - } - + accountCache = connection?.accountCache voucherRedeemer = connection?.voucherRedeemer + } - updateRedeemButton() + accountCache?.apply { + jobTracker.newUiJob("updateExpiry") { + accountCache?.accountExpiryState?.collect { state -> + accountExpiry = state.date() + } + } } + + updateRedeemButton() } override fun onCreateView( @@ -121,6 +122,7 @@ class RedeemVoucherDialogFragment : DialogFragment() { } override fun onDetach() { + jobTracker.cancelJob("updateExpiry") parentActivity.serviceNotifier.unsubscribe(this) super.onDetach() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt index dea5d900db..08afd5c59e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt @@ -8,6 +8,7 @@ import android.widget.ImageButton import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.model.DeviceState @@ -107,10 +108,6 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar active = false versionInfoCache?.onUpdate = null - accountCache?.apply { - onAccountExpiryChange.unsubscribe(this@SettingsFragment) - } - jobTracker.cancelAllJobs() super.onStop() @@ -123,10 +120,12 @@ class SettingsFragment : ServiceAwareFragment(), StatusBarPainter, NavigationBar private fun configureListeners() { accountCache?.apply { - onAccountExpiryChange.subscribe(this@SettingsFragment) { expiry -> - jobTracker.newUiJob("updateAccountInfo") { - accountMenu.accountExpiry = expiry - } + jobTracker.newUiJob("updateAccountExpiry") { + accountExpiryState + .map { state -> state.date() } + .collect { expiryDate -> + accountMenu.accountExpiry = expiryDate + } } fetchAccountExpiry() diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt index ad3ee9c5e9..92364fdf0f 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt @@ -64,8 +64,10 @@ class WelcomeFragment : ServiceDependentFragment(OnNoService.GoToLaunchScreen) { } } - accountCache.onAccountExpiryChange.subscribe(this) { expiry -> - checkExpiry(expiry) + jobTracker.newUiJob("checkAccountExpiry") { + accountCache.accountExpiryState.collect { + checkExpiry(it.date()) + } } jobTracker.newBackgroundJob("pollAccountData") { @@ -79,7 +81,7 @@ class WelcomeFragment : ServiceDependentFragment(OnNoService.GoToLaunchScreen) { } override fun onSafelyStop() { - accountCache.onAccountExpiryChange.unsubscribe(this) + jobTracker.cancelJob("checkAccountExpiry") jobTracker.cancelJob("pollAccountData") jobTracker.cancelJob("updateAccountNumber") } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt index b959a06607..794c372f72 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.ui.notification import android.content.Context +import kotlinx.coroutines.flow.collect import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.ui.serviceconnection.AccountCache import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache @@ -20,15 +21,15 @@ class AccountExpiryNotification( } override fun onResume() { - accountCache.onAccountExpiryChange.subscribe(this) { accountExpiry -> - jobTracker.newUiJob("updateAccountExpiry") { - updateAccountExpiry(accountExpiry) + jobTracker.newUiJob("updateAccountExpiry") { + accountCache.accountExpiryState.collect { state -> + updateAccountExpiry(state.date()) } } } override fun onPause() { - accountCache.onAccountExpiryChange.unsubscribe(this) + jobTracker.cancelJob("updateAccountExpiry") } private fun updateAccountExpiry(expiry: DateTime?) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AccountCache.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AccountCache.kt index 4deaf4937d..58295a521d 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AccountCache.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AccountCache.kt @@ -3,18 +3,20 @@ package net.mullvad.mullvadvpn.ui.serviceconnection import android.os.Messenger import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow import net.mullvad.mullvadvpn.ipc.Event import net.mullvad.mullvadvpn.ipc.EventDispatcher import net.mullvad.mullvadvpn.ipc.Request import net.mullvad.mullvadvpn.model.AccountCreationResult +import net.mullvad.mullvadvpn.model.AccountExpiry import net.mullvad.mullvadvpn.model.AccountHistory import net.mullvad.mullvadvpn.model.LoginStatus import net.mullvad.talpid.util.EventNotifier import org.joda.time.DateTime class AccountCache(private val connection: Messenger, eventDispatcher: EventDispatcher) { - val onAccountExpiryChange = EventNotifier<DateTime?>(null) val onLoginStatusChange = EventNotifier<LoginStatus?>(null) private var loginStatus by onLoginStatusChange.notifiable() @@ -25,6 +27,9 @@ class AccountCache(private val connection: Messenger, eventDispatcher: EventDisp ) val accountCreationEvents = _accountCreationEvents.asSharedFlow() + private val _accountExpiryState = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing) + val accountExpiryState = _accountExpiryState.asStateFlow() + private val _accountHistoryEvents = MutableSharedFlow<AccountHistory>( extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST @@ -45,7 +50,6 @@ class AccountCache(private val connection: Messenger, eventDispatcher: EventDisp registerHandler(Event.LoginStatus::class) { event -> loginStatus = event.status - onAccountExpiryChange.notifyIfChanged(loginStatus?.expiry) } registerHandler(Event.AccountCreationEvent::class) { event -> @@ -55,6 +59,10 @@ class AccountCache(private val connection: Messenger, eventDispatcher: EventDisp registerHandler(Event.LoginEvent::class) { event -> _loginEvents.tryEmit(event) } + + registerHandler(Event.AccountExpiryEvent::class) { event -> + _accountExpiryState.tryEmit(event.expiry) + } } } @@ -89,7 +97,6 @@ class AccountCache(private val connection: Messenger, eventDispatcher: EventDisp } fun onDestroy() { - onAccountExpiryChange.unsubscribeAll() onLoginStatusChange.unsubscribeAll() } } |
