mirror of
https://github.com/bitwarden/android.git
synced 2026-02-04 03:05:28 -06:00
Add concrete FlightRecorderDiskSource (#6281)
This commit is contained in:
parent
5245a7a0c7
commit
c4a94cf5d1
@ -5,7 +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.bitwarden.data.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
|
||||
@ -47,7 +47,6 @@ 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"
|
||||
private const val IS_DYNAMIC_COLORS_ENABLED = "isDynamicColorsEnabled"
|
||||
private const val BROWSER_AUTOFILL_DIALOG_RESHOW_TIME = "browserAutofillDialogReshowTime"
|
||||
|
||||
@ -58,8 +57,10 @@ private const val BROWSER_AUTOFILL_DIALOG_RESHOW_TIME = "browserAutofillDialogRe
|
||||
class SettingsDiskSourceImpl(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val json: Json,
|
||||
flightRecorderDiskSource: FlightRecorderDiskSource,
|
||||
) : BaseDiskSource(sharedPreferences = sharedPreferences),
|
||||
SettingsDiskSource {
|
||||
SettingsDiskSource,
|
||||
FlightRecorderDiskSource by flightRecorderDiskSource {
|
||||
private val mutableAppLanguageFlow = bufferedMutableSharedFlow<AppLanguage?>(replay = 1)
|
||||
private val mutableAppThemeFlow = bufferedMutableSharedFlow<AppTheme>(replay = 1)
|
||||
|
||||
@ -92,8 +93,6 @@ class SettingsDiskSourceImpl(
|
||||
|
||||
private val mutableHasUserLoggedInOrCreatedAccountFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableFlightRecorderDataFlow = bufferedMutableSharedFlow<FlightRecorderDataSet?>()
|
||||
|
||||
private val mutableHasSeenAddLoginCoachMarkFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
|
||||
private val mutableHasSeenGeneratorCoachMarkFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||
@ -214,20 +213,6 @@ 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 var browserAutofillDialogReshowTime: Instant?
|
||||
get() = getLong(key = BROWSER_AUTOFILL_DIALOG_RESHOW_TIME)?.let { Instant.ofEpochMilli(it) }
|
||||
set(value) {
|
||||
|
||||
@ -5,6 +5,7 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.room.Room
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.data.datasource.disk.di.EncryptedPreferences
|
||||
import com.bitwarden.data.datasource.disk.di.UnencryptedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
@ -139,10 +140,12 @@ object PlatformDiskModule {
|
||||
fun provideSettingsDiskSource(
|
||||
@UnencryptedPreferences sharedPreferences: SharedPreferences,
|
||||
json: Json,
|
||||
flightRecorderDiskSource: FlightRecorderDiskSource,
|
||||
): SettingsDiskSource =
|
||||
SettingsDiskSourceImpl(
|
||||
sharedPreferences = sharedPreferences,
|
||||
json = json,
|
||||
flightRecorderDiskSource = flightRecorderDiskSource,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
||||
@ -12,8 +12,6 @@ import com.bitwarden.core.data.manager.toast.ToastManagerImpl
|
||||
import com.bitwarden.cxf.registry.CredentialExchangeRegistry
|
||||
import com.bitwarden.cxf.registry.dsl.credentialExchangeRegistry
|
||||
import com.bitwarden.data.manager.NativeLibraryManager
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderManager
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderWriter
|
||||
import com.bitwarden.data.repository.ServerConfigRepository
|
||||
import com.bitwarden.network.BitwardenServiceClient
|
||||
import com.bitwarden.network.service.EventService
|
||||
@ -106,22 +104,6 @@ object PlatformManagerModule {
|
||||
application: Application,
|
||||
): AppStateManager = AppStateManagerImpl(application = application)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFlightRecorderManager(
|
||||
@ApplicationContext context: Context,
|
||||
clock: Clock,
|
||||
dispatcherManager: DispatcherManager,
|
||||
settingsDiskSource: SettingsDiskSource,
|
||||
flightRecorderWriter: FlightRecorderWriter,
|
||||
): FlightRecorderManager = FlightRecorderManager.create(
|
||||
context = context,
|
||||
clock = clock,
|
||||
dispatcherManager = dispatcherManager,
|
||||
flightRecorderDiskSource = settingsDiskSource,
|
||||
flightRecorderWriter = flightRecorderWriter,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthenticatorBridgeProcessor(
|
||||
|
||||
@ -2,17 +2,16 @@ package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.data.util.assertJsonEquals
|
||||
import com.bitwarden.core.data.util.decodeFromStringOrNull
|
||||
import com.bitwarden.core.di.CoreModule
|
||||
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
|
||||
import com.bitwarden.data.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
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.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
@ -27,9 +26,10 @@ class SettingsDiskSourceTest {
|
||||
private val fakeSharedPreferences = FakeSharedPreferences()
|
||||
private val json = CoreModule.providesJson()
|
||||
|
||||
private val settingsDiskSource = SettingsDiskSourceImpl(
|
||||
private val settingsDiskSource: SettingsDiskSource = SettingsDiskSourceImpl(
|
||||
sharedPreferences = fakeSharedPreferences,
|
||||
json = json,
|
||||
flightRecorderDiskSource = mockk(),
|
||||
)
|
||||
|
||||
@Test
|
||||
@ -85,104 +85,6 @@ 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"
|
||||
|
||||
@ -2,7 +2,8 @@ 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.bitwarden.data.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.data.datasource.disk.util.FakeFlightRecorderDiskSource
|
||||
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppResumeScreenData
|
||||
@ -19,7 +20,10 @@ import java.time.Instant
|
||||
/**
|
||||
* Fake, memory-based implementation of [SettingsDiskSource].
|
||||
*/
|
||||
class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
class FakeSettingsDiskSource(
|
||||
flightRecorderDiskSource: FakeFlightRecorderDiskSource = FakeFlightRecorderDiskSource(),
|
||||
) : SettingsDiskSource,
|
||||
FlightRecorderDiskSource by flightRecorderDiskSource {
|
||||
|
||||
private val mutableAppLanguageFlow = bufferedMutableSharedFlow<AppLanguage?>(replay = 1)
|
||||
|
||||
@ -52,9 +56,6 @@ 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?>()
|
||||
@ -86,7 +87,6 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
private var createSendActionCount: Int? = null
|
||||
private var hasSeenAddLoginCoachMark: Boolean? = null
|
||||
private var hasSeenGeneratorCoachMark: Boolean? = null
|
||||
private var storedFlightRecorderData: FlightRecorderDataSet? = null
|
||||
private var storedIsDynamicColorsEnabled: Boolean? = null
|
||||
private var storedBrowserAutofillDialogReshowTime: Instant? = null
|
||||
|
||||
@ -200,17 +200,6 @@ 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 var browserAutofillDialogReshowTime: Instant?
|
||||
get() = storedBrowserAutofillDialogReshowTime
|
||||
set(value) {
|
||||
@ -486,13 +475,6 @@ class FakeSettingsDiskSource : SettingsDiskSource {
|
||||
return storedAppResumeScreenData[userId]?.let { Json.decodeFromStringOrNull(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the stored [FlightRecorderDataSet] matches the [expected] one.
|
||||
*/
|
||||
fun assertFlightRecorderData(expected: FlightRecorderDataSet) {
|
||||
assertEquals(expected, storedFlightRecorderData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the stored last sync time matches the [expected] one.
|
||||
*/
|
||||
|
||||
@ -2,13 +2,14 @@ package com.bitwarden.authenticator.data.platform.datasource.disk
|
||||
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.data.model.DefaultSaveOption
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Primary access point for general settings-related disk information.
|
||||
*/
|
||||
interface SettingsDiskSource {
|
||||
interface SettingsDiskSource : FlightRecorderDiskSource {
|
||||
|
||||
/**
|
||||
* The currently persisted app language (or `null` if not set).
|
||||
|
||||
@ -5,6 +5,7 @@ import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.data.model.DefaultSaveOption
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.data.datasource.disk.BaseDiskSource
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
@ -32,8 +33,10 @@ private const val DEFAULT_ALERT_THRESHOLD_SECONDS = 7
|
||||
*/
|
||||
class SettingsDiskSourceImpl(
|
||||
sharedPreferences: SharedPreferences,
|
||||
flightRecorderDiskSource: FlightRecorderDiskSource,
|
||||
) : BaseDiskSource(sharedPreferences = sharedPreferences),
|
||||
SettingsDiskSource {
|
||||
SettingsDiskSource,
|
||||
FlightRecorderDiskSource by flightRecorderDiskSource {
|
||||
private val mutableAppThemeFlow =
|
||||
bufferedMutableSharedFlow<AppTheme>(replay = 1)
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagOver
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.FeatureFlagOverrideDiskSourceImpl
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSourceImpl
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.data.datasource.disk.di.UnencryptedPreferences
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@ -23,8 +24,12 @@ object PlatformDiskModule {
|
||||
@Singleton
|
||||
fun provideSettingsDiskSource(
|
||||
@UnencryptedPreferences sharedPreferences: SharedPreferences,
|
||||
flightRecorderDiskSource: FlightRecorderDiskSource,
|
||||
): SettingsDiskSource =
|
||||
SettingsDiskSourceImpl(sharedPreferences = sharedPreferences)
|
||||
SettingsDiskSourceImpl(
|
||||
sharedPreferences = sharedPreferences,
|
||||
flightRecorderDiskSource = flightRecorderDiskSource,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
||||
@ -12,6 +12,7 @@ import com.bitwarden.network.interceptor.BaseUrlsProvider
|
||||
import com.bitwarden.network.model.AuthTokenData
|
||||
import com.bitwarden.network.model.BitwardenServiceClientConfig
|
||||
import com.bitwarden.network.service.ConfigService
|
||||
import com.bitwarden.network.service.DownloadService
|
||||
import com.bitwarden.network.ssl.CertificateProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@ -71,4 +72,10 @@ object PlatformNetworkModule {
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDownloadService(
|
||||
bitwardenServiceClient: BitwardenServiceClient,
|
||||
): DownloadService = bitwardenServiceClient.downloadService
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.bitwarden.authenticator.data.platform.repository
|
||||
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.data.model.DefaultSaveOption
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderManager
|
||||
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@ -9,7 +10,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
/**
|
||||
* Provides an API for observing and modifying settings state.
|
||||
*/
|
||||
interface SettingsRepository {
|
||||
interface SettingsRepository : FlightRecorderManager {
|
||||
|
||||
/**
|
||||
* The [AppLanguage] for the current user.
|
||||
|
||||
@ -5,6 +5,7 @@ import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSou
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.data.model.DefaultSaveOption
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderManager
|
||||
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -20,8 +21,10 @@ private val DEFAULT_IS_SCREEN_CAPTURE_ALLOWED = BuildConfig.DEBUG
|
||||
*/
|
||||
class SettingsRepositoryImpl(
|
||||
private val settingsDiskSource: SettingsDiskSource,
|
||||
flightRecorderManager: FlightRecorderManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
) : SettingsRepository {
|
||||
) : SettingsRepository,
|
||||
FlightRecorderManager by flightRecorderManager {
|
||||
|
||||
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import com.bitwarden.authenticator.data.platform.repository.DebugMenuRepositoryI
|
||||
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||
import com.bitwarden.authenticator.data.platform.repository.SettingsRepositoryImpl
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderManager
|
||||
import com.bitwarden.data.repository.ServerConfigRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@ -25,10 +26,12 @@ object PlatformRepositoryModule {
|
||||
@Singleton
|
||||
fun provideSettingsRepository(
|
||||
settingsDiskSource: SettingsDiskSource,
|
||||
flightRecorderManager: FlightRecorderManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): SettingsRepository =
|
||||
SettingsRepositoryImpl(
|
||||
settingsDiskSource = settingsDiskSource,
|
||||
flightRecorderManager = flightRecorderManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.bitwarden.authenticator.ui.platform.manager.di
|
||||
|
||||
import com.bitwarden.authenticator.ui.platform.manager.AuthenticatorBuildInfoManagerImpl
|
||||
import com.bitwarden.authenticator.ui.platform.model.SnackbarRelay
|
||||
import com.bitwarden.core.data.manager.BuildInfoManager
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
|
||||
import com.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManagerImpl
|
||||
@ -16,6 +18,10 @@ import javax.inject.Singleton
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class PlatformUiManagerModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBuildInfoManager(): BuildInfoManager = AuthenticatorBuildInfoManagerImpl()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSnackbarRelayManager(
|
||||
|
||||
@ -4,6 +4,7 @@ import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.authenticator.ui.platform.feature.settings.data.model.DefaultSaveOption
|
||||
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
@ -15,8 +16,9 @@ class SettingDiskSourceTest {
|
||||
|
||||
private val sharedPreferences: FakeSharedPreferences = FakeSharedPreferences()
|
||||
|
||||
private val settingDiskSource = SettingsDiskSourceImpl(
|
||||
sharedPreferences,
|
||||
private val settingDiskSource: SettingsDiskSource = SettingsDiskSourceImpl(
|
||||
sharedPreferences = sharedPreferences,
|
||||
flightRecorderDiskSource = mockk(),
|
||||
)
|
||||
|
||||
@Test
|
||||
|
||||
@ -23,8 +23,9 @@ class SettingsRepositoryTest {
|
||||
every { getAlertThresholdSeconds() } returns 7
|
||||
}
|
||||
|
||||
private val settingsRepository = SettingsRepositoryImpl(
|
||||
private val settingsRepository: SettingsRepository = SettingsRepositoryImpl(
|
||||
settingsDiskSource = settingsDiskSource,
|
||||
flightRecorderManager = mockk(),
|
||||
dispatcherManager = FakeDispatcherManager(),
|
||||
)
|
||||
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package com.bitwarden.data.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.core.data.util.decodeFromStringOrNull
|
||||
import com.bitwarden.data.datasource.disk.model.FlightRecorderDataSet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private const val FLIGHT_RECORDER_KEY = "flightRecorderData"
|
||||
|
||||
/**
|
||||
* Primary implementation of [FlightRecorderDiskSource].
|
||||
*/
|
||||
internal class FlightRecorderDiskSourceImpl(
|
||||
private val json: Json,
|
||||
sharedPreferences: SharedPreferences,
|
||||
) : BaseDiskSource(sharedPreferences = sharedPreferences),
|
||||
FlightRecorderDiskSource {
|
||||
private val mutableFlightRecorderDataFlow = bufferedMutableSharedFlow<FlightRecorderDataSet?>()
|
||||
|
||||
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) }
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
package com.bitwarden.data.datasource.disk.di
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.data.datasource.disk.ConfigDiskSource
|
||||
import com.bitwarden.data.datasource.disk.ConfigDiskSourceImpl
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSourceImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@ -26,4 +28,15 @@ object DiskModule {
|
||||
sharedPreferences = sharedPreferences,
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFlightRecorderDiskSource(
|
||||
@UnencryptedPreferences sharedPreferences: SharedPreferences,
|
||||
json: Json,
|
||||
): FlightRecorderDiskSource =
|
||||
FlightRecorderDiskSourceImpl(
|
||||
sharedPreferences = sharedPreferences,
|
||||
json = json,
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,12 +3,15 @@ package com.bitwarden.data.manager.di
|
||||
import android.content.Context
|
||||
import com.bitwarden.core.data.manager.BuildInfoManager
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.data.manager.BitwardenPackageManager
|
||||
import com.bitwarden.data.manager.BitwardenPackageManagerImpl
|
||||
import com.bitwarden.data.manager.NativeLibraryManager
|
||||
import com.bitwarden.data.manager.NativeLibraryManagerImpl
|
||||
import com.bitwarden.data.manager.file.FileManager
|
||||
import com.bitwarden.data.manager.file.FileManagerImpl
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderManager
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderManagerImpl
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderWriter
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderWriterImpl
|
||||
import com.bitwarden.network.service.DownloadService
|
||||
@ -45,6 +48,22 @@ object DataManagerModule {
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFlightRecorderManager(
|
||||
@ApplicationContext context: Context,
|
||||
clock: Clock,
|
||||
dispatcherManager: DispatcherManager,
|
||||
flightRecorderDiskSource: FlightRecorderDiskSource,
|
||||
flightRecorderWriter: FlightRecorderWriter,
|
||||
): FlightRecorderManager = FlightRecorderManagerImpl(
|
||||
context = context,
|
||||
clock = clock,
|
||||
dispatcherManager = dispatcherManager,
|
||||
flightRecorderDiskSource = flightRecorderDiskSource,
|
||||
flightRecorderWriter = flightRecorderWriter,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFlightRecorderWriter(
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
package com.bitwarden.data.manager.flightrecorder
|
||||
|
||||
import android.content.Context
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.data.datasource.disk.model.FlightRecorderDataSet
|
||||
import com.bitwarden.data.manager.model.FlightRecorderDuration
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.time.Clock
|
||||
|
||||
/**
|
||||
* Manager class that handles recording logs for the flight recorder.
|
||||
@ -46,24 +42,4 @@ interface FlightRecorderManager {
|
||||
* Deletes the raw log files and metadata.
|
||||
*/
|
||||
fun deleteAllLogs()
|
||||
|
||||
@Suppress("UndocumentedPublicClass")
|
||||
companion object {
|
||||
/**
|
||||
* Creates a new instance of the [FlightRecorderManager].
|
||||
*/
|
||||
fun create(
|
||||
context: Context,
|
||||
clock: Clock,
|
||||
flightRecorderDiskSource: FlightRecorderDiskSource,
|
||||
flightRecorderWriter: FlightRecorderWriter,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): FlightRecorderManager = FlightRecorderManagerImpl(
|
||||
context = context,
|
||||
clock = clock,
|
||||
flightRecorderDiskSource = flightRecorderDiskSource,
|
||||
flightRecorderWriter = flightRecorderWriter,
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
package com.bitwarden.data.datasource.disk
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.data.util.assertJsonEquals
|
||||
import com.bitwarden.core.di.CoreModule
|
||||
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
|
||||
import com.bitwarden.data.datasource.disk.model.FlightRecorderDataSet
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class FlightRecorderDiskSourceTest {
|
||||
private val fakeSharedPreferences = FakeSharedPreferences()
|
||||
private val json = CoreModule.providesJson()
|
||||
|
||||
private val flightRecorderDiskSource = FlightRecorderDiskSourceImpl(
|
||||
sharedPreferences = fakeSharedPreferences,
|
||||
json = json,
|
||||
)
|
||||
|
||||
@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(flightRecorderDiskSource.flightRecorderData)
|
||||
|
||||
// Updating the shared preferences should update disk source.
|
||||
fakeSharedPreferences.edit { putString(flightRecorderKey, encodedData) }
|
||||
val actual = flightRecorderDiskSource.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,
|
||||
),
|
||||
),
|
||||
)
|
||||
flightRecorderDiskSource.flightRecorderDataFlow.test {
|
||||
// The initial values of the Flow and the property are in sync
|
||||
assertNull(flightRecorderDiskSource.flightRecorderData)
|
||||
assertNull(awaitItem())
|
||||
|
||||
flightRecorderDiskSource.flightRecorderData = expected
|
||||
assertEquals(expected, awaitItem())
|
||||
|
||||
flightRecorderDiskSource.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()
|
||||
flightRecorderDiskSource.flightRecorderData = data
|
||||
val actual = fakeSharedPreferences.getString(flightRecorderKey, null)
|
||||
assertJsonEquals(expected, actual!!)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user