Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow users to toggle crash logging #72

Merged
merged 6 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
plugins {
alias(libs.plugins.android.application)
// alias(libs.plugins.crashlytics)
alias(libs.plugins.crashlytics)
alias(libs.plugins.hilt)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlinx.kover)
alias(libs.plugins.ksp)
// alias(libs.plugins.google.services)
alias(libs.plugins.google.services)
}

android {
Expand Down
12 changes: 12 additions & 0 deletions app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application tools:ignore="MissingApplicationIcon">
<!-- Disable Crashlytics for debug builds -->
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
</application>

</manifest>
29 changes: 29 additions & 0 deletions app/src/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "867301491091",
"project_id": "bitwarden-authenticator",
"storage_bucket": "bitwarden-authenticator.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:867301491091:android:50b626dba42a361651e866",
"android_client_info": {
"package_name": "com.bitwarden.authenticator"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyDDXnnBuWzuh8rlihiMWRPif_sqkGk3fxw"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package com.bitwarden.authenticator

import android.app.Application
import com.bitwarden.authenticator.data.platform.manager.CrashLogsManager
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject

@HiltAndroidApp
class AuthenticatorApplication : Application()
class AuthenticatorApplication : Application() {
// Inject classes here that must be triggered on startup but are not otherwise consumed by
// other callers.

@Inject
lateinit var crashLogsManager: CrashLogsManager
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ interface SettingsDiskSource {
*/
val hasSeenWelcomeTutorialFlow: Flow<Boolean>

/**
* The current setting for if crash logging is enabled.
*/
var isCrashLoggingEnabled: Boolean?

/**
* The current setting for if crash logging is enabled.
*/
val isCrashLoggingEnabledFlow: Flow<Boolean?>

/**
* Stores the threshold at which users are alerted that an items validity period is nearing
* expiration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ private const val SYSTEM_BIOMETRIC_INTEGRITY_SOURCE_KEY = "$BASE_KEY:biometricIn
private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "$BASE_KEY:accountBiometricIntegrityValid"
private const val ALERT_THRESHOLD_SECONDS_KEY = "$BASE_KEY:alertThresholdSeconds"
private const val FIRST_LAUNCH_KEY = "$BASE_KEY:hasSeenWelcomeTutorial"
private const val CRASH_LOGGING_ENABLED_KEY = "$BASE_KEY:crashLoggingEnabled"

/**
* Primary implementation of [SettingsDiskSource].
Expand All @@ -32,6 +33,9 @@ class SettingsDiskSourceImpl(
private val mutableAlertThresholdSecondsFlow =
bufferedMutableSharedFlow<Int>()

private val mutableIsCrashLoggingEnabledFlow =
bufferedMutableSharedFlow<Boolean?>()

override var appLanguage: AppLanguage?
get() = getString(key = APP_LANGUAGE_KEY)
?.let { storedValue ->
Expand Down Expand Up @@ -81,6 +85,17 @@ class SettingsDiskSourceImpl(
override val hasSeenWelcomeTutorialFlow: Flow<Boolean>
get() = mutableFirstLaunchFlow.onSubscription { emit(hasSeenWelcomeTutorial) }

override var isCrashLoggingEnabled: Boolean?
get() = getBoolean(key = CRASH_LOGGING_ENABLED_KEY)
set(value) {
putBoolean(key = CRASH_LOGGING_ENABLED_KEY, value = value)
mutableIsCrashLoggingEnabledFlow.tryEmit(value)
}

override val isCrashLoggingEnabledFlow: Flow<Boolean?>
get() = mutableIsCrashLoggingEnabledFlow
.onSubscription { emit(getBoolean(CRASH_LOGGING_ENABLED_KEY)) }

override fun storeAlertThresholdSeconds(thresholdSeconds: Int) {
putInt(
ALERT_THRESHOLD_SECONDS_KEY,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bitwarden.authenticator.data.platform.manager

/**
* Implementations of this interface provide a way to enable or disable the collection of crash
* logs, giving control over whether crash logs are generated and stored.
*/
interface CrashLogsManager {
/**
* Gets or sets whether the collection of crash logs is enabled.
*/
var isEnabled: Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.bitwarden.authenticator.data.platform.manager

import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase

/**
* CrashLogsManager implementation for standard flavor builds.
*/
class CrashLogsManagerImpl(
private val settingsRepository: SettingsRepository,
) : CrashLogsManager {

override var isEnabled: Boolean
get() = settingsRepository.isCrashLoggingEnabled
set(value) {
settingsRepository.isCrashLoggingEnabled = value
Firebase.crashlytics.setCrashlyticsCollectionEnabled(value)
}

init {
isEnabled = settingsRepository.isCrashLoggingEnabled
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import android.content.Context
import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSource
import com.bitwarden.authenticator.data.platform.manager.BiometricsEncryptionManager
import com.bitwarden.authenticator.data.platform.manager.BiometricsEncryptionManagerImpl
import com.bitwarden.authenticator.data.platform.manager.CrashLogsManager
import com.bitwarden.authenticator.data.platform.manager.CrashLogsManagerImpl
import com.bitwarden.authenticator.data.platform.manager.DispatcherManager
import com.bitwarden.authenticator.data.platform.manager.DispatcherManagerImpl
import com.bitwarden.authenticator.data.platform.manager.SdkClientManager
import com.bitwarden.authenticator.data.platform.manager.SdkClientManagerImpl
import com.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager
import com.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManagerImpl
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand Down Expand Up @@ -48,4 +51,10 @@ object PlatformManagerModule {
fun provideBiometricsEncryptionManager(
settingsDiskSource: SettingsDiskSource,
): BiometricsEncryptionManager = BiometricsEncryptionManagerImpl(settingsDiskSource)

@Provides
fun provideCrashLogsManager(settingsRepository: SettingsRepository): CrashLogsManager =
CrashLogsManagerImpl(
settingsRepository = settingsRepository,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.bitwarden.authenticator.data.platform.repository
import com.bitwarden.authenticator.data.platform.repository.model.BiometricsKeyResult
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

/**
Expand Down Expand Up @@ -60,4 +61,14 @@ interface SettingsRepository {
* user's vault.
*/
suspend fun setupBiometricsKey(): BiometricsKeyResult

/**
* The current setting for crash logging.
*/
var isCrashLoggingEnabled: Boolean

/**
* Emits updates that track the [isCrashLoggingEnabled] value.
*/
val isCrashLoggingEnabledFlow: Flow<Boolean>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.bitwarden.authenticator.data.platform.repository.model.BiometricsKeyR
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppLanguage
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -90,4 +91,20 @@ class SettingsRepositoryImpl(
override fun clearBiometricsKey() {
authDiskSource.storeUserBiometricUnlockKey(biometricsKey = null)
}

override var isCrashLoggingEnabled: Boolean
get() = settingsDiskSource.isCrashLoggingEnabled ?: true
set(value) {
settingsDiskSource.isCrashLoggingEnabled = value
}

override val isCrashLoggingEnabledFlow: Flow<Boolean>
get() = settingsDiskSource
.isCrashLoggingEnabledFlow
.map { it ?: isCrashLoggingEnabled }
.stateIn(
scope = unconfinedScope,
started = SharingStarted.Eagerly,
initialValue = isCrashLoggingEnabled,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,18 @@ fun SettingsScreen(
)
Spacer(modifier = Modifier.height(16.dp))
AboutSettings(
modifier = Modifier
.padding(horizontal = 16.dp),
state = state,
onSubmitCrashLogsCheckedChange = remember(viewModel) {
{ viewModel.trySendAction(SettingsAction.AboutClick.SubmitCrashLogsClick(it)) }
},
onPrivacyPolicyClick = remember(viewModel) {
{ viewModel.trySendAction(SettingsAction.AboutClick.PrivacyPolicyClick) }
},
onVersionClick = remember(viewModel) {
{ viewModel.trySendAction(SettingsAction.AboutClick.VersionClick) }
}
},
)
Box(
modifier = Modifier
Expand Down Expand Up @@ -452,14 +457,22 @@ private fun HelpSettings(
//region About settings
@Composable
private fun AboutSettings(
modifier: Modifier = Modifier,
state: SettingsState,
onSubmitCrashLogsCheckedChange: (Boolean) -> Unit,
onPrivacyPolicyClick: () -> Unit,
onVersionClick: () -> Unit,
) {
BitwardenListHeaderText(
modifier = Modifier.padding(horizontal = 16.dp),
modifier = modifier,
label = stringResource(id = R.string.about)
)
BitwardenWideSwitch(
modifier = modifier,
label = stringResource(id = R.string.submit_crash_logs),
isChecked = state.isSubmitCrashLogsEnabled,
onCheckedChange = onSubmitCrashLogsCheckedChange,
)
BitwardenExternalLinkRow(
text = stringResource(id = R.string.privacy_policy),
onConfirmClick = onPrivacyPolicyClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class SettingsViewModel @Inject constructor(
clock,
settingsRepository.appLanguage,
settingsRepository.appTheme,
settingsRepository.isUnlockWithBiometricsEnabled
settingsRepository.isUnlockWithBiometricsEnabled,
settingsRepository.isCrashLoggingEnabled
)
) {
override fun handleAction(action: SettingsAction) {
Expand Down Expand Up @@ -192,9 +193,18 @@ class SettingsViewModel @Inject constructor(
SettingsAction.AboutClick.VersionClick -> {
handleVersionClick()
}

is SettingsAction.AboutClick.SubmitCrashLogsClick -> {
handleSubmitCrashLogsClick(action.enabled)
}
}
}

private fun handleSubmitCrashLogsClick(enabled: Boolean) {
mutableStateFlow.update { it.copy(isSubmitCrashLogsEnabled = enabled) }
settingsRepository.isCrashLoggingEnabled = enabled
}

private fun handlePrivacyPolicyClick() {
sendEvent(SettingsEvent.NavigateToPrivacyPolicy)
}
Expand All @@ -211,6 +221,7 @@ class SettingsViewModel @Inject constructor(
appLanguage: AppLanguage,
appTheme: AppTheme,
unlockWithBiometricsEnabled: Boolean,
isSubmitCrashLogsEnabled: Boolean,
): SettingsState {
val currentYear = Year.now(clock)
val copyrightInfo = "ยฉ Bitwarden Inc. 2015-$currentYear".asText()
Expand All @@ -220,11 +231,12 @@ class SettingsViewModel @Inject constructor(
theme = appTheme,
),
isUnlockWithBiometricsEnabled = unlockWithBiometricsEnabled,
isSubmitCrashLogsEnabled = isSubmitCrashLogsEnabled,
dialog = null,
version = R.string.version
.asText()
.concat(": ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText()),
copyrightInfo = copyrightInfo,
dialog = null,
copyrightInfo = copyrightInfo
)
}
}
Expand All @@ -237,6 +249,7 @@ class SettingsViewModel @Inject constructor(
data class SettingsState(
val appearance: Appearance,
val isUnlockWithBiometricsEnabled: Boolean,
val isSubmitCrashLogsEnabled: Boolean,
val dialog: Dialog?,
val version: Text,
val copyrightInfo: Text,
Expand Down Expand Up @@ -306,6 +319,9 @@ sealed class SettingsAction(
) : Dialog()
}

/**
* Indicates the user clicked the Unlock with biometrics button.
*/
sealed class SecurityClick : SettingsAction() {
data class UnlockWithBiometricToggle(val enabled: Boolean) : SecurityClick()
}
Expand Down Expand Up @@ -370,6 +386,11 @@ sealed class SettingsAction(
* Indicates the user clicked version.
*/
data object VersionClick : AboutClick()

/**
* Indicates the user clicked submit crash logs toggle.
*/
data class SubmitCrashLogsClick(val enabled: Boolean) : AboutClick()
}

/**
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,5 @@
<string name="create_verification_code">Create Verification code</string>
<string name="key_is_required">Key is required.</string>
<string name="name_is_required">Name is required.</string>
<string name="submit_crash_logs">Submit crash logs</string>
</resources>
Loading