summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2022-04-27 17:36:56 +0200
committerAlbin <albin@mullvad.net>2022-05-17 15:06:04 +0200
commit0b34a1d957d9635ff532fa31ebb6f54d9c4bf792 (patch)
treeb8035c5f8574a27f08edc90f18ede79e17bf8150 /android
parentf2135182cea41664fdee736511035761b60140d1 (diff)
downloadmullvadvpn-0b34a1d957d9635ff532fa31ebb6f54d9c4bf792.tar.xz
mullvadvpn-0b34a1d957d9635ff532fa31ebb6f54d9c4bf792.zip
Refactor account expiry
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ipc/Event.kt4
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/AccountExpiry.kt17
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/model/LoginStatus.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/AccountCache.kt160
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt34
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/AccountFragment.kt15
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/ConnectFragment.kt20
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/OutOfTimeFragment.kt12
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/RedeemVoucherDialogFragment.kt22
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/SettingsFragment.kt15
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/WelcomeFragment.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/notification/AccountExpiryNotification.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/AccountCache.kt13
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()
}
}