Skip to content

Commit 8245ad8

Browse files
authored
Handle preference stores corruption by clearing them (#5086)
* Handle preference stores corruption by clearing them: - Use the centralised `PreferenceDataStoreFactory` instead of `preferences by`. - Add `DefaultPreferencesCorruptionHandlerFactory.replaceWithEmpty` to its `create(name)` method so all preference stores are cleared if they're corrupted. * Add detekt rule to make sure we use `PreferenceDataStoreFactory` instead of `by preferencesDataStore` * Remove `@SingleIn` annotations as the annotated class no longer have to be singletons
1 parent 3faaab4 commit 8245ad8

File tree

30 files changed

+198
-138
lines changed

30 files changed

+198
-138
lines changed

app/src/main/kotlin/io/element/android/x/initializer/CrashInitializer.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ package io.element.android.x.initializer
1010
import android.content.Context
1111
import androidx.startup.Initializer
1212
import io.element.android.features.rageshake.impl.crash.VectorUncaughtExceptionHandler
13+
import io.element.android.features.rageshake.impl.di.RageshakeBindings
14+
import io.element.android.libraries.architecture.bindings
1315

1416
class CrashInitializer : Initializer<Unit> {
1517
override fun create(context: Context) {
16-
VectorUncaughtExceptionHandler(context).activate()
18+
VectorUncaughtExceptionHandler(
19+
context.bindings<RageshakeBindings>().preferencesCrashDataStore(),
20+
).activate()
1721
}
1822

1923
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()

features/lockscreen/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dependencies {
2727
implementation(projects.appconfig)
2828
implementation(projects.features.enterprise.api)
2929
implementation(projects.libraries.core)
30+
implementation(projects.libraries.androidutils)
3031
implementation(projects.libraries.architecture)
3132
implementation(projects.libraries.matrix.api)
3233
implementation(projects.libraries.matrixui)

features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,88 +7,83 @@
77

88
package io.element.android.features.lockscreen.impl.storage
99

10-
import android.content.Context
11-
import androidx.datastore.core.DataStore
1210
import androidx.datastore.preferences.core.Preferences
1311
import androidx.datastore.preferences.core.booleanPreferencesKey
1412
import androidx.datastore.preferences.core.edit
1513
import androidx.datastore.preferences.core.intPreferencesKey
1614
import androidx.datastore.preferences.core.stringPreferencesKey
17-
import androidx.datastore.preferences.preferencesDataStore
1815
import com.squareup.anvil.annotations.ContributesBinding
1916
import io.element.android.features.lockscreen.impl.LockScreenConfig
2017
import io.element.android.libraries.di.AppScope
21-
import io.element.android.libraries.di.ApplicationContext
22-
import io.element.android.libraries.di.SingleIn
18+
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
2319
import kotlinx.coroutines.flow.Flow
2420
import kotlinx.coroutines.flow.first
2521
import kotlinx.coroutines.flow.map
2622
import javax.inject.Inject
2723

28-
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "pin_code_store")
29-
30-
@SingleIn(AppScope::class)
3124
@ContributesBinding(AppScope::class)
3225
class PreferencesLockScreenStore @Inject constructor(
33-
@ApplicationContext private val context: Context,
26+
preferenceDataStoreFactory: PreferenceDataStoreFactory,
3427
private val lockScreenConfig: LockScreenConfig,
3528
) : LockScreenStore {
29+
private val dataStore = preferenceDataStoreFactory.create("pin_code_store")
30+
3631
private val pinCodeKey = stringPreferencesKey("encoded_pin_code")
3732
private val remainingAttemptsKey = intPreferencesKey("remaining_pin_code_attempts")
3833
private val biometricUnlockKey = booleanPreferencesKey("biometric_unlock_enabled")
3934

4035
override suspend fun getRemainingPinCodeAttemptsNumber(): Int {
41-
return context.dataStore.data.map { preferences ->
36+
return dataStore.data.map { preferences ->
4237
preferences.getRemainingPinCodeAttemptsNumber()
4338
}.first()
4439
}
4540

4641
override suspend fun onWrongPin() {
47-
context.dataStore.edit { preferences ->
42+
dataStore.edit { preferences ->
4843
val current = preferences.getRemainingPinCodeAttemptsNumber()
4944
val remaining = (current - 1).coerceAtLeast(0)
5045
preferences[remainingAttemptsKey] = remaining
5146
}
5247
}
5348

5449
override suspend fun resetCounter() {
55-
context.dataStore.edit { preferences ->
50+
dataStore.edit { preferences ->
5651
preferences[remainingAttemptsKey] = lockScreenConfig.maxPinCodeAttemptsBeforeLogout
5752
}
5853
}
5954

6055
override suspend fun getEncryptedCode(): String? {
61-
return context.dataStore.data.map { preferences ->
56+
return dataStore.data.map { preferences ->
6257
preferences[pinCodeKey]
6358
}.first()
6459
}
6560

6661
override suspend fun saveEncryptedPinCode(pinCode: String) {
67-
context.dataStore.edit { preferences ->
62+
dataStore.edit { preferences ->
6863
preferences[pinCodeKey] = pinCode
6964
}
7065
}
7166

7267
override suspend fun deleteEncryptedPinCode() {
73-
context.dataStore.edit { preferences ->
68+
dataStore.edit { preferences ->
7469
preferences.remove(pinCodeKey)
7570
}
7671
}
7772

7873
override fun hasPinCode(): Flow<Boolean> {
79-
return context.dataStore.data.map { preferences ->
74+
return dataStore.data.map { preferences ->
8075
preferences[pinCodeKey] != null
8176
}
8277
}
8378

8479
override fun isBiometricUnlockAllowed(): Flow<Boolean> {
85-
return context.dataStore.data.map { preferences ->
80+
return dataStore.data.map { preferences ->
8681
preferences[biometricUnlockKey] ?: false
8782
}
8883
}
8984

9085
override suspend fun setIsBiometricUnlockAllowed(isAllowed: Boolean) {
91-
context.dataStore.edit { preferences ->
86+
dataStore.edit { preferences ->
9287
preferences[biometricUnlockKey] = isAllowed
9388
}
9489
}

features/migration/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ setupAnvil()
2020
dependencies {
2121
implementation(projects.features.migration.api)
2222
implementation(projects.libraries.architecture)
23+
implementation(projects.libraries.androidutils)
2324
implementation(projects.libraries.preferences.impl)
2425
implementation(libs.androidx.datastore.preferences)
2526
implementation(projects.features.rageshake.api)

features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/DefaultMigrationStore.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,22 @@
77

88
package io.element.android.features.migration.impl
99

10-
import android.content.Context
11-
import androidx.datastore.core.DataStore
12-
import androidx.datastore.preferences.core.Preferences
1310
import androidx.datastore.preferences.core.edit
1411
import androidx.datastore.preferences.core.intPreferencesKey
15-
import androidx.datastore.preferences.preferencesDataStore
1612
import com.squareup.anvil.annotations.ContributesBinding
1713
import io.element.android.libraries.di.AppScope
18-
import io.element.android.libraries.di.ApplicationContext
14+
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
1915
import kotlinx.coroutines.flow.Flow
2016
import kotlinx.coroutines.flow.map
2117
import javax.inject.Inject
2218

23-
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_migration")
2419
private val applicationMigrationVersion = intPreferencesKey("applicationMigrationVersion")
2520

2621
@ContributesBinding(AppScope::class)
2722
class DefaultMigrationStore @Inject constructor(
28-
@ApplicationContext context: Context,
23+
preferenceDataStoreFactory: PreferenceDataStoreFactory,
2924
) : MigrationStore {
30-
private val store = context.dataStore
25+
private val store = preferenceDataStoreFactory.create("elementx_migration")
3126

3227
override suspend fun setApplicationMigrationVersion(version: Int) {
3328
store.edit { prefs ->

features/rageshake/impl/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dependencies {
3333
implementation(projects.libraries.network)
3434
implementation(projects.libraries.architecture)
3535
implementation(projects.libraries.designsystem)
36+
implementation(projects.libraries.preferences.api)
3637
implementation(projects.libraries.uiStrings)
3738
implementation(projects.libraries.sessionStorage.api)
3839
implementation(projects.libraries.matrix.api)
@@ -56,6 +57,7 @@ dependencies {
5657
testImplementation(projects.libraries.sessionStorage.implMemory)
5758
testImplementation(projects.libraries.sessionStorage.test)
5859
testImplementation(projects.features.rageshake.test)
60+
testImplementation(projects.libraries.preferences.test)
5961
testImplementation(projects.tests.testutils)
6062
testImplementation(projects.services.toolbox.test)
6163
testImplementation(libs.network.mockwebserver)

features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/PreferencesCrashDataStore.kt

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,26 @@
77

88
package io.element.android.features.rageshake.impl.crash
99

10-
import android.content.Context
11-
import androidx.datastore.core.DataStore
12-
import androidx.datastore.preferences.core.Preferences
1310
import androidx.datastore.preferences.core.booleanPreferencesKey
1411
import androidx.datastore.preferences.core.edit
1512
import androidx.datastore.preferences.core.stringPreferencesKey
16-
import androidx.datastore.preferences.preferencesDataStore
1713
import com.squareup.anvil.annotations.ContributesBinding
1814
import io.element.android.libraries.core.bool.orFalse
1915
import io.element.android.libraries.di.AppScope
20-
import io.element.android.libraries.di.ApplicationContext
16+
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
2117
import kotlinx.coroutines.flow.Flow
2218
import kotlinx.coroutines.flow.map
2319
import kotlinx.coroutines.runBlocking
2420
import javax.inject.Inject
2521

26-
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_crash")
27-
2822
private val appHasCrashedKey = booleanPreferencesKey("appHasCrashed")
2923
private val crashDataKey = stringPreferencesKey("crashData")
3024

3125
@ContributesBinding(AppScope::class)
3226
class PreferencesCrashDataStore @Inject constructor(
33-
@ApplicationContext context: Context
27+
preferenceDataStoreFactory: PreferenceDataStoreFactory,
3428
) : CrashDataStore {
35-
private val store = context.dataStore
29+
private val store = preferenceDataStoreFactory.create("elementx_crash")
3630

3731
override fun setCrashData(crashData: String) {
3832
// Must block

features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandler.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@
77

88
package io.element.android.features.rageshake.impl.crash
99

10-
import android.content.Context
1110
import android.os.Build
1211
import io.element.android.libraries.core.data.tryOrNull
1312
import timber.log.Timber
1413
import java.io.PrintWriter
1514
import java.io.StringWriter
1615

1716
class VectorUncaughtExceptionHandler(
18-
context: Context
17+
private val preferencesCrashDataStore: PreferencesCrashDataStore,
1918
) : Thread.UncaughtExceptionHandler {
20-
private val crashDataStore = PreferencesCrashDataStore(context)
2119
private var previousHandler: Thread.UncaughtExceptionHandler? = null
2220

2321
/**
@@ -65,7 +63,7 @@ class VectorUncaughtExceptionHandler(
6563
append(sw.buffer.toString())
6664
}
6765
Timber.e("FATAL EXCEPTION $bugDescription")
68-
crashDataStore.setCrashData(bugDescription)
66+
preferencesCrashDataStore.setCrashData(bugDescription)
6967
// Show the classical system popup
7068
previousHandler?.uncaughtException(thread, throwable)
7169
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.rageshake.impl.di
9+
10+
import com.squareup.anvil.annotations.ContributesTo
11+
import io.element.android.features.rageshake.impl.crash.PreferencesCrashDataStore
12+
import io.element.android.libraries.di.AppScope
13+
14+
@ContributesTo(AppScope::class)
15+
interface RageshakeBindings {
16+
fun preferencesCrashDataStore(): PreferencesCrashDataStore
17+
}

features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/PreferencesRageshakeDataStore.kt

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,25 @@
77

88
package io.element.android.features.rageshake.impl.rageshake
99

10-
import android.content.Context
11-
import androidx.datastore.core.DataStore
12-
import androidx.datastore.preferences.core.Preferences
1310
import androidx.datastore.preferences.core.booleanPreferencesKey
1411
import androidx.datastore.preferences.core.edit
1512
import androidx.datastore.preferences.core.floatPreferencesKey
16-
import androidx.datastore.preferences.preferencesDataStore
1713
import com.squareup.anvil.annotations.ContributesBinding
1814
import io.element.android.libraries.core.bool.orFalse
1915
import io.element.android.libraries.di.AppScope
20-
import io.element.android.libraries.di.ApplicationContext
16+
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
2117
import kotlinx.coroutines.flow.Flow
2218
import kotlinx.coroutines.flow.map
2319
import javax.inject.Inject
2420

25-
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_rageshake")
26-
2721
private val enabledKey = booleanPreferencesKey("enabled")
2822
private val sensitivityKey = floatPreferencesKey("sensitivity")
2923

3024
@ContributesBinding(AppScope::class)
3125
class PreferencesRageshakeDataStore @Inject constructor(
32-
@ApplicationContext context: Context
26+
preferenceDataStoreFactory: PreferenceDataStoreFactory,
3327
) : RageshakeDataStore {
34-
private val store = context.dataStore
28+
private val store = preferenceDataStoreFactory.create("elementx_rageshake")
3529

3630
override fun isEnabled(): Flow<Boolean> {
3731
return store.data.map { prefs ->

0 commit comments

Comments
 (0)