[PM-24411] Use BuildInfoManager for build-related information (#5663)

This commit is contained in:
Patrick Honkonen
2025-08-11 18:34:47 +00:00
committed by GitHub
parent 9b120701eb
commit d06c87beb3
9 changed files with 93 additions and 139 deletions
@@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.di
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.network.BitwardenServiceClient
import com.bitwarden.network.bitwardenServiceClient
import com.bitwarden.network.interceptor.BaseUrlsProvider
@@ -13,7 +14,6 @@ import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CL
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_CLIENT_VERSION
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_USER_AGENT
import com.x8bit.bitwarden.data.platform.manager.CertificateManager
import com.x8bit.bitwarden.data.platform.util.isDevBuild
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -54,6 +54,7 @@ object PlatformNetworkModule {
baseUrlsProvider: BaseUrlsProvider,
authDiskSource: AuthDiskSource,
certificateManager: CertificateManager,
buildInfoManager: BuildInfoManager,
clock: Clock,
): BitwardenServiceClient = bitwardenServiceClient(
BitwardenServiceClientConfig(
@@ -67,7 +68,7 @@ object PlatformNetworkModule {
authTokenProvider = authTokenManager,
baseUrlsProvider = baseUrlsProvider,
certificateProvider = certificateManager,
enableHttpBodyLogging = isDevBuild,
enableHttpBodyLogging = buildInfoManager.isDevBuild,
),
)
}
@@ -1,73 +0,0 @@
package com.x8bit.bitwarden.data.platform.util
import android.os.Build
import com.x8bit.bitwarden.BuildConfig
/**
* A boolean property that indicates whether the current build flavor is "fdroid".
*/
val isFdroid: Boolean
get() = BuildConfig.FLAVOR == "fdroid"
/**
* A boolean property that indicates whether the current build is a dev build.
*/
val isDevBuild: Boolean
get() = BuildConfig.BUILD_TYPE == "debug"
/**
* A string that represents a displayable app version.
*/
val versionData: String
get() = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
/**
* A string that represents a displayable SDK version.
*/
val sdkData: String get() = BuildConfig.SDK_VERSION
/**
* A string that represents device data.
*/
val deviceData: String get() = "$deviceBrandModel $osInfo $buildInfo"
/**
* A string representing the CI information if available.
*/
val ciBuildInfo: String? get() = BuildConfig.CI_INFO.takeUnless { it.isBlank() }
/**
* A string representing the build flavor or blank if it is the standard configuration.
*/
private val buildFlavorName: String
get() = when (BuildConfig.FLAVOR) {
"standard" -> ""
else -> "-${BuildConfig.FLAVOR}"
}
/**
* A string representing the build type.
*/
private val buildTypeName: String
get() = when (BuildConfig.BUILD_TYPE) {
"debug" -> "dev"
"release" -> "prod"
else -> BuildConfig.BUILD_TYPE
}
/**
* A string representing the device brand and model.
*/
private val deviceBrandModel: String get() = "\uD83D\uDCF1 ${Build.BRAND} ${Build.MODEL}"
/**
* A string representing the operating system information.
*/
private val osInfo: String get() = "\uD83E\uDD16 ${Build.VERSION.RELEASE}@${Build.VERSION.SDK_INT}"
/**
* A string representing the build information.
*/
private val buildInfo: String
get() = "\uD83D\uDCE6 $buildTypeName" +
buildFlavorName.takeUnless { it.isBlank() }?.let { " $it" }.orEmpty()
@@ -3,9 +3,11 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.about
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.data.repository.ServerConfigRepository
import com.bitwarden.data.repository.util.baseWebVaultUrlOrDefault
import com.bitwarden.ui.platform.base.BaseViewModel
import com.bitwarden.ui.platform.manager.util.deviceData
import com.bitwarden.ui.util.Text
import com.bitwarden.ui.util.asText
import com.bitwarden.ui.util.concat
@@ -14,11 +16,6 @@ import com.x8bit.bitwarden.data.platform.manager.LogsManager
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.util.ciBuildInfo
import com.x8bit.bitwarden.data.platform.util.deviceData
import com.x8bit.bitwarden.data.platform.util.isFdroid
import com.x8bit.bitwarden.data.platform.util.sdkData
import com.x8bit.bitwarden.data.platform.util.versionData
import com.x8bit.bitwarden.ui.platform.feature.settings.about.util.getStopsLoggingStringForActiveLog
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
@@ -44,13 +41,14 @@ class AboutViewModel @Inject constructor(
private val logsManager: LogsManager,
private val environmentRepository: EnvironmentRepository,
private val settingsRepository: SettingsRepository,
buildInfoManager: BuildInfoManager,
serverConfigRepository: ServerConfigRepository,
) : BaseViewModel<AboutState, AboutEvent, AboutAction>(
initialState = savedStateHandle[KEY_STATE] ?: run {
val serverData = serverConfigRepository.serverConfigStateFlow.value?.serverData
AboutState(
version = "Version: $versionData".asText(),
sdkVersion = "\uD83E\uDD80 SDK: $sdkData".asText(),
version = "Version: ${buildInfoManager.versionData}".asText(),
sdkVersion = "\uD83E\uDD80 SDK: ${buildInfoManager.sdkData}".asText(),
serverData = StringBuilder()
.append("\uD83C\uDF29 Server:")
.apply {
@@ -60,10 +58,10 @@ class AboutViewModel @Inject constructor(
}
.toString()
.asText(),
deviceData = deviceData.asText(),
ciData = ciBuildInfo?.let { "\n$it" }.orEmpty().asText(),
deviceData = buildInfoManager.deviceData.asText(),
ciData = buildInfoManager.ciBuildInfo?.let { "\n$it" }.orEmpty().asText(),
isSubmitCrashLogsEnabled = logsManager.isEnabled,
shouldShowCrashLogsButton = !isFdroid,
shouldShowCrashLogsButton = !buildInfoManager.isFdroid,
isFlightRecorderEnabled = settingsRepository
.flightRecorderData
.hasActiveFlightRecorderData,
@@ -2,7 +2,6 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.pending
import android.Manifest
import android.annotation.SuppressLint
import android.os.Build
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -40,7 +39,6 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bitwarden.core.util.isBuildVersionAtLeast
import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.LifecycleEventEffect
import com.bitwarden.ui.platform.base.util.cardStyle
@@ -56,7 +54,6 @@ import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.data.platform.util.isFdroid
import com.x8bit.bitwarden.ui.platform.components.bottomsheet.BitwardenModalBottomSheet
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.model.rememberBitwardenPullToRefreshState
@@ -109,8 +106,6 @@ fun PendingRequestsScreen(
}
val hideBottomSheet = state.hideBottomSheet ||
isFdroid ||
!isBuildVersionAtLeast(Build.VERSION_CODES.TIRAMISU) ||
permissionsManager.checkPermission(Manifest.permission.POST_NOTIFICATIONS) ||
permissionsManager.shouldShowRequestPermissionRationale(
permission = Manifest.permission.POST_NOTIFICATIONS,
@@ -1,9 +1,13 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.pendingrequests
import android.os.Build
import android.os.Parcelable
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.core.data.util.toFormattedDateTimeStyle
import com.bitwarden.core.util.isBuildVersionAtLeast
import com.bitwarden.core.util.isOverFiveMinutesOld
import com.bitwarden.ui.platform.base.BackgroundEvent
import com.bitwarden.ui.platform.base.BaseViewModel
@@ -38,6 +42,7 @@ class PendingRequestsViewModel @Inject constructor(
private val authRepository: AuthRepository,
snackbarRelayManager: SnackbarRelayManager,
settingsRepository: SettingsRepository,
buildInfoManager: BuildInfoManager,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<PendingRequestsState, PendingRequestsEvent, PendingRequestsAction>(
initialState = savedStateHandle[KEY_STATE] ?: PendingRequestsState(
@@ -45,7 +50,8 @@ class PendingRequestsViewModel @Inject constructor(
viewState = PendingRequestsState.ViewState.Loading,
isPullToRefreshSettingEnabled = settingsRepository.getPullToRefreshEnabledFlow().value,
isRefreshing = false,
hideBottomSheet = false,
internalHideBottomSheet = false,
isFdroid = buildInfoManager.isFdroid,
),
) {
private var authJob: Job = Job().apply { complete() }
@@ -103,7 +109,7 @@ class PendingRequestsViewModel @Inject constructor(
}
private fun handleHideBottomSheet() {
mutableStateFlow.update { it.copy(hideBottomSheet = true) }
mutableStateFlow.update { it.copy(internalHideBottomSheet = true) }
}
private fun handleOnLifecycleResumed() {
@@ -221,8 +227,19 @@ data class PendingRequestsState(
val viewState: ViewState,
private val isPullToRefreshSettingEnabled: Boolean,
val isRefreshing: Boolean,
val hideBottomSheet: Boolean,
private val internalHideBottomSheet: Boolean,
private val isFdroid: Boolean,
) : Parcelable {
/**
* Indicates that the bottom sheet should be hidden.
*/
@get:ChecksSdkIntAtLeast(parameter = Build.VERSION_CODES.TIRAMISU)
val hideBottomSheet: Boolean
get() = internalHideBottomSheet &&
!isFdroid &&
isBuildVersionAtLeast(Build.VERSION_CODES.TIRAMISU)
/**
* Indicates that the pull-to-refresh should be enabled in the UI.
*/
@@ -2,10 +2,12 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.about
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.data.repository.ServerConfigRepository
import com.bitwarden.data.repository.util.baseWebVaultUrlOrDefault
import com.bitwarden.ui.platform.base.BaseViewModelTest
import com.bitwarden.ui.platform.manager.util.deviceData
import com.bitwarden.ui.util.asText
import com.bitwarden.ui.util.concat
import com.x8bit.bitwarden.data.platform.datasource.disk.model.FlightRecorderDataSet
@@ -51,6 +53,14 @@ class AboutViewModelTest : BaseViewModelTest() {
private val serverConfigRepository: ServerConfigRepository = mockk {
every { serverConfigStateFlow } returns mutableServerConfigStateFlow
}
private val buildInfoManager: BuildInfoManager = mockk {
every { buildTypeName } returns "mockBuildType"
every { versionData } returns "mockVersionData"
every { sdkData } returns "mockSdkData"
every { deviceData } returns "mockDeviceData"
every { ciBuildInfo } returns "mockCiBuildInfo"
every { isFdroid } returns false
}
@AfterEach
fun tearDown() {
@@ -233,6 +243,7 @@ class AboutViewModelTest : BaseViewModelTest() {
logsManager = logsManager,
settingsRepository = settingsRepository,
serverConfigRepository = serverConfigRepository,
buildInfoManager = buildInfoManager,
)
}
@@ -16,7 +16,6 @@ import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.core.util.isBuildVersionAtLeast
import com.bitwarden.ui.util.asText
import com.bitwarden.ui.util.assertNoDialogExists
import com.x8bit.bitwarden.data.platform.util.isFdroid
import com.x8bit.bitwarden.data.util.advanceTimeByAndRunCurrent
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarData
@@ -54,9 +53,7 @@ class PendingRequestsScreenTest : BitwardenComposeTest() {
@Before
fun setUp() {
mockkStatic(::isFdroid)
mockkStatic(::isBuildVersionAtLeast)
every { isFdroid } returns false
every { isBuildVersionAtLeast(any()) } returns true
setContent(
permissionsManager = permissionsManager,
@@ -71,7 +68,6 @@ class PendingRequestsScreenTest : BitwardenComposeTest() {
@After
fun tearDown() {
unmockkStatic(::isFdroid)
unmockkStatic(::isBuildVersionAtLeast)
}
@@ -114,7 +110,7 @@ class PendingRequestsScreenTest : BitwardenComposeTest() {
),
),
),
hideBottomSheet = true,
internalHideBottomSheet = true,
)
composeTestRule.onNodeWithText("Decline all requests").performClick()
composeTestRule
@@ -146,7 +142,7 @@ class PendingRequestsScreenTest : BitwardenComposeTest() {
),
),
),
hideBottomSheet = true,
internalHideBottomSheet = true,
)
composeTestRule.onNodeWithText("Decline all requests").performClick()
composeTestRule
@@ -191,5 +187,6 @@ private val DEFAULT_STATE: PendingRequestsState = PendingRequestsState(
viewState = PendingRequestsState.ViewState.Loading,
isPullToRefreshSettingEnabled = false,
isRefreshing = false,
hideBottomSheet = false,
internalHideBottomSheet = false,
isFdroid = false,
)
@@ -2,7 +2,9 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.pending
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.core.data.manager.BuildInfoManager
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.core.data.util.toFormattedDateTimeStyle
import com.bitwarden.ui.platform.base.BaseViewModelTest
import com.bitwarden.ui.util.asText
import com.x8bit.bitwarden.data.auth.manager.model.AuthRequest
@@ -17,15 +19,20 @@ import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.Clock
import java.time.Instant
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.time.temporal.TemporalAccessor
class PendingRequestsViewModelTest : BaseViewModelTest() {
@@ -49,6 +56,19 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
getSnackbarDataFlow(relay = any(), relays = anyVararg())
} returns mutableSnackbarDataFlow
}
private val buildInfoManager = mockk<BuildInfoManager> {
every { isFdroid } returns false
}
@BeforeEach
fun setUp() {
mockkStatic(TemporalAccessor::toFormattedDateTimeStyle)
}
@AfterEach
fun tearDown() {
unmockkStatic(TemporalAccessor::toFormattedDateTimeStyle)
}
@Test
fun `init should call getAuthRequestsWithUpdates`() {
@@ -74,10 +94,14 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
@Suppress("LongMethod")
@Test
fun `getPendingResults success with content should update state with some requests filtered`() {
val dateTimeFormatter = DateTimeFormatter
.ofPattern("M/d/yy, hh:mm a")
.withZone(fixedClock.zone)
val nowZonedDateTime = ZonedDateTime.now(fixedClock)
every {
nowZonedDateTime.toFormattedDateTimeStyle(
dateStyle = FormatStyle.SHORT,
timeStyle = FormatStyle.SHORT,
clock = fixedClock,
)
} returns "10/27/23, 12:00 PM"
val requestList = listOf(
AuthRequest(
id = "1",
@@ -139,7 +163,7 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
PendingRequestsState.ViewState.Content.PendingLoginRequest(
fingerprintPhrase = "pantry-overdue-survive-sleep-jab",
platform = "Android",
timestamp = nowZonedDateTime.format(dateTimeFormatter),
timestamp = "10/27/23, 12:00 PM",
),
),
),
@@ -190,7 +214,7 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
fun `on HideBottomSheet should make hideBottomSheet true`() {
val viewModel = createViewModel()
viewModel.trySendAction(PendingRequestsAction.HideBottomSheet)
assertEquals(DEFAULT_STATE.copy(hideBottomSheet = true), viewModel.stateFlow.value)
assertEquals(DEFAULT_STATE.copy(internalHideBottomSheet = true), viewModel.stateFlow.value)
}
@Test
@@ -308,6 +332,20 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
val nowZonedDateTime = ZonedDateTime.now(fixedClock)
val fiveMinZonedDateTime = ZonedDateTime.now(fixedClock).minusMinutes(5)
val sixMinZonedDateTime = ZonedDateTime.now(fixedClock).minusMinutes(6)
every {
nowZonedDateTime.toFormattedDateTimeStyle(
dateStyle = FormatStyle.SHORT,
timeStyle = FormatStyle.SHORT,
clock = fixedClock,
)
} returns "10/27/23, 12:00 PM"
every {
fiveMinZonedDateTime.toFormattedDateTimeStyle(
dateStyle = FormatStyle.SHORT,
timeStyle = FormatStyle.SHORT,
clock = fixedClock,
)
} returns "10/27/23, 11:55 AM"
val requestList = listOf(
AuthRequest(
id = "1",
@@ -393,6 +431,7 @@ class PendingRequestsViewModelTest : BaseViewModelTest() {
authRepository = authRepository,
settingsRepository = settingsRepository,
snackbarRelayManager = snackbarRelayManager,
buildInfoManager = buildInfoManager,
savedStateHandle = SavedStateHandle().apply { set("state", state) },
)
}
@@ -402,5 +441,6 @@ private val DEFAULT_STATE: PendingRequestsState = PendingRequestsState(
viewState = PendingRequestsState.ViewState.Empty,
isPullToRefreshSettingEnabled = false,
isRefreshing = false,
hideBottomSheet = false,
internalHideBottomSheet = false,
isFdroid = false,
)
@@ -1,32 +0,0 @@
package com.x8bit.bitwarden.ui.platform.util
import android.os.Build
import com.x8bit.bitwarden.BuildConfig
import com.x8bit.bitwarden.data.platform.util.deviceData
import com.x8bit.bitwarden.data.platform.util.isFdroid
import com.x8bit.bitwarden.data.platform.util.versionData
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class BuildConfigTest {
@Test
fun `deviceData should be formatted correctly`() {
val deviceBrandModel = "\uD83D\uDCF1 ${Build.BRAND} ${Build.MODEL}"
val osInfo = "\uD83E\uDD16 ${Build.VERSION.RELEASE}@${Build.VERSION.SDK_INT}"
val buildInfo = "\uD83D\uDCE6 dev"
if (isFdroid) {
assertEquals("$deviceBrandModel $osInfo $buildInfo -fdroid", deviceData)
} else {
assertEquals("$deviceBrandModel $osInfo $buildInfo", deviceData)
}
}
@Test
fun `versionData should be formatted correctly`() {
val versionName = BuildConfig.VERSION_NAME
val versionCode = BuildConfig.VERSION_CODE
assertEquals("$versionName ($versionCode)", versionData)
}
}