Skip to content

Commit 260d9c0

Browse files
mikescamellaitorvsCDRussell
authored
Target Android 15 (SDK 35) (#5204)
Task/Issue URL: https://app.asana.com/0/1207908166761516/1206705314358009/f ### Description Targets Android SDK 35 and makes the required changes to be compatible. See the Asana task for full details. A key part was adding native library alignment checks and updated dependencies to support 16KB page size alignment. End to End test run: https://github.com/duckduckgo/Android/actions/runs/16137443827/job/45536745475 Key changes include: - Updated target SDK to v35 - Added script to verify ELF alignment in native libraries - Updated SQLCipher to use newer version with proper alignment - Added alignment flags to CMake configurations - Removed deprecated WebSQL database settings - Updated Conscrypt, NetGuard, and sync-crypto dependencies ### Steps to test this PR - [ ] Run the app and verify no crashes related to native libraries - [ ] Test autofill functionality with updated SQLCipher - [ ] Verify VPN and network protection features work correctly - [ ] Test web browsing functionality - [ ] Check it runs on different Android versions to ensure compatibility (SDK 26-35) - [ ] Test AppTp functionality - [ ] Test sync funcationality ### UI changes N/A --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1209856558112240 --------- Co-authored-by: Aitor Viana <[email protected]> Co-authored-by: Craig Russell <[email protected]>
1 parent cee28b5 commit 260d9c0

File tree

20 files changed

+260
-80
lines changed

20 files changed

+260
-80
lines changed

.github/workflows/ci.yml

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,24 +149,55 @@ jobs:
149149
- name: Build
150150
run: ./gradlew androidTestsBuild
151151

152+
- name: Find test APK path
153+
id: find-apk
154+
run: |
155+
TEST_APK_PATH=$(find . -path '*/build/outputs/apk/play/debug/*.apk' -type f -print -quit)
156+
echo "Found test APK at: $TEST_APK_PATH"
157+
echo "apk_path=$TEST_APK_PATH" >> "$GITHUB_OUTPUT"
158+
159+
- name: Make script executable
160+
run: chmod +x scripts/check_elf_alignment.sh
161+
162+
- name: Check native libraries alignment
163+
id: check-alignment
164+
continue-on-error: true
165+
run: ./scripts/check_elf_alignment.sh ${{ steps.find-apk.outputs.apk_path }}
166+
167+
- name: Handle native alignment failure
168+
if: steps.check-alignment.outcome == 'failure'
169+
run: |
170+
echo "::error::Native library alignment check failed!"
171+
echo "::error::Please check the native libraries in your APK for correct page size alignment."
172+
exit 1
173+
152174
- name: Run Android Tests
175+
if: steps.check-alignment.outcome == 'success'
153176
run: ./gradlew runFlankAndroidTests
154177

155178
- name: Bundle the Android CI tests report
156-
if: always()
179+
if: |
180+
always() &&
181+
steps.check-alignment.outcome == 'success'
157182
run: find . -type d -name 'fladleResults' | zip -@ -r android-tests-report.zip
158183

159184
- name: Generate json file with failures
160-
if: ${{ failure() }}
185+
if: |
186+
failure() &&
187+
steps.check-alignment.outcome == 'success'
161188
run: cat build/fladle/fladleResults/HtmlErrorReport.html | cut -d\` -f2 >> results.json
162189

163190
- name: Print failure report
164-
if: ${{ failure() }}
191+
if: |
192+
failure() &&
193+
steps.check-alignment.outcome == 'success'
165194
run: |
166195
jq -r '.[] | .label as $id | .items[] | "Test:", $id, "Failure:", .label, "URL:", .url, "\n"' results.json
167196
168197
- name: Upload the Android CI tests report
169-
if: always()
198+
if: |
199+
always() &&
200+
steps.check-alignment.outcome == 'success'
170201
uses: actions/upload-artifact@v4
171202
with:
172203
name: android-tests-report

anrs/anrs-impl/CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,10 @@ target_link_libraries(
2626

2727
# Links the target library to the log library
2828
# included in the NDK.
29-
${log-lib} )
29+
${log-lib} )
30+
31+
target_link_options(
32+
crash-ndk
33+
PRIVATE
34+
"-Wl,-z,common-page-size=16384"
35+
"-Wl,-z,max-page-size=16384")

anrs/anrs-impl/src/main/java/com/duckduckgo/app/anr/ndk/NativeCrashInit.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class NativeCrashInit @Inject constructor(
9595
}
9696
}
9797

98-
override fun failure(t: Throwable?) {
98+
override fun failure(t: Throwable) {
9999
logcat(ERROR) { "ndk-crash: error loading library in process $processName: ${t?.asLog()}" }
100100
}
101101

app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/heartbeat/VpnServiceHeartbeat.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.duckduckgo.mobile.android.vpn.heartbeat
1818

19-
import android.content.Context
2019
import android.os.Process
2120
import com.duckduckgo.common.utils.ConflatedJob
2221
import com.duckduckgo.common.utils.DispatcherProvider
@@ -42,7 +41,6 @@ import logcat.logcat
4241
)
4342
@SingleInstanceIn(VpnScope::class)
4443
class VpnServiceHeartbeat @Inject constructor(
45-
private val context: Context,
4644
private val vpnDatabase: VpnDatabase,
4745
private val dispatcherProvider: DispatcherProvider,
4846
) : VpnServiceCallbacks {

app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2452,7 +2452,7 @@ class BrowserTabFragment :
24522452

24532453
private fun launchDialogForIntent(
24542454
context: Context,
2455-
pm: PackageManager?,
2455+
pm: PackageManager,
24562456
intent: Intent,
24572457
activities: List<ResolveInfo>,
24582458
useFirstActivityFound: Boolean,
@@ -2942,7 +2942,6 @@ class BrowserTabFragment :
29422942
mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
29432943
javaScriptCanOpenWindowsAutomatically = appBuildConfig.isTest // only allow when running tests
29442944
setSupportMultipleWindows(true)
2945-
disableWebSql(this)
29462945
setSupportZoom(true)
29472946
if (accessibilitySettingsDataStore.overrideSystemFontSize) {
29482947
textZoom = accessibilitySettingsDataStore.fontSize.toInt()
@@ -3347,13 +3346,6 @@ class BrowserTabFragment :
33473346
binding.swipeRefreshContainer.progressViewStartOffset -= 15
33483347
}
33493348

3350-
/**
3351-
* Explicitly disable database to try protect against Magellan WebSQL/SQLite vulnerability
3352-
*/
3353-
private fun disableWebSql(settings: WebSettings) {
3354-
settings.databaseEnabled = false
3355-
}
3356-
33573349
@Suppress("NewApi") // This API and the behaviour described only apply to apps with targetSdkVersion ≥ TIRAMISU.
33583350
private fun setAlgorithmicDarkeningAllowed(settings: WebSettings) {
33593351
// https://developer.android.com/reference/androidx/webkit/WebSettingsCompat#setAlgorithmicDarkeningAllowed(android.webkit.WebSettings,boolean)

app/src/main/java/com/duckduckgo/app/browser/urlextraction/UrlExtractingWebView.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ class UrlExtractingWebView(
4040
javaScriptEnabled = true
4141
domStorageEnabled = true
4242
mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
43-
disableWebSql(this)
4443
loadsImagesAutomatically = false
4544
}
4645
setWebViewClient(webViewClient)
@@ -54,13 +53,6 @@ class UrlExtractingWebView(
5453
}
5554
}
5655

57-
/**
58-
* Explicitly disable database to try protect against Magellan WebSQL/SQLite vulnerability
59-
*/
60-
private fun disableWebSql(settings: WebSettings) {
61-
settings.databaseEnabled = false
62-
}
63-
6456
override fun loadUrl(url: String) {
6557
initialUrl = url
6658
super.loadUrl(url)

app/src/main/java/com/duckduckgo/app/global/view/ViewChildrenSequences.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private class ViewChildrenRecursiveSequence(private val view: View) : Sequence<V
6767

6868
private class RecursiveViewIterator(view: View) : Iterator<View> {
6969
private val sequences = arrayListOf(view.childrenSequence())
70-
private var current = sequences.removeLast().iterator()
70+
private var current = sequences.removeLastElement().iterator()
7171

7272
override fun next(): View {
7373
if (!hasNext()) throw NoSuchElementException()
@@ -80,13 +80,13 @@ private class ViewChildrenRecursiveSequence(private val view: View) : Sequence<V
8080

8181
override fun hasNext(): Boolean {
8282
if (!current.hasNext() && sequences.isNotEmpty()) {
83-
current = sequences.removeLast().iterator()
83+
current = sequences.removeLastElement().iterator()
8484
}
8585
return current.hasNext()
8686
}
8787

8888
@Suppress("NOTHING_TO_INLINE")
89-
private inline fun <T : Any> MutableList<T>.removeLast(): T {
89+
private inline fun <T : Any> MutableList<T>.removeLastElement(): T {
9090
if (isEmpty()) throw NoSuchElementException()
9191
return removeAt(size - 1)
9292
}

app/src/test/java/com/duckduckgo/app/bookmarks/model/SyncSavedSitesRepositoryTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ class SyncSavedSitesRepositoryTest {
213213
savedSitesRelationsDao.insertList(relation)
214214

215215
val removedEntities = entities.toMutableList()
216-
val removedEntity = removedEntities.removeFirst()
216+
val removedEntity = removedEntities.removeAt(0)
217217

218218
val removedEntitiesIds = removedEntities.map { it.entityId }
219219
val childrenJSON = stringListAdapter.toJson(removedEntitiesIds)
@@ -320,7 +320,7 @@ class SyncSavedSitesRepositoryTest {
320320
savedSitesRelationsDao.insertList(folderRelation)
321321

322322
val updatedChildren = bookmarks.toMutableList()
323-
val removedChildren = updatedChildren.removeFirst()
323+
val removedChildren = updatedChildren.removeAt(0)
324324

325325
repository.replaceBookmarkFolder(folder, updatedChildren.map { it.entityId })
326326

autofill/autofill-impl/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ dependencies {
4646
implementation project(':data-store-api')
4747
testImplementation project(':feature-toggles-test')
4848
implementation project(path: ':settings-api') // temporary until we release new settings
49+
implementation project(':library-loader-api')
4950

5051
anvil project(path: ':anvil-compiler')
5152
implementation project(path: ':anvil-annotations')
@@ -80,7 +81,7 @@ dependencies {
8081
ksp AndroidX.room.compiler
8182
implementation AndroidX.room.ktx
8283

83-
implementation "net.zetetic:android-database-sqlcipher:_"
84+
implementation "net.zetetic:sqlcipher-android:_"
8485
implementation "com.facebook.shimmer:shimmer:_"
8586

8687
// Testing dependencies

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/securestorage/SecureStorageDatabaseFactory.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,27 @@ import com.duckduckgo.autofill.api.AutofillFeature
2222
import com.duckduckgo.autofill.store.db.ALL_MIGRATIONS
2323
import com.duckduckgo.autofill.store.db.SecureStorageDatabase
2424
import com.duckduckgo.di.scopes.AppScope
25+
import com.duckduckgo.library.loader.LibraryLoader
2526
import com.squareup.anvil.annotations.ContributesBinding
2627
import dagger.SingleInstanceIn
2728
import javax.inject.Inject
2829
import kotlinx.coroutines.runBlocking
2930
import kotlinx.coroutines.sync.Mutex
3031
import kotlinx.coroutines.sync.withLock
31-
import net.sqlcipher.database.SupportFactory
32+
import logcat.LogPriority.ERROR
33+
import logcat.asLog
34+
import logcat.logcat
35+
import net.zetetic.database.sqlcipher.SupportOpenHelperFactory
3236

3337
interface SecureStorageDatabaseFactory {
3438
suspend fun getDatabase(): SecureStorageDatabase?
3539
}
3640

3741
@SingleInstanceIn(AppScope::class)
38-
@ContributesBinding(AppScope::class)
42+
@ContributesBinding(
43+
scope = AppScope::class,
44+
boundType = SecureStorageDatabaseFactory::class,
45+
)
3946
class RealSecureStorageDatabaseFactory @Inject constructor(
4047
private val context: Context,
4148
private val keyProvider: SecureStorageKeyProvider,
@@ -45,6 +52,17 @@ class RealSecureStorageDatabaseFactory @Inject constructor(
4552

4653
private val mutex = Mutex()
4754

55+
init {
56+
logcat { "Loading the sqlcipher native library" }
57+
try {
58+
LibraryLoader.loadLibrary(context, "sqlcipher")
59+
logcat { "sqlcipher native library loaded ok" }
60+
} catch (t: Throwable) {
61+
// error loading the library
62+
logcat(ERROR) { "Error loading sqlcipher library: ${t.asLog()}" }
63+
}
64+
}
65+
4866
override suspend fun getDatabase(): SecureStorageDatabase? {
4967
return if (autofillFeature.createAsyncPreferences().isEnabled()) {
5068
getAsyncDatabase()
@@ -80,7 +98,7 @@ class RealSecureStorageDatabaseFactory @Inject constructor(
8098
context,
8199
SecureStorageDatabase::class.java,
82100
"secure_storage_database_encrypted.db",
83-
).openHelperFactory(SupportFactory(keyProvider.getl1Key()))
101+
).openHelperFactory(SupportOpenHelperFactory(keyProvider.getl1Key()))
84102
.addMigrations(*ALL_MIGRATIONS)
85103
.build()
86104
_database

0 commit comments

Comments
 (0)