From 9dfd17ffbe8b82dc5313f7f822262f06408bf718 Mon Sep 17 00:00:00 2001 From: domedav Date: Mon, 12 Jan 2026 16:18:21 +0100 Subject: [PATCH] fix: resolve SQLite unique constraint crash in Work Profiles (Android 15) Fixed a critical ANR / Crash cycle occurring on Android 15 when the app is running within a Work Profile. The crash was caused by a SQLiteConstraintException in the ProxyApplicationMapping table. During profile refreshes or package updates, Android 15 sometimes reassigns UIDs in a way that creates collisions with existing database entries. The previous tombstoneApp implementation performed a blind update, which triggered a unique constraint violation and corrupted the database state. --- .../database/ProxyApplicationMappingDAO.kt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMappingDAO.kt b/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMappingDAO.kt index 5a6ec8757..279d0f065 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMappingDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMappingDAO.kt @@ -23,6 +23,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update +import androidx.room.Transaction @Dao interface ProxyApplicationMappingDAO { @@ -101,6 +102,23 @@ interface ProxyApplicationMappingDAO { @Query("update ProxyApplicationMapping set uid = :uid where packageName = :packageName") fun updateUidForApp(uid: Int, packageName: String) - @Query("update ProxyApplicationMapping set uid = :newUid where uid = :oldUid") - fun tombstoneApp(oldUid: Int, newUid: Int) + @Transaction + fun tombstoneApp(oldUid: Int, newUid: Int) { + // Only apply the logic bellow, if the uid is from a work profile + if (newUid >= 1_000_000) { + // If a record with the 'newUid' already exists, delete it first + // This prevents an application crash: database constraint + deleteMappingByUid(newUid) + } + + // Now that the slot is empty, move the 'oldUid' records to 'newUid' + updateUidByOldUid(oldUid, newUid) + } + + @Query("delete from ProxyApplicationMapping where uid = :uid") + fun deleteMappingByUid(uid: Int) + + // 'ignore' provides an extra layer of safety + @Query("update or ignore ProxyApplicationMapping set uid = :newUid where uid = :oldUid") + fun updateUidByOldUid(oldUid: Int, newUid: Int) }