mirror of
https://github.com/bitwarden/android.git
synced 2025-03-03 04:55:54 +03:00
WIP
This commit is contained in:
parent
3fd1e86a03
commit
8cbda5e5a0
8 changed files with 145 additions and 5 deletions
app/src
main/java/com/x8bit/bitwarden
standard/java/com/x8bit/bitwarden/data/platform/manager
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
|
@ -11,6 +12,7 @@ import androidx.activity.viewModels
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
|
@ -25,6 +27,8 @@ import com.x8bit.bitwarden.ui.platform.composition.LocalManagerProvider
|
|||
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.manager.DebugMenuLaunchManager
|
||||
import com.x8bit.bitwarden.ui.platform.feature.debugmenu.navigateToDebugMenuScreen
|
||||
import com.x8bit.bitwarden.ui.platform.feature.rootnav.RootNavScreen
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManagerImpl
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
@ -76,6 +80,8 @@ class MainActivity : AppCompatActivity() {
|
|||
setContent {
|
||||
val state by mainViewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
val navController = rememberNavController()
|
||||
val activity = LocalContext.current as Activity
|
||||
val intentManager: IntentManager = IntentManagerImpl(activity)
|
||||
EventsEffect(viewModel = mainViewModel) { event ->
|
||||
when (event) {
|
||||
is MainEvent.CompleteAccessibilityAutofill -> {
|
||||
|
@ -94,10 +100,15 @@ class MainActivity : AppCompatActivity() {
|
|||
)
|
||||
.show()
|
||||
}
|
||||
|
||||
is MainEvent.ShareText -> intentManager.shareText(event.logText)
|
||||
}
|
||||
}
|
||||
updateScreenCapture(isScreenCaptureAllowed = state.isScreenCaptureAllowed)
|
||||
LocalManagerProvider {
|
||||
LocalManagerProvider(
|
||||
activity = activity,
|
||||
intentManager = intentManager,
|
||||
) {
|
||||
BitwardenTheme(theme = state.theme) {
|
||||
RootNavScreen(
|
||||
onSplashScreenRemoved = { shouldShowSplashScreen = false },
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
|||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.InMemoryLogManager
|
||||
import com.x8bit.bitwarden.data.platform.util.isAddTotpLoginItemFromAuthenticator
|
||||
import com.x8bit.bitwarden.data.vault.manager.model.VaultStateEvent
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
|
@ -72,6 +73,7 @@ class MainViewModel @Inject constructor(
|
|||
private val environmentRepository: EnvironmentRepository,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val clock: Clock,
|
||||
inMemoryLogManager: InMemoryLogManager,
|
||||
) : BaseViewModel<MainState, MainEvent, MainAction>(
|
||||
initialState = MainState(
|
||||
theme = settingsRepository.appTheme,
|
||||
|
@ -161,6 +163,10 @@ class MainViewModel @Inject constructor(
|
|||
settingsRepository.storeUserHasLoggedInValue(it.userId)
|
||||
}
|
||||
}
|
||||
|
||||
inMemoryLogManager.publishedLogsFlow.onEach {
|
||||
sendAction(MainAction.Internal.ShareLogText(it))
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun handleAction(action: MainAction) {
|
||||
|
@ -180,9 +186,14 @@ class MainViewModel @Inject constructor(
|
|||
is MainAction.ReceiveFirstIntent -> handleFirstIntentReceived(action)
|
||||
is MainAction.ReceiveNewIntent -> handleNewIntentReceived(action)
|
||||
MainAction.OpenDebugMenu -> handleOpenDebugMenu()
|
||||
is MainAction.Internal.ShareLogText -> handleShareLogText(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleShareLogText(action: MainAction.Internal.ShareLogText) {
|
||||
sendEvent(MainEvent.ShareText(action.logText))
|
||||
}
|
||||
|
||||
private fun handleOpenDebugMenu() {
|
||||
sendEvent(MainEvent.NavigateToDebugMenu)
|
||||
}
|
||||
|
@ -485,6 +496,8 @@ sealed class MainAction {
|
|||
* Indicates a relevant change in the current vault lock state.
|
||||
*/
|
||||
data object VaultUnlockStateChange : Internal()
|
||||
|
||||
data class ShareLogText(val logText: String) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,4 +531,7 @@ sealed class MainEvent {
|
|||
* Show a toast with the given [message].
|
||||
*/
|
||||
data class ShowToast(val message: Text) : MainEvent()
|
||||
data class ShareText(val logText: String) : MainEvent() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import okio.ByteString.Companion.decodeBase64
|
|||
import java.net.UnknownHostException
|
||||
import java.nio.charset.Charset
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.CertificateException
|
||||
import java.util.Base64
|
||||
import javax.net.ssl.SSLHandshakeException
|
||||
|
||||
|
@ -49,6 +50,19 @@ fun Throwable?.isNoConnectionError(): Boolean {
|
|||
*/
|
||||
fun Throwable?.isSslHandShakeError(): Boolean {
|
||||
return this is SSLHandshakeException ||
|
||||
this is CertificateException ||
|
||||
this is CertPathValidatorException ||
|
||||
this?.cause?.isSslHandShakeError() ?: false
|
||||
}
|
||||
|
||||
fun Throwable?.getCertPathValidatorExceptionOrNull(): CertPathValidatorException? {
|
||||
return this?.cause?.getCertPathValidatorExceptionOrNull()
|
||||
?: when (this) {
|
||||
is CertPathValidatorException -> this
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun CertPathValidatorException?.getCertificateChainListOrEmpty(): List<String> {
|
||||
return this?.certPath?.certificates?.map { it.toString() } ?: emptyList()
|
||||
}
|
||||
|
|
|
@ -60,6 +60,8 @@ import com.x8bit.bitwarden.data.platform.repository.DebugMenuRepository
|
|||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.ServerConfigRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.InMemoryLogManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.InMemoryLogManagerImpl
|
||||
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import dagger.Module
|
||||
|
@ -247,9 +249,11 @@ object PlatformManagerModule {
|
|||
fun provideLogsManager(
|
||||
legacyAppCenterMigrator: LegacyAppCenterMigrator,
|
||||
settingsRepository: SettingsRepository,
|
||||
inMemoryLogManager: InMemoryLogManager,
|
||||
): LogsManager = LogsManagerImpl(
|
||||
settingsRepository = settingsRepository,
|
||||
legacyAppCenterMigrator = legacyAppCenterMigrator,
|
||||
inMemoryLogManager = inMemoryLogManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
@ -310,4 +314,8 @@ object PlatformManagerModule {
|
|||
authDiskSource = authDiskSource,
|
||||
settingsDiskSource = settingsDiskSource,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideInMemoryLogManager(): InMemoryLogManager = InMemoryLogManagerImpl()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package com.x8bit.bitwarden.data.platform.repository.model
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.getCertPathValidatorExceptionOrNull
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.getCertificateChainListOrEmpty
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
|
||||
data class LoggableResult(
|
||||
val message: String?,
|
||||
val throwable: Throwable?,
|
||||
) {
|
||||
val hasNothingToCapture: Boolean = message.isNullOrEmpty() && throwable == null
|
||||
}
|
||||
|
||||
interface InMemoryLogManager {
|
||||
val publishedLogsFlow: Flow<String>
|
||||
fun clearLogs()
|
||||
fun registerLoggableResult(loggableResult: LoggableResult)
|
||||
fun publishLogs()
|
||||
}
|
||||
|
||||
class InMemoryLogManagerImpl: InMemoryLogManager {
|
||||
private val loggableResults = MemoryLimitedBuffer<LoggableResult>(maxSize = 1)
|
||||
private val publishedLogsChannel: Channel<String> = Channel(Channel.BUFFERED)
|
||||
private val publishedLogsSendChannel: SendChannel<String> = publishedLogsChannel
|
||||
override val publishedLogsFlow: Flow<String> = publishedLogsChannel.consumeAsFlow()
|
||||
override fun clearLogs() {
|
||||
loggableResults.clear()
|
||||
}
|
||||
|
||||
override fun registerLoggableResult(loggableResult: LoggableResult) {
|
||||
if (loggableResult.hasNothingToCapture) return
|
||||
loggableResults.add(loggableResult)
|
||||
}
|
||||
|
||||
override fun publishLogs() {
|
||||
if (loggableResults.getAll().isEmpty()) return
|
||||
val publishableLogs = loggableResults.getAll().joinToString(
|
||||
separator = "\n"
|
||||
) { result ->
|
||||
val message = "Message: ${result.message}\n".takeIf { result.message != null }.orEmpty()
|
||||
val stacktraceMessage = "Stacktrace: ${result.throwable?.stackTraceToString()}\n"
|
||||
.takeIf { result.throwable != null }.orEmpty()
|
||||
message + stacktraceMessage + result.throwable.getCertPathValidatorExceptionOrNull()
|
||||
.getCertificateChainListOrEmpty()
|
||||
}
|
||||
clearLogs()
|
||||
publishedLogsSendChannel.trySend(publishableLogs)
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryLimitedBuffer<T>(private val maxSize: Int) {
|
||||
|
||||
private val buffer = ArrayDeque<T>(maxSize)
|
||||
|
||||
fun add(item: T) {
|
||||
if (buffer.size >= maxSize) {
|
||||
buffer.removeFirst() // Remove oldest item if buffer is full
|
||||
}
|
||||
buffer.addLast(item) // Add new item to the end
|
||||
}
|
||||
|
||||
fun getAll(): List<T> = buffer.toList()
|
||||
|
||||
fun clear() {
|
||||
buffer.clear()
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.InMemoryLogManager
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
|
@ -39,6 +40,7 @@ class LoginViewModel @Inject constructor(
|
|||
environmentRepository: EnvironmentRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
private val inMemoryLogManager: InMemoryLogManager,
|
||||
) : BaseViewModel<LoginState, LoginEvent, LoginAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
|
@ -207,6 +209,7 @@ class LoginViewModel @Inject constructor(
|
|||
|
||||
private fun handleErrorDialogDismiss() {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
inMemoryLogManager.publishLogs()
|
||||
}
|
||||
|
||||
private fun handleCaptchaTokenReceived(tokenResult: CaptchaCallbackTokenResult) {
|
||||
|
|
|
@ -31,19 +31,19 @@ import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManagerImp
|
|||
*/
|
||||
@Composable
|
||||
fun LocalManagerProvider(
|
||||
activity: Activity = LocalContext.current as Activity,
|
||||
intentManager: IntentManager = IntentManagerImpl(activity),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val activity = LocalContext.current as Activity
|
||||
val fido2IntentManager: IntentManager = IntentManagerImpl(activity)
|
||||
val fido2CompletionManager =
|
||||
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
|
||||
Fido2CompletionManagerUnsupportedApiImpl
|
||||
} else {
|
||||
Fido2CompletionManagerImpl(activity, fido2IntentManager)
|
||||
Fido2CompletionManagerImpl(activity, intentManager)
|
||||
}
|
||||
CompositionLocalProvider(
|
||||
LocalPermissionsManager provides PermissionsManagerImpl(activity),
|
||||
LocalIntentManager provides fido2IntentManager,
|
||||
LocalIntentManager provides intentManager,
|
||||
LocalExitManager provides ExitManagerImpl(activity),
|
||||
LocalBiometricsManager provides BiometricsManagerImpl(activity),
|
||||
LocalNfcManager provides NfcManagerImpl(activity),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import android.util.Log
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.x8bit.bitwarden.BuildConfig
|
||||
|
@ -7,6 +8,8 @@ import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
|||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacyAppCenterMigrator
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.InMemoryLogManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.LoggableResult
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
@ -16,6 +19,7 @@ import timber.log.Timber
|
|||
class LogsManagerImpl(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
legacyAppCenterMigrator: LegacyAppCenterMigrator,
|
||||
private val inMemoryLogManager: InMemoryLogManager,
|
||||
) : LogsManager {
|
||||
|
||||
private val nonfatalErrorTree: NonfatalErrorTree = NonfatalErrorTree()
|
||||
|
@ -52,6 +56,7 @@ class LogsManagerImpl(
|
|||
init {
|
||||
legacyAppCenterMigrator.migrateIfNecessary()
|
||||
isEnabled = settingsRepository.isCrashLoggingEnabled
|
||||
Timber.plant(LocalLoggerTree())
|
||||
}
|
||||
|
||||
private inner class NonfatalErrorTree : Timber.Tree() {
|
||||
|
@ -59,6 +64,19 @@ class LogsManagerImpl(
|
|||
t?.let { trackNonFatalException(BitwardenNonfatalException(message, it)) }
|
||||
}
|
||||
}
|
||||
|
||||
private inner class LocalLoggerTree : Timber.Tree() {
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
if (priority == Log.WARN || priority == Log.ERROR) {
|
||||
inMemoryLogManager.registerLoggableResult(
|
||||
LoggableResult(
|
||||
message = message,
|
||||
throwable = t,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BitwardenNonfatalException(
|
||||
|
|
Loading…
Add table
Reference in a new issue