mirror of
https://github.com/bitwarden/android.git
synced 2026-10-23 19:43:26 -05:00
PM-20123: Add flight recorder metadata (#5024)
This commit is contained in:
1 parent
dd0f08d759
commit
d7f771990e
5 files changed
+191
No files matched your search
+11
@@ -1,5 +1,6 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
|
||||
@@ -84,6 +85,16 @@ interface SettingsDiskSource {
|
||||
*/
|
||||
val hasUserLoggedInOrCreatedAccountFlow: Flow<Boolean?>
|
||||
|
||||
/**
|
||||
* The current status of whether the flight recorder is enabled.
|
||||
*/
|
||||
var flightRecorderData: FlightRecorderDataSet?
|
||||
|
||||
/**
|
||||
* Emits updates that track [flightRecorderData].
|
||||
*/
|
||||
val flightRecorderDataFlow: Flow<FlightRecorderDataSet?>
|
||||
|
||||
/**
|
||||
* Clears all the settings data for the given user.
|
||||
*/
|
||||
|
||||
+18
@@ -5,6 +5,7 @@ import androidx.core.content.edit
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.core.data.util.decodeFromStringOrNull
|
||||
import com.bitwarden.data.datasource.disk.BaseDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
|
||||
@@ -45,6 +46,7 @@ private const val CREATE_ACTION_COUNT = "createActionCount"
|
||||
private const val SHOULD_SHOW_ADD_LOGIN_COACH_MARK = "shouldShowAddLoginCoachMark"
|
||||
private const val SHOULD_SHOW_GENERATOR_COACH_MARK = "shouldShowGeneratorCoachMark"
|
||||
private const val RESUME_SCREEN = "resumeScreen"
|
||||
private const val FLIGHT_RECORDER_KEY = "flightRecorderData"
|
||||
|
||||
/**
|
||||
* Primary implementation of [SettingsDiskSource].
|
||||
@@ -84,6 +86,8 @@ class SettingsDiskSourceImpl(
|
||||
|
||||
private val mutableHasUserLoggedInOrCreatedAccountFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableFlightRecorderDataFlow = bufferedMutableSharedFlow<FlightRecorderDataSet?>()
|
||||
|
||||
private val mutableHasSeenAddLoginCoachMarkFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableHasSeenGeneratorCoachMarkFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
@@ -189,6 +193,20 @@ class SettingsDiskSourceImpl(
|
||||
get() = mutableHasUserLoggedInOrCreatedAccountFlow
|
||||
.onSubscription { emit(getBoolean(HAS_USER_LOGGED_IN_OR_CREATED_AN_ACCOUNT_KEY)) }
|
||||
|
||||
override var flightRecorderData: FlightRecorderDataSet?
|
||||
get() = getString(key = FLIGHT_RECORDER_KEY)
|
||||
?.let { json.decodeFromStringOrNull<FlightRecorderDataSet>(it) }
|
||||
set(value) {
|
||||
putString(
|
||||
key = FLIGHT_RECORDER_KEY,
|
||||
value = value?.let { json.encodeToString(it) },
|
||||
)
|
||||
mutableFlightRecorderDataFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override val flightRecorderDataFlow: Flow<FlightRecorderDataSet?>
|
||||
get() = mutableFlightRecorderDataFlow.onSubscription { emit(flightRecorderData) }
|
||||
|
||||
override fun clearData(userId: String) {
|
||||
storeVaultTimeoutInMinutes(userId = userId, vaultTimeoutInMinutes = null)
|
||||
storeVaultTimeoutAction(userId = userId, vaultTimeoutAction = null)
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Persistable class containing all required data for a all instances of the flight recorder.
|
||||
*/
|
||||
@Serializable
|
||||
data class FlightRecorderDataSet(
|
||||
@SerialName("data")
|
||||
val data: Set<FlightRecorderData>,
|
||||
) {
|
||||
/**
|
||||
* Returns `true` is there is an active [FlightRecorderDataSet.FlightRecorderData].
|
||||
*/
|
||||
val hasActiveFlightRecorderData: Boolean
|
||||
get() = this.activeFlightRecorderData != null
|
||||
|
||||
/**
|
||||
* Retrieves tha active log if available.
|
||||
*/
|
||||
val activeFlightRecorderData: FlightRecorderData?
|
||||
get() = this.data.find { it.isActive }
|
||||
|
||||
/**
|
||||
* Persistable class containing all required data for a single instance of the flight recorder.
|
||||
*/
|
||||
@Serializable
|
||||
data class FlightRecorderData(
|
||||
@SerialName("id")
|
||||
val id: String,
|
||||
|
||||
@SerialName("fileName")
|
||||
val fileName: String,
|
||||
|
||||
@SerialName("startTime")
|
||||
val startTimeMs: Long,
|
||||
|
||||
@SerialName("duration")
|
||||
val durationMs: Long,
|
||||
|
||||
@SerialName("isActive")
|
||||
val isActive: Boolean,
|
||||
)
|
||||
}
|
||||
+100
@@ -5,10 +5,12 @@ import app.cash.turbine.test
|
||||
import com.bitwarden.core.data.util.decodeFromStringOrNull
|
||||
import com.bitwarden.core.di.CoreModule
|
||||
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.ClearClipboardFrequency
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
|
||||
import com.x8bit.bitwarden.data.util.assertJsonEquals
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -83,6 +85,104 @@ class SettingsDiskSourceTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flightRecorderData should pull from SharedPreferences`() {
|
||||
val flightRecorderKey = "bwPreferencesStorage:flightRecorderData"
|
||||
val encodedData = """
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "51"
|
||||
"fileName": "flight_recorder_2025-04-03_14-22-40",
|
||||
"startTime": 1744059882,
|
||||
"duration": 3600,
|
||||
"isActive": false
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
val expected = FlightRecorderDataSet(
|
||||
data = setOf(
|
||||
FlightRecorderDataSet.FlightRecorderData(
|
||||
id = "51",
|
||||
fileName = "flight_recorder_2025-04-03_14-22-40",
|
||||
startTimeMs = 1_744_059_882L,
|
||||
durationMs = 3_600L,
|
||||
isActive = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Verify initial value is null and disk source matches shared preferences.
|
||||
assertNull(fakeSharedPreferences.getString(flightRecorderKey, null))
|
||||
assertNull(settingsDiskSource.flightRecorderData)
|
||||
|
||||
// Updating the shared preferences should update disk source.
|
||||
fakeSharedPreferences.edit { putString(flightRecorderKey, encodedData) }
|
||||
val actual = settingsDiskSource.flightRecorderData
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `flightRecorderDataFlow should react to changes in isFLightRecorderEnabled`() = runTest {
|
||||
val expected = FlightRecorderDataSet(
|
||||
data = setOf(
|
||||
FlightRecorderDataSet.FlightRecorderData(
|
||||
id = "52",
|
||||
fileName = "flight_recorder_2025-04-03_14-22-40",
|
||||
startTimeMs = 1_744_059_882L,
|
||||
durationMs = 3_600L,
|
||||
isActive = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
settingsDiskSource.flightRecorderDataFlow.test {
|
||||
// The initial values of the Flow and the property are in sync
|
||||
assertNull(settingsDiskSource.flightRecorderData)
|
||||
assertNull(awaitItem())
|
||||
|
||||
settingsDiskSource.flightRecorderData = expected
|
||||
assertEquals(expected, awaitItem())
|
||||
|
||||
settingsDiskSource.flightRecorderData = null
|
||||
assertNull(awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setting flightRecorderData should update SharedPreferences`() {
|
||||
val flightRecorderKey = "bwPreferencesStorage:flightRecorderData"
|
||||
val data = FlightRecorderDataSet(
|
||||
data = setOf(
|
||||
FlightRecorderDataSet.FlightRecorderData(
|
||||
id = "53",
|
||||
fileName = "flight_recorder_2025-04-03_14-22-40",
|
||||
startTimeMs = 1_744_059_882L,
|
||||
durationMs = 3_600L,
|
||||
isActive = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
val expected = """
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "53",
|
||||
"fileName": "flight_recorder_2025-04-03_14-22-40",
|
||||
"startTime": 1744059882,
|
||||
"duration": 3600,
|
||||
"isActive": true
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
settingsDiskSource.flightRecorderData = data
|
||||
val actual = fakeSharedPreferences.getString(flightRecorderKey, null)
|
||||
assertJsonEquals(expected, actual!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `systemBiometricIntegritySource should pull from SharedPreferences`() {
|
||||
val biometricIntegritySource = "bwPreferencesStorage:biometricIntegritySource"
|
||||
|
||||
+16
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.util
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.core.data.util.decodeFromStringOrNull
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
|
||||
@@ -50,6 +51,9 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
private val mutableShouldShowGeneratorCoachMarkFlow =
|
||||
bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableFlightRecorderDataFlow =
|
||||
bufferedMutableSharedFlow<FlightRecorderDataSet?>(replay = 1)
|
||||
|
||||
private var storedAppLanguage: AppLanguage? = null
|
||||
private var storedAppTheme: AppTheme = AppTheme.DEFAULT
|
||||
private val storedLastSyncTime = mutableMapOf<String, Instant?>()
|
||||
@@ -80,6 +84,7 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
private var createSendActionCount: Int? = null
|
||||
private var hasSeenAddLoginCoachMark: Boolean? = null
|
||||
private var hasSeenGeneratorCoachMark: Boolean? = null
|
||||
private var storedFlightRecorderData: FlightRecorderDataSet? = null
|
||||
|
||||
private val mutableShowAutoFillSettingBadgeFlowMap =
|
||||
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||
@@ -173,6 +178,17 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
emit(hasUserLoggedInOrCreatedAccount)
|
||||
}
|
||||
|
||||
override var flightRecorderData: FlightRecorderDataSet?
|
||||
get() = storedFlightRecorderData
|
||||
set(value) {
|
||||
storedFlightRecorderData = value
|
||||
mutableFlightRecorderDataFlow.tryEmit(value)
|
||||
}
|
||||
|
||||
override val flightRecorderDataFlow: Flow<FlightRecorderDataSet?>
|
||||
get() = mutableFlightRecorderDataFlow
|
||||
.onSubscription { emit(storedFlightRecorderData) }
|
||||
|
||||
override fun getAccountBiometricIntegrityValidity(
|
||||
userId: String,
|
||||
systemBioIntegrityState: String,
|
||||
|
||||
Reference in new issue
Block a user