Skip to content

Commit aef1d6f

Browse files
committed
UPD: Upgrade to the new sqlcipher-android library
1 parent 28a5034 commit aef1d6f

3 files changed

Lines changed: 96 additions & 64 deletions

File tree

buildSrc/src/main/kotlin/LicenseeConfig.kt

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2025 Google LLC
2+
* Copyright 2023-2026 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -80,13 +80,12 @@ private fun Project.configureLicensee() {
8080
}
8181

8282
// SQLCipher
83-
allowDependency("net.zetetic", "android-database-sqlcipher", "4.5.0") {
83+
allowDependency("net.zetetic", "android-database-sqlcipher", "4.5.4") {
8484
because("Custom license, essentially BSD-3. https://www.zetetic.net/sqlcipher/license/")
8585
}
86-
allowDependency("net.zetetic", "android-database-sqlcipher", "4.5.4") {
86+
allowDependency("net.zetetic", "sqlcipher-android", "4.12.0") {
8787
because("Custom license, essentially BSD-3. https://www.zetetic.net/sqlcipher/license/")
8888
}
89-
9089
// Jakarta XML Binding API
9190
allowDependency("jakarta.xml.bind", "jakarta.xml.bind-api", "4.0.1") {
9291
because("BSD 3-clause.")
@@ -145,20 +144,54 @@ private fun Project.configureLicensee() {
145144

146145
// More utility classes
147146
// https://developers.google.com/android/reference/com/google/android/gms/common/package-summary
148-
allowDependency("com.google.android.gms", "play-services-basement", "17.4.0") { because("") }
149-
allowDependency("com.google.android.gms", "play-services-basement", "18.0.0") { because("") }
150-
allowDependency("com.google.android.gms", "play-services-basement", "18.1.0") { because("") }
147+
allowDependency(
148+
"com.google.android.gms",
149+
"play-services-basement",
150+
"17.4.0",
151+
) {
152+
because("")
153+
}
154+
allowDependency(
155+
"com.google.android.gms",
156+
"play-services-basement",
157+
"18.0.0",
158+
) {
159+
because("")
160+
}
161+
allowDependency(
162+
"com.google.android.gms",
163+
"play-services-basement",
164+
"18.1.0",
165+
) {
166+
because("")
167+
}
151168

152169
// https://developers.google.com/android/reference/com/google/android/gms/common/package-summary
153-
allowDependency("com.google.android.gms", "play-services-clearcut", "17.0.0") { because("") }
170+
allowDependency(
171+
"com.google.android.gms",
172+
"play-services-clearcut",
173+
"17.0.0",
174+
) {
175+
because("")
176+
}
154177

155178
// ML Kit barcode scanning https://developers.google.com/ml-kit/vision/barcode-scanning/android
156-
allowDependency("com.google.android.gms", "play-services-mlkit-barcode-scanning", "16.1.4") {
179+
allowDependency(
180+
"com.google.android.gms",
181+
"play-services-mlkit-barcode-scanning",
182+
"16.1.4",
183+
) {
157184
because("")
158185
}
159186

160187
// Play Services Phenotype
161-
allowDependency("com.google.android.gms", "play-services-phenotype", "17.0.0") { because("") }
188+
allowDependency(
189+
"com.google.android.gms",
190+
"play-services-phenotype",
191+
"17.0.0",
192+
) {
193+
because("")
194+
}
162195

163196
// Tasks API Android https://developers.google.com/android/guides/tasks
164197
allowDependency("com.google.android.gms", "play-services-tasks", "17.2.0") { because("") }

engine/src/main/java/com/google/android/fhir/db/impl/SQLCipherSupportHelper.kt

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2025 Google LLC
2+
* Copyright 2026 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,58 +27,32 @@ import com.google.android.fhir.db.impl.DatabaseImpl.Companion.UNENCRYPTED_DATABA
2727
import java.time.Duration
2828
import kotlinx.coroutines.delay
2929
import kotlinx.coroutines.runBlocking
30-
import net.sqlcipher.database.SQLiteDatabase
31-
import net.sqlcipher.database.SQLiteDatabaseHook
32-
import net.sqlcipher.database.SQLiteOpenHelper
30+
import net.zetetic.database.sqlcipher.SQLiteDatabaseHook
31+
import net.zetetic.database.sqlcipher.SupportOpenHelperFactory
3332
import timber.log.Timber
3433

35-
/** A [SupportSQLiteOpenHelper] which initializes a [SQLiteDatabase] with a passphrase. */
34+
/** A [SupportSQLiteOpenHelper] which initializes SQLCipher with a passphrase. */
3635
internal class SQLCipherSupportHelper(
3736
private val configuration: SupportSQLiteOpenHelper.Configuration,
38-
hook: SQLiteDatabaseHook? = null,
37+
private val hook: SQLiteDatabaseHook? = null,
3938
private val databaseErrorStrategy: DatabaseErrorStrategy,
4039
private val passphraseFetcher: () -> ByteArray,
4140
) : SupportSQLiteOpenHelper {
4241

4342
init {
44-
SQLiteDatabase.loadLibs(configuration.context)
43+
System.loadLibrary("sqlcipher")
4544
}
4645

47-
private val standardHelper =
48-
object :
49-
SQLiteOpenHelper(
50-
configuration.context,
51-
configuration.name,
52-
/* factory= */ null,
53-
configuration.callback.version,
54-
hook,
55-
) {
56-
override fun onCreate(db: SQLiteDatabase) {
57-
configuration.callback.onCreate(db)
58-
}
46+
@Volatile private var delegate: SupportSQLiteOpenHelper? = null
5947

60-
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
61-
configuration.callback.onUpgrade(db, oldVersion, newVersion)
62-
}
48+
@Volatile private var walEnabled: Boolean = false
6349

64-
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
65-
configuration.callback.onDowngrade(db, oldVersion, newVersion)
66-
}
67-
68-
override fun onOpen(db: SQLiteDatabase) {
69-
configuration.callback.onOpen(db)
70-
}
71-
72-
override fun onConfigure(db: SQLiteDatabase) {
73-
configuration.callback.onConfigure(db)
74-
}
75-
}
76-
77-
override val databaseName
78-
get() = standardHelper.databaseName
50+
override val databaseName: String?
51+
get() = configuration.name
7952

8053
override fun setWriteAheadLoggingEnabled(enabled: Boolean) {
81-
standardHelper.setWriteAheadLoggingEnabled(enabled)
54+
walEnabled = enabled
55+
delegate?.setWriteAheadLoggingEnabled(enabled)
8256
}
8357

8458
override val writableDatabase: SupportSQLiteDatabase
@@ -87,20 +61,52 @@ internal class SQLCipherSupportHelper(
8761
"Unexpected unencrypted database, $UNENCRYPTED_DATABASE_NAME, already exists. " +
8862
"Check if you have accidentally disabled database encryption across releases."
8963
}
90-
val key = runBlocking { getPassphraseWithRetry() }
64+
65+
val helper = delegate ?: createDelegate().also { delegate = it }
66+
9167
return try {
92-
standardHelper.getWritableDatabase(key)
68+
helper.writableDatabase
9369
} catch (ex: SQLiteException) {
9470
if (databaseErrorStrategy == DatabaseErrorStrategy.RECREATE_AT_OPEN) {
9571
Timber.w("Fail to open database. Recreating database.")
9672
configuration.context.getDatabasePath(databaseName).delete()
97-
standardHelper.getWritableDatabase(key)
73+
74+
// Reset and retry with a fresh helper instance
75+
delegate?.close()
76+
delegate = null
77+
createDelegate().also { delegate = it }.writableDatabase
9878
} else {
9979
throw ex
10080
}
10181
}
10282
}
10383

84+
override val readableDatabase: SupportSQLiteDatabase
85+
get() = writableDatabase
86+
87+
override fun close() {
88+
delegate?.close()
89+
delegate = null
90+
}
91+
92+
/** Creates a SQLCipher-aware SupportSQLiteOpenHelper using SupportOpenHelperFactory. */
93+
private fun createDelegate(): SupportSQLiteOpenHelper {
94+
val passphrase = runBlocking { getPassphraseWithRetry() }
95+
96+
val factory =
97+
if (hook == null) {
98+
SupportOpenHelperFactory(passphrase)
99+
} else {
100+
SupportOpenHelperFactory(
101+
passphrase,
102+
hook,
103+
/* enableWriteAheadLogging = */ walEnabled,
104+
)
105+
}
106+
107+
return factory.create(configuration)
108+
}
109+
104110
private suspend fun getPassphraseWithRetry(): ByteArray {
105111
var lastException: DatabaseEncryptionException? = null
106112
for (retryAttempt in 1..MAX_RETRY_ATTEMPTS) {
@@ -120,13 +126,6 @@ internal class SQLCipherSupportHelper(
120126
throw lastException ?: DatabaseEncryptionException(Exception(), UNKNOWN)
121127
}
122128

123-
override val readableDatabase
124-
get() = writableDatabase
125-
126-
override fun close() {
127-
standardHelper.close()
128-
}
129-
130129
private companion object {
131130
const val MAX_RETRY_ATTEMPTS = 3
132131

gradle/libs.versions.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ retrofit = "2.9.0"
7878
robolectric = "4.10.3"
7979
ruler-gradle-plugin = "1.4.0"
8080
spotless-plugin-gradle = "6.22.0"
81-
sqlcipher = "4.5.4"
81+
sqlcipher-android = "4.12.0"
8282
timber = "5.0.1"
8383
truth = "1.1.5"
8484
uiautomator = "2.3.0"
@@ -89,7 +89,7 @@ zxing = "3.4.1"
8989
lifecycle-runtime-testing = "2.10.0"
9090

9191
[libraries]
92-
accompanist-themeadapter-material3 = { module = "com.google.accompanist:accompanist-themeadapter-material3", version.ref = "accompanist-themeadapter-material3"}
92+
accompanist-themeadapter-material3 = { module = "com.google.accompanist:accompanist-themeadapter-material3", version.ref = "accompanist-themeadapter-material3" }
9393
android-fhir-common = { module = "com.google.android.fhir:common", version.ref = "android-fhir-common" }
9494
android-fhir-engine = { module = "com.google.android.fhir:engine", version.ref = "android-fhir-engine" }
9595
android-fhir-knowledge = { module = "com.google.android.fhir:knowledge", version.ref = "android-fhir-knowledge" }
@@ -101,7 +101,7 @@ androidx-benchmark-junit4 = { module = "androidx.benchmark:benchmark-junit4", ve
101101
androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "androidx-benchmark-macro" }
102102
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidx-compose-bom" }
103103
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidx-compose-material3" }
104-
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "androidx-compose-ui"}
104+
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "androidx-compose-ui" }
105105
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "androidx-compose-ui" }
106106
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "androidx-compose-ui" }
107107
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "androidx-compose-ui" }
@@ -160,7 +160,7 @@ json-assert = { module = "org.skyscreamer:jsonassert", version.ref = "json-asser
160160
json-tools-patch = { module = "com.github.java-json-tools:json-patch", version.ref = "json-tools-patch" }
161161
junit = { module = "junit:junit", version.ref = "junit" }
162162
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
163-
kotest-assertions-core = {module = "io.kotest:kotest-assertions-core", version.ref="kotest"}
163+
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
164164
kotlin-fhir = { module = "com.google.fhir:fhir-model", version.ref = "kotlin-fhir" }
165165
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
166166
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
@@ -196,7 +196,7 @@ retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit
196196
retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
197197
ruler-gradle-plugin = { module = "com.spotify.ruler:ruler-gradle-plugin", version.ref = "ruler-gradle-plugin" }
198198
spotless-plugin-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless-plugin-gradle" }
199-
sqlcipher = { module = "net.zetetic:android-database-sqlcipher", version.ref = "sqlcipher" }
199+
sqlcipher = { module = "net.zetetic:sqlcipher-android", version.ref = "sqlcipher-android" }
200200
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
201201
truth = { module = "com.google.truth:truth", version.ref = "truth" }
202202
woodstox = { module = "com.fasterxml.woodstox:woodstox-core", version.ref = "woodstox" }
@@ -224,4 +224,4 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose" }
224224
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
225225
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization" }
226226
kotlin-serialization-build-src = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
227-
ksp = { id = "com.google.devtools.ksp" }
227+
ksp = { id = "com.google.devtools.ksp" }

0 commit comments

Comments
 (0)