mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
Add disk storage for Vault Timeout (#518)
This commit is contained in:
parent
f54af724b1
commit
54c288cb25
5 changed files with 244 additions and 0 deletions
|
@ -10,6 +10,37 @@ import androidx.core.content.edit
|
|||
abstract class BaseDiskSource(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
) {
|
||||
/**
|
||||
* Gets the [Int] for the given [key] from [SharedPreferences], or return the [default] value
|
||||
* if that key is not present.
|
||||
*/
|
||||
protected fun getInt(
|
||||
key: String,
|
||||
default: Int? = null,
|
||||
): Int? =
|
||||
if (sharedPreferences.contains(key)) {
|
||||
sharedPreferences.getInt(key, 0)
|
||||
} else {
|
||||
// Make sure we can return a null value as a default if necessary
|
||||
default
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the [value] in [SharedPreferences] for the given [key] (or removes the key when the
|
||||
* value is `null`).
|
||||
*/
|
||||
protected fun putInt(
|
||||
key: String,
|
||||
value: Int?,
|
||||
): Unit =
|
||||
sharedPreferences.edit {
|
||||
if (value != null) {
|
||||
putInt(key, value)
|
||||
} else {
|
||||
remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getString(
|
||||
key: String,
|
||||
default: String? = null,
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Primary access point for general settings-related disk information.
|
||||
*/
|
||||
interface SettingsDiskSource {
|
||||
/**
|
||||
* Gets the current vault timeout (in minutes) for the given [userId] (or `null` if the vault
|
||||
* should never time out).
|
||||
*/
|
||||
fun getVaultTimeoutInMinutes(userId: String): Int?
|
||||
|
||||
/**
|
||||
* Emits updates that track [getVaultTimeoutInMinutes] for the given [userId]. This will replay
|
||||
* the last known value, if any.
|
||||
*/
|
||||
fun getVaultTimeoutInMinutesFlow(userId: String): Flow<Int?>
|
||||
|
||||
/**
|
||||
* Stores the given [vaultTimeoutInMinutes] for the given [userId].
|
||||
*/
|
||||
fun storeVaultTimeoutInMinutes(userId: String, vaultTimeoutInMinutes: Int?)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
|
||||
private const val VAULT_TIME_IN_MINUTES_KEY = "$BASE_KEY:vaultTimeout"
|
||||
|
||||
/**
|
||||
* Primary implementation of [SettingsDiskSource].
|
||||
*/
|
||||
class SettingsDiskSourceImpl(
|
||||
val sharedPreferences: SharedPreferences,
|
||||
) : BaseDiskSource(sharedPreferences = sharedPreferences),
|
||||
SettingsDiskSource {
|
||||
private val mutableVaultTimeoutInMinutesFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<Int?>>()
|
||||
|
||||
override fun getVaultTimeoutInMinutes(userId: String): Int? =
|
||||
getInt(key = "${VAULT_TIME_IN_MINUTES_KEY}_$userId")
|
||||
|
||||
override fun getVaultTimeoutInMinutesFlow(userId: String): Flow<Int?> =
|
||||
getMutableVaultTimeoutInMinutesFlow(userId = userId)
|
||||
.onSubscription { emit(getVaultTimeoutInMinutes(userId = userId)) }
|
||||
|
||||
override fun storeVaultTimeoutInMinutes(
|
||||
userId: String,
|
||||
vaultTimeoutInMinutes: Int?,
|
||||
) {
|
||||
putInt(
|
||||
key = "${VAULT_TIME_IN_MINUTES_KEY}_$userId",
|
||||
value = vaultTimeoutInMinutes,
|
||||
)
|
||||
getMutableVaultTimeoutInMinutesFlow(userId = userId).tryEmit(vaultTimeoutInMinutes)
|
||||
}
|
||||
|
||||
private fun getMutableVaultTimeoutInMinutesFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<Int?> =
|
||||
mutableVaultTimeoutInMinutesFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class SettingsDiskSourceTest {
|
||||
private val fakeSharedPreferences = FakeSharedPreferences()
|
||||
|
||||
private val settingsDiskSource = SettingsDiskSourceImpl(
|
||||
sharedPreferences = fakeSharedPreferences,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `getVaultTimeoutInMinutes when values are present should pull from SharedPreferences`() {
|
||||
val vaultTimeoutBaseKey = "bwPreferencesStorage:vaultTimeout"
|
||||
val mockUserId = "mockUserId"
|
||||
val vaultTimeoutInMinutes = 360
|
||||
fakeSharedPreferences
|
||||
.edit()
|
||||
.putInt(
|
||||
"${vaultTimeoutBaseKey}_$mockUserId",
|
||||
vaultTimeoutInMinutes,
|
||||
)
|
||||
.apply()
|
||||
val actual = settingsDiskSource.getVaultTimeoutInMinutes(userId = mockUserId)
|
||||
assertEquals(
|
||||
vaultTimeoutInMinutes,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getVaultTimeoutInMinutes when values are absent should return null`() {
|
||||
val mockUserId = "mockUserId"
|
||||
assertNull(settingsDiskSource.getVaultTimeoutInMinutes(userId = mockUserId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getVaultTimeoutInMinutesFlow should react to changes in getOrganizations`() = runTest {
|
||||
val mockUserId = "mockUserId"
|
||||
val vaultTimeoutInMinutes = 360
|
||||
settingsDiskSource.getVaultTimeoutInMinutesFlow(userId = mockUserId).test {
|
||||
// The initial values of the Flow and the property are in sync
|
||||
assertNull(settingsDiskSource.getVaultTimeoutInMinutes(userId = mockUserId))
|
||||
assertNull(awaitItem())
|
||||
|
||||
// Updating the repository updates shared preferences
|
||||
settingsDiskSource.storeVaultTimeoutInMinutes(
|
||||
userId = mockUserId,
|
||||
vaultTimeoutInMinutes = vaultTimeoutInMinutes,
|
||||
)
|
||||
assertEquals(vaultTimeoutInMinutes, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeVaultTimeoutInMinutes for non-null values should update SharedPreferences`() {
|
||||
val vaultTimeoutBaseKey = "bwPreferencesStorage:vaultTimeout"
|
||||
val mockUserId = "mockUserId"
|
||||
val vaultTimeoutInMinutes = 360
|
||||
settingsDiskSource.storeVaultTimeoutInMinutes(
|
||||
userId = mockUserId,
|
||||
vaultTimeoutInMinutes = vaultTimeoutInMinutes,
|
||||
)
|
||||
val actual = fakeSharedPreferences.getInt(
|
||||
"${vaultTimeoutBaseKey}_$mockUserId",
|
||||
0,
|
||||
)
|
||||
assertEquals(
|
||||
vaultTimeoutInMinutes,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeVaultTimeoutInMinutes for null values should clear SharedPreferences`() {
|
||||
val vaultTimeoutBaseKey = "bwPreferencesStorage:vaultTimeout"
|
||||
val mockUserId = "mockUserId"
|
||||
val previousValue = 123
|
||||
val vaultTimeoutKey = "${vaultTimeoutBaseKey}_$mockUserId"
|
||||
fakeSharedPreferences.edit {
|
||||
putInt(vaultTimeoutKey, previousValue)
|
||||
}
|
||||
assertTrue(fakeSharedPreferences.contains(vaultTimeoutKey))
|
||||
settingsDiskSource.storeVaultTimeoutInMinutes(
|
||||
userId = mockUserId,
|
||||
vaultTimeoutInMinutes = null,
|
||||
)
|
||||
assertFalse(fakeSharedPreferences.contains(vaultTimeoutKey))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.disk.util
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
|
||||
/**
|
||||
* Fake, memory-based implementation of [SettingsDiskSource].
|
||||
*/
|
||||
class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
|
||||
private val mutableVaultTimeoutInMinutesFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<Int?>>()
|
||||
|
||||
private val storedVaultTimeoutInMinutes = mutableMapOf<String, Int?>()
|
||||
|
||||
override fun getVaultTimeoutInMinutes(userId: String): Int? =
|
||||
storedVaultTimeoutInMinutes[userId]
|
||||
|
||||
override fun getVaultTimeoutInMinutesFlow(userId: String): Flow<Int?> =
|
||||
getMutableVaultTimeoutInMinutesFlow(userId = userId)
|
||||
.onSubscription { emit(getVaultTimeoutInMinutes(userId = userId)) }
|
||||
|
||||
override fun storeVaultTimeoutInMinutes(
|
||||
userId: String,
|
||||
vaultTimeoutInMinutes: Int?,
|
||||
) {
|
||||
storedVaultTimeoutInMinutes[userId] = vaultTimeoutInMinutes
|
||||
getMutableVaultTimeoutInMinutesFlow(userId = userId).tryEmit(vaultTimeoutInMinutes)
|
||||
}
|
||||
|
||||
//region Private helper functions
|
||||
|
||||
private fun getMutableVaultTimeoutInMinutesFlow(
|
||||
userId: String,
|
||||
): MutableSharedFlow<Int?> =
|
||||
mutableVaultTimeoutInMinutesFlowMap.getOrPut(userId) {
|
||||
bufferedMutableSharedFlow(replay = 1)
|
||||
}
|
||||
|
||||
//endregion Private helper functions
|
||||
}
|
Loading…
Add table
Reference in a new issue