diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6e120f5daa..5175d2110d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -95,7 +95,7 @@ securityCrypto = "1.1.0-alpha06" simpledialogfragments = "09a642bc42" slf4jApi = "2.0.13" snakeyaml = "2.2" -sqlcipher = "4.5.4" +sqlcipher = "4.6.1" taptargetview = "1.13.3" tesseract4androidOpenmp = "4.7.0" mlkitTextRecognition = "16.0.1" @@ -111,7 +111,7 @@ accompanist-themeadapter-material3 = { module = "com.google.accompanist:accompan acra-core = { module = "ch.acra:acra-core", version.ref = "acraVersion" } acra-dialog = { module = "ch.acra:acra-dialog", version.ref = "acraVersion" } acra-mail = { module = "ch.acra:acra-mail", version.ref = "acraVersion" } -android-database-sqlcipher = { module = "net.zetetic:android-database-sqlcipher", version.ref = "sqlcipher" } +android-database-sqlcipher = { module = "net.zetetic:sqlcipher-android", version.ref = "sqlcipher" } android-image-cropper = { module = "com.github.mtotschnig:Android-Image-Cropper", version.ref = "androidImageCropper" } android-state = { module = "com.evernote:android-state", version.ref = "evernote" } android-state-processor = { module = "com.evernote:android-state-processor", version.ref = "evernote" } diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CategoriesCabTest.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CategoriesCabTest.kt index f467f5f25f..8c4efdbfc3 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CategoriesCabTest.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CategoriesCabTest.kt @@ -29,6 +29,7 @@ import com.adevinta.android.barista.internal.matcher.HelperMatchers import com.google.common.truth.Truth.assertThat import org.hamcrest.CoreMatchers.containsString import org.hamcrest.Matchers +import org.junit.After import org.junit.Test import org.totschnig.myexpenses.R import org.totschnig.myexpenses.activity.Action @@ -37,6 +38,10 @@ import org.totschnig.myexpenses.compose.TEST_TAG_EDIT_TEXT import org.totschnig.myexpenses.compose.TEST_TAG_POSITIVE_BUTTON import org.totschnig.myexpenses.contract.TransactionsContract.Transactions import org.totschnig.myexpenses.db2.FLAG_NEUTRAL +import org.totschnig.myexpenses.db2.deleteAccount +import org.totschnig.myexpenses.db2.deleteBudget +import org.totschnig.myexpenses.db2.deleteCategory +import org.totschnig.myexpenses.db2.deleteTemplate import org.totschnig.myexpenses.model.CurrencyUnit import org.totschnig.myexpenses.model.Grouping import org.totschnig.myexpenses.model.Money @@ -55,14 +60,22 @@ class CategoriesCabTest : BaseComposeTest() { private lateinit var account: org.totschnig.myexpenses.model2.Account private var categoryId: Long = 0 private var origListSize = 0 + private var controlCategory: Long = 0 private fun baseFixture() { account = buildAccount("Test account 1") categoryId = writeCategory(label = "TestCategory") - writeCategory(label = "Control Category") + controlCategory = writeCategory(label = "Control Category") origListSize = repository.count(TransactionProvider.CATEGORIES_URI) } + @After + fun clearDb() { + repository.deleteAccount(account.id) + repository.deleteCategory(categoryId) + repository.deleteCategory(controlCategory) + } + private fun launch() = ActivityScenario.launch( Intent(targetContext, ManageCategories::class.java).also { @@ -73,18 +86,18 @@ class CategoriesCabTest : BaseComposeTest() { testScenario = it } - private fun fixtureWithMappedTransaction() { + private fun fixtureWithMappedTransaction(): Long { baseFixture() - with(Transaction.getNewInstance(account.id, homeCurrency)) { + return with(Transaction.getNewInstance(account.id, homeCurrency)) { amount = Money(homeCurrency, -1200L) catId = categoryId - save(contentResolver) + ContentUris.parseId(save(contentResolver)!!) } } - private fun fixtureWithMappedTemplate() { + private fun fixtureWithMappedTemplate(): Long { baseFixture() - with( + return with( Template( contentResolver, account.id, @@ -95,11 +108,11 @@ class CategoriesCabTest : BaseComposeTest() { ) { amount = Money(CurrencyUnit(Currency.getInstance("USD")), -1200L) catId = categoryId - save(contentResolver) + ContentUris.parseId(save(contentResolver)!!) } } - private fun fixtureWithMappedBudget() { + private fun fixtureWithMappedBudget(): Long { baseFixture() val budget = Budget( 0L, @@ -121,6 +134,7 @@ class CategoriesCabTest : BaseComposeTest() { )!! ) setCategoryBudget(budgetId, categoryId, 50000) + return budgetId } private fun setCategoryBudget( @@ -161,8 +175,9 @@ class CategoriesCabTest : BaseComposeTest() { @Test fun shouldNotDeleteCategoryMappedToTransaction() { - fixtureWithMappedTransaction() + val transactionId = fixtureWithMappedTransaction() launch().use { + assertTextAtPosition("TestCategory", 0) callDelete() onView(withId(com.google.android.material.R.id.snackbar_text)) .check( @@ -176,11 +191,12 @@ class CategoriesCabTest : BaseComposeTest() { ) assertThat(repository.count(TransactionProvider.CATEGORIES_URI)).isEqualTo(origListSize) } + repository.deleteTransaction(transactionId) } @Test fun shouldNotDeleteCategoryMappedToTemplate() { - fixtureWithMappedTemplate() + val templateId = fixtureWithMappedTemplate() launch().use { callDelete() onView(withId(com.google.android.material.R.id.snackbar_text)) @@ -194,12 +210,13 @@ class CategoriesCabTest : BaseComposeTest() { ) ) assertThat(repository.count(TransactionProvider.CATEGORIES_URI)).isEqualTo(origListSize) + repository.deleteTemplate(templateId) } } @Test fun shouldNotDeleteCategoryMappedToBudget() { - fixtureWithMappedBudget() + val budgetId = fixtureWithMappedBudget() launch().use { callDelete(false) onView(withText(containsString(getString(R.string.warning_delete_category_with_budget)))).check( @@ -208,6 +225,7 @@ class CategoriesCabTest : BaseComposeTest() { onView(withText(R.string.response_no)).perform(click()) assertThat(repository.count(TransactionProvider.CATEGORIES_URI)).isEqualTo(origListSize) } + repository.deleteBudget(budgetId) } private fun callDelete(withConfirmation: Boolean = true, position: Int = 0) { diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CriterionReachedTest.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CriterionReachedTest.kt index c038177df0..8989717bf9 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CriterionReachedTest.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CriterionReachedTest.kt @@ -3,22 +3,16 @@ package org.totschnig.myexpenses.test.espresso import android.content.ContentUris import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.onNodeWithText -import androidx.test.espresso.Espresso.onData -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.matcher.ViewMatchers.withId -import org.hamcrest.CoreMatchers.allOf -import org.hamcrest.CoreMatchers.instanceOf +import org.junit.After import org.totschnig.myexpenses.R import org.totschnig.myexpenses.activity.ExpenseEdit -import org.totschnig.myexpenses.adapter.IdHolder import org.totschnig.myexpenses.contract.TransactionsContract.Transactions +import org.totschnig.myexpenses.db2.deleteAccount import org.totschnig.myexpenses.model.CurrencyUnit import org.totschnig.myexpenses.model.Money import org.totschnig.myexpenses.model.Transaction import org.totschnig.myexpenses.model2.Account import org.totschnig.myexpenses.provider.DatabaseConstants -import org.totschnig.myexpenses.testutils.withAccount import java.util.Currency import kotlin.math.absoluteValue import kotlin.math.sign @@ -27,22 +21,18 @@ import kotlin.test.Test class CriterionReachedTest : BaseExpenseEditTest() { val currency = CurrencyUnit(Currency.getInstance("USD")) - lateinit var account2: Account - - fun fixture(criterion: Long, withAccount2: Boolean, openingBalance: Long = 0) { + fun fixture(criterion: Long, openingBalance: Long = 0) { account1 = Account( label = "Test label 1", currency = currency.code, criterion = criterion, openingBalance = openingBalance ).createIn(repository) - if (withAccount2) { - account2 = Account( - label = "Test label 2", - currency = currency.code, - criterion = criterion - ).createIn(repository) - } + } + + @After + fun cleanup() { + repository.deleteAccount(account1.id) } @Test @@ -184,7 +174,7 @@ class CriterionReachedTest : BaseExpenseEditTest() { expectedTitle: Int?, openingBalance: Long = 0, ) { - fixture(criterion, false, openingBalance) + fixture(criterion, openingBalance) launchForResult(intentForNewTransaction.apply { putExtra(ExpenseEdit.KEY_INCOME, amount > 0) putExtra(Transactions.OPERATION_TYPE, Transactions.TYPE_TRANSACTION) @@ -205,7 +195,7 @@ class CriterionReachedTest : BaseExpenseEditTest() { editedAmount: Int, expectedTitle: Int?, ) { - fixture(criterion, false) + fixture(criterion) val transactionId = Transaction.getNewInstance(account1.id, currency).let { it.amount = Money(currency, existingAmount) ContentUris.parseId(it.save(contentResolver)!!) @@ -233,7 +223,12 @@ class CriterionReachedTest : BaseExpenseEditTest() { editedAmount: Int, expectedTitle: Int?, ) { - fixture(criterion, true) + fixture(criterion) + val account2 = Account( + label = "Test label 2", + currency = currency.code, + criterion = criterion + ).createIn(repository) val transactionId = Transaction.getNewInstance(account1.id, currency).let { it.amount = Money(currency, existingAmount) ContentUris.parseId(it.save(contentResolver)!!) @@ -254,5 +249,6 @@ class CriterionReachedTest : BaseExpenseEditTest() { assertFinishing() } } + repository.deleteAccount(account2.id) } } \ No newline at end of file diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CriterionReachedTestTransfer.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CriterionReachedTestTransfer.kt index f5248c53e5..1f9a550bcf 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CriterionReachedTestTransfer.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/CriterionReachedTestTransfer.kt @@ -9,8 +9,10 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withSubstring import androidx.test.espresso.matcher.ViewMatchers.withText +import org.junit.After import org.totschnig.myexpenses.R import org.totschnig.myexpenses.contract.TransactionsContract.Transactions +import org.totschnig.myexpenses.db2.deleteAccount import org.totschnig.myexpenses.model.CurrencyUnit import org.totschnig.myexpenses.model.Money import org.totschnig.myexpenses.model.Transfer @@ -31,13 +33,17 @@ class CriterionReachedTestTransfer : BaseExpenseEditTest() { currency = currency.code, criterion = -10000 ).createIn(repository) - if (true) { - account2 = Account( - label = "Saving", - currency = currency.code, - criterion = 5000 - ).createIn(repository) - } + account2 = Account( + label = "Saving", + currency = currency.code, + criterion = 5000 + ).createIn(repository) + } + + @After + fun cleanup() { + repository.deleteAccount(account1.id) + repository.deleteAccount(account2.id) } diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/DistributionTest.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/DistributionTest.kt index 8d046a3f9a..a3a834b57b 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/DistributionTest.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/espresso/DistributionTest.kt @@ -1,5 +1,6 @@ package org.totschnig.myexpenses.test.espresso +import android.content.ContentUris import android.content.Intent import androidx.annotation.StringRes import androidx.compose.ui.test.junit4.createEmptyComposeRule @@ -17,12 +18,15 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import org.assertj.core.api.Assertions.assertThat import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.containsString +import org.junit.After import org.junit.Rule import org.junit.Test import org.totschnig.myexpenses.R import org.totschnig.myexpenses.activity.DistributionActivity import org.totschnig.myexpenses.activity.ProtectedFragmentActivity import org.totschnig.myexpenses.db2.FLAG_INCOME +import org.totschnig.myexpenses.db2.deleteAccount +import org.totschnig.myexpenses.db2.deleteCategory import org.totschnig.myexpenses.model.Money import org.totschnig.myexpenses.model.Transaction import org.totschnig.myexpenses.model2.Account @@ -36,6 +40,12 @@ class DistributionTest : BaseUiTest() { private lateinit var account: Account + private var categoryExpenseId = 0L + private var categoryIncomeId = 0L + private var transactionExpenseId = 0L + private var transactionIncomeId = 0L + + private fun baseFixture( showIncome: Boolean = false, showExpense: Boolean = true, @@ -56,21 +66,39 @@ class DistributionTest : BaseUiTest() { showExpense: Boolean = true ) { baseFixture(showIncome, showExpense) { - val categoryExpenseId = writeCategory("Expense") - val categoryIncomeId = writeCategory("Income", type = FLAG_INCOME) - with(Transaction.getNewInstance(account.id, homeCurrency)) { + categoryExpenseId = writeCategory("Expense") + categoryIncomeId = writeCategory("Income", type = FLAG_INCOME) + transactionExpenseId = with(Transaction.getNewInstance(account.id, homeCurrency)) { amount = Money(homeCurrency, -1200L) catId = categoryExpenseId - save(contentResolver) + ContentUris.parseId(save(contentResolver)!!) } - with(Transaction.getNewInstance(account.id, homeCurrency)) { + transactionIncomeId = with(Transaction.getNewInstance(account.id, homeCurrency)) { amount = Money(homeCurrency, 3400L) catId = categoryIncomeId - save(contentResolver) + ContentUris.parseId(save(contentResolver)!!) } } } + @After + fun cleanup() { + if (transactionExpenseId != 0L) { + repository.deleteTransaction(transactionExpenseId) + } + if (transactionIncomeId != 0L) { + repository.deleteTransaction(transactionIncomeId) + } + if (categoryExpenseId != 0L) { + repository.deleteCategory(categoryExpenseId) + } + if (categoryIncomeId != 0L) { + repository.deleteCategory(categoryIncomeId) + } + repository.deleteAccount(account.id) + } + + private fun assertIncome() { onView(allOf(withText(containsString("Income")), withText(containsString("34")))) .inRoot(isDialog()) diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/provider/TransactionDebtTest.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/provider/TransactionDebtTest.kt index f1e2f9be16..397147540c 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/provider/TransactionDebtTest.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/provider/TransactionDebtTest.kt @@ -14,7 +14,6 @@ import org.totschnig.myexpenses.provider.insert import org.totschnig.myexpenses.provider.update import org.totschnig.myexpenses.testutils.BaseDbTest import org.totschnig.myexpenses.viewmodel.data.Debt -import java.util.* class TransactionDebtTest: BaseDbTest() { private var testAccountId: Long = 0 @@ -74,7 +73,7 @@ class TransactionDebtTest: BaseDbTest() { "$KEY_ROWID = ?", arrayOf(closedTransaction.toString()) ) kotlin.test.fail("Update of closed debt did not raise SQLiteConstraintException") - } catch (e: SQLiteConstraintException) { + } catch (_: SQLiteConstraintException) { //Expected } } @@ -86,7 +85,7 @@ class TransactionDebtTest: BaseDbTest() { "$KEY_ROWID = ?", arrayOf(closedTransaction.toString()) ) kotlin.test.fail("Delete of transaction for closed debt did not raise SQLiteConstraintException") - } catch (e: SQLiteConstraintException) { + } catch (_: SQLiteConstraintException) { //Expected } } @@ -106,7 +105,7 @@ class TransactionDebtTest: BaseDbTest() { testTransaction.contentValues ) kotlin.test.fail("Insert into closed debt dit no raise SQLiteConstraintException") - } catch (e: SQLiteConstraintException) { + } catch (_: SQLiteConstraintException) { //Expected } } diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/testutils/BaseUiTest.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/testutils/BaseUiTest.kt index 2ceaef8230..82b643a8ee 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/testutils/BaseUiTest.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/testutils/BaseUiTest.kt @@ -13,7 +13,6 @@ import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.* import androidx.test.espresso.NoMatchingViewException -import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.scrollTo import androidx.test.espresso.assertion.ViewAssertions.doesNotExist @@ -129,10 +128,10 @@ abstract class BaseUiTest { ) { inRoot(RootMatchers.isPlatformPopup()) } - }.perform(ViewActions.click()) + }.perform(click()) } catch (_: NoMatchingViewException) { Espresso.openActionBarOverflowMenu(isCab) - onData(menuIdMatcher(menuItemId)).inRoot(RootMatchers.isPlatformPopup()).perform(ViewActions.click()) + onData(menuIdMatcher(menuItemId)).inRoot(RootMatchers.isPlatformPopup()).perform(click()) } } @@ -169,14 +168,14 @@ abstract class BaseUiTest { if (DistributionHelper.isPlay) { try { //without play service a billing setup error dialog is displayed - onView(ViewMatchers.withText(android.R.string.ok)).perform(ViewActions.click()) + onView(ViewMatchers.withText(android.R.string.ok)).perform(click()) } catch (_: Exception) { } } onView(ViewMatchers.withSubstring(getString(R.string.dialog_title_contrib_feature))).check( matches(isDisplayed()) ) - onView(ViewMatchers.withText(R.string.button_try)).perform(ViewActions.scrollTo(), ViewActions.click()) + onView(ViewMatchers.withText(R.string.button_try)).perform(scrollTo(), click()) } } @@ -268,7 +267,7 @@ abstract class BaseUiTest { } fun clickFab() { - onView(withId(R.id.fab)).perform(ViewActions.click()) + onView(withId(R.id.fab)).perform(click()) } fun checkAccount(label: String) { diff --git a/myExpenses/src/main/java/org/totschnig/myexpenses/db2/RepositoryBudget.kt b/myExpenses/src/main/java/org/totschnig/myexpenses/db2/RepositoryBudget.kt index 505145fd8b..4dc0af134c 100644 --- a/myExpenses/src/main/java/org/totschnig/myexpenses/db2/RepositoryBudget.kt +++ b/myExpenses/src/main/java/org/totschnig/myexpenses/db2/RepositoryBudget.kt @@ -31,7 +31,6 @@ import org.totschnig.myexpenses.viewmodel.data.BudgetAllocation import org.totschnig.myexpenses.viewmodel.data.BudgetProgress import org.totschnig.myexpenses.viewmodel.data.DateInfo import org.totschnig.myexpenses.viewmodel.data.DateInfoExtra -import timber.log.Timber import java.lang.IllegalArgumentException import java.time.LocalDate import java.time.temporal.ChronoUnit @@ -239,4 +238,8 @@ suspend fun Repository.loadBudgetProgress(budgetId: Long, period: Pair totalDays, currentDay ) - } \ No newline at end of file + } + +fun Repository.deleteBudget(id: Long) { + contentResolver.delete(ContentUris.withAppendedId(TransactionProvider.BUDGETS_URI, id), null, null) +} \ No newline at end of file diff --git a/myExpenses/src/main/java/org/totschnig/myexpenses/db2/RepositoryTransaction.kt b/myExpenses/src/main/java/org/totschnig/myexpenses/db2/RepositoryTransaction.kt index 6f128c26e1..8f77ba8643 100644 --- a/myExpenses/src/main/java/org/totschnig/myexpenses/db2/RepositoryTransaction.kt +++ b/myExpenses/src/main/java/org/totschnig/myexpenses/db2/RepositoryTransaction.kt @@ -247,3 +247,8 @@ private fun ContentResolver.findBySelection( )?.use { if (it.moveToFirst()) it.getLong(0) else null } ?: -1 + + +fun Repository.deleteTemplate(id: Long) { + contentResolver.delete(ContentUris.withAppendedId(TransactionProvider.TEMPLATES_URI, id), null, null) +} \ No newline at end of file diff --git a/myExpenses/src/main/java/org/totschnig/myexpenses/di/DataModule.kt b/myExpenses/src/main/java/org/totschnig/myexpenses/di/DataModule.kt index 8ff3e0b4bc..16a58342a6 100644 --- a/myExpenses/src/main/java/org/totschnig/myexpenses/di/DataModule.kt +++ b/myExpenses/src/main/java/org/totschnig/myexpenses/di/DataModule.kt @@ -39,7 +39,7 @@ open class DataModule(private val shouldInsertDefaultTransferCategory: Boolean = companion object { val cryptProvider: SqlCryptProvider by lazy { Class.forName("org.totschnig.sqlcrypt.SQLiteOpenHelperFactory") - .newInstance() as SqlCryptProvider + .getDeclaredConstructor().newInstance() as SqlCryptProvider } } @@ -123,7 +123,7 @@ open class DataModule(private val shouldInsertDefaultTransferCategory: Boolean = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY).use { if (!try { it.isDatabaseIntegrityOk - } catch (e: SQLiteDatabaseCorruptException) { + } catch (_: SQLiteDatabaseCorruptException) { false } ) { diff --git a/myExpenses/src/main/java/org/totschnig/myexpenses/provider/BaseTransactionProvider.kt b/myExpenses/src/main/java/org/totschnig/myexpenses/provider/BaseTransactionProvider.kt index a6b5d7e7a5..8bc090c9d9 100644 --- a/myExpenses/src/main/java/org/totschnig/myexpenses/provider/BaseTransactionProvider.kt +++ b/myExpenses/src/main/java/org/totschnig/myexpenses/provider/BaseTransactionProvider.kt @@ -1703,7 +1703,7 @@ abstract class BaseTransactionProvider : ContentProvider() { ) null else category.id } - } catch (e: SQLiteConstraintException) { + } catch (_: SQLiteConstraintException) { null } } diff --git a/myExpenses/src/main/java/org/totschnig/myexpenses/provider/MoreDbUtils.kt b/myExpenses/src/main/java/org/totschnig/myexpenses/provider/MoreDbUtils.kt index 58e398c181..31f6ed4208 100644 --- a/myExpenses/src/main/java/org/totschnig/myexpenses/provider/MoreDbUtils.kt +++ b/myExpenses/src/main/java/org/totschnig/myexpenses/provider/MoreDbUtils.kt @@ -597,12 +597,15 @@ fun SupportSQLiteDatabase.update( values: ContentValues, whereClause: String?, whereArgs: Array? -) = //https://github.com/sqlcipher/android-database-sqlcipher/issues/615 +) = //https://github.com/sqlcipher/sqlcipher-android/issues/50 update(table, SQLiteDatabase.CONFLICT_NONE, values, whereClause, whereArgs ?: emptyArray()) fun SupportSQLiteDatabase.insert(table: String, values: ContentValues): Long = insert(table, SQLiteDatabase.CONFLICT_NONE, values) +fun SupportSQLiteDatabase.delete(table: String, whereClause: String?, whereArgs: Array?) = + delete(table, whereClause, whereArgs ?: emptyArray()) + /** * insert where conflicts are ignored instead of raising exception */ diff --git a/myExpenses/src/main/java/org/totschnig/myexpenses/provider/TransactionDatabase.java b/myExpenses/src/main/java/org/totschnig/myexpenses/provider/TransactionDatabase.java index 1a26b984ac..e8fc188c0c 100644 --- a/myExpenses/src/main/java/org/totschnig/myexpenses/provider/TransactionDatabase.java +++ b/myExpenses/src/main/java/org/totschnig/myexpenses/provider/TransactionDatabase.java @@ -442,7 +442,7 @@ public void onOpen(@NonNull SupportSQLiteDatabase db) { "%1$s IN %2$s OR %3$s OR %4$s IN (SELECT %5$s FROM %6$s WHERE %3$s)", KEY_ROWID, uncommitedSelect, uncommitedParentSelect, KEY_TRANSFER_PEER, KEY_ROWID, TABLE_TRANSACTIONS); Timber.d(whereClause); - MoreDbUtilsKt.safeUpdateWithSealed(db, () -> db.delete(TABLE_TRANSACTIONS, whereClause, null)); + MoreDbUtilsKt.safeUpdateWithSealed(db, () -> MoreDbUtilsKt.delete(db, TABLE_TRANSACTIONS, whereClause, null)); } catch (SQLiteException e) { CrashHandler.report(e); } diff --git a/myExpenses/src/main/java/org/totschnig/myexpenses/provider/TransactionProvider.java b/myExpenses/src/main/java/org/totschnig/myexpenses/provider/TransactionProvider.java index 2eedf16460..366f136276 100644 --- a/myExpenses/src/main/java/org/totschnig/myexpenses/provider/TransactionProvider.java +++ b/myExpenses/src/main/java/org/totschnig/myexpenses/provider/TransactionProvider.java @@ -1110,7 +1110,7 @@ public int delete(@NonNull Uri uri, String where, String[] whereArgs) { int uriMatch = URI_MATCHER.match(uri); maybeSetDirty(uriMatch); switch (uriMatch) { - case TRANSACTIONS, UNCOMMITTED -> count = db.delete(TABLE_TRANSACTIONS, where, whereArgs); + case TRANSACTIONS, UNCOMMITTED -> count = MoreDbUtilsKt.delete(db, TABLE_TRANSACTIONS, where, whereArgs); case TRANSACTION_ID -> { //maybe TODO ?: where and whereArgs are ignored segment = uri.getPathSegments().get(1); @@ -1129,8 +1129,8 @@ public int delete(@NonNull Uri uri, String where, String[] whereArgs) { //we delete the transaction, its children and its transfer peer, and transfer peers of its children if (uri.getQueryParameter(QUERY_PARAMETER_MARK_VOID) == null) { //we delete the parent separately, so that the changes trigger can correctly record the parent uuid - count = db.delete(TABLE_TRANSACTIONS, WHERE_DEPENDENT, new String[]{segment, segment}); - count += db.delete(TABLE_TRANSACTIONS, WHERE_SELF_OR_PEER, new String[]{segment, segment}); + count = MoreDbUtilsKt.delete(db, TABLE_TRANSACTIONS, WHERE_DEPENDENT, new String[]{segment, segment}); + count += MoreDbUtilsKt.delete(db, TABLE_TRANSACTIONS, WHERE_SELF_OR_PEER, new String[]{segment, segment}); } else { ContentValues v = new ContentValues(); v.put(KEY_CR_STATUS, CrStatus.VOID.name()); @@ -1141,53 +1141,53 @@ public int delete(@NonNull Uri uri, String where, String[] whereArgs) { db.endTransaction(); } } - case TEMPLATES -> count = db.delete(TABLE_TEMPLATES, where, whereArgs); - case TEMPLATE_ID -> count = db.delete(TABLE_TEMPLATES, + case TEMPLATES -> count = MoreDbUtilsKt.delete(db, TABLE_TEMPLATES, where, whereArgs); + case TEMPLATE_ID -> count = MoreDbUtilsKt.delete(db, TABLE_TEMPLATES, KEY_ROWID + " = " + uri.getLastPathSegment() + prefixAnd(where), whereArgs); - case ACCOUNTTYPES_METHODS -> count = db.delete(TABLE_ACCOUNTTYES_METHODS, where, whereArgs); - case ACCOUNTS -> count = db.delete(TABLE_ACCOUNTS, where, whereArgs); - case ACCOUNT_ID -> count = db.delete(TABLE_ACCOUNTS, + case ACCOUNTTYPES_METHODS -> count = MoreDbUtilsKt.delete(db, TABLE_ACCOUNTTYES_METHODS, where, whereArgs); + case ACCOUNTS -> count = MoreDbUtilsKt.delete(db, TABLE_ACCOUNTS, where, whereArgs); + case ACCOUNT_ID -> count = MoreDbUtilsKt.delete(db, TABLE_ACCOUNTS, KEY_ROWID + " = " + uri.getLastPathSegment() + prefixAnd(where), whereArgs); //update aggregate cursor //getContext().getContentResolver().notifyChange(AGGREGATES_URI, null); case CATEGORIES -> - count = db.delete(TABLE_CATEGORIES, KEY_ROWID + " != " + SPLIT_CATID + prefixAnd(where), + count = MoreDbUtilsKt.delete(db, TABLE_CATEGORIES, KEY_ROWID + " != " + SPLIT_CATID + prefixAnd(where), whereArgs); case CATEGORY_ID -> { String lastPathSegment = uri.getLastPathSegment(); if (Long.parseLong(lastPathSegment) == SPLIT_CATID) throw new IllegalArgumentException("split category can not be deleted"); - count = db.delete(TABLE_CATEGORIES, + count = MoreDbUtilsKt.delete(db, TABLE_CATEGORIES, KEY_ROWID + " = " + lastPathSegment + prefixAnd(where), whereArgs); } - case PAYEE_ID -> count = db.delete(TABLE_PAYEES, + case PAYEE_ID -> count = MoreDbUtilsKt.delete(db, TABLE_PAYEES, KEY_ROWID + " = " + uri.getLastPathSegment() + prefixAnd(where), whereArgs); - case METHOD_ID -> count = db.delete(TABLE_METHODS, + case METHOD_ID -> count = MoreDbUtilsKt.delete(db, TABLE_METHODS, KEY_ROWID + " = " + uri.getLastPathSegment() + prefixAnd(where), whereArgs); case PLANINSTANCE_TRANSACTION_STATUS -> - count = db.delete(TABLE_PLAN_INSTANCE_STATUS, where, whereArgs); + count = MoreDbUtilsKt.delete(db, TABLE_PLAN_INSTANCE_STATUS, where, whereArgs); case PLANINSTANCE_STATUS_SINGLE -> { - count = db.delete(TABLE_PLAN_INSTANCE_STATUS, + count = MoreDbUtilsKt.delete(db, TABLE_PLAN_INSTANCE_STATUS, String.format(Locale.ROOT, "%s = ? AND %s = ?", KEY_TEMPLATEID, KEY_INSTANCEID), new String[]{uri.getPathSegments().get(1), uri.getPathSegments().get(2)}); notifyChange(uri, false); return count; } - case EVENT_CACHE -> count = db.delete(TABLE_EVENT_CACHE, where, whereArgs); + case EVENT_CACHE -> count = MoreDbUtilsKt.delete(db, TABLE_EVENT_CACHE, where, whereArgs); case STALE_IMAGES_ID -> { //will fail if attachment is not stale segment = uri.getPathSegments().get(1); - count = db.delete(TABLE_ATTACHMENTS, KEY_ROWID + " = ?", new String[] { segment}); + count = MoreDbUtilsKt.delete(db, TABLE_ATTACHMENTS, KEY_ROWID + " = ?", new String[] { segment}); } - case STALE_IMAGES -> count = db.delete(TABLE_ATTACHMENTS, Companion.getSTALE_ATTACHMENT_SELECTION(), null); + case STALE_IMAGES -> count = MoreDbUtilsKt.delete(db, TABLE_ATTACHMENTS, Companion.getSTALE_ATTACHMENT_SELECTION(), null); - case CHANGES -> count = db.delete(TABLE_CHANGES, where, whereArgs); - case SETTINGS -> count = db.delete(TABLE_SETTINGS, where, whereArgs); + case CHANGES -> count = MoreDbUtilsKt.delete(db, TABLE_CHANGES, where, whereArgs); + case SETTINGS -> count = MoreDbUtilsKt.delete(db, TABLE_SETTINGS, where, whereArgs); case BUDGET_ID -> - count = db.delete(TABLE_BUDGETS, KEY_ROWID + " = " + uri.getLastPathSegment() + prefixAnd(where), whereArgs); - case PAYEES -> count = db.delete(TABLE_PAYEES, where, whereArgs); - case TAG_ID -> count = db.delete(TABLE_TAGS, + count = MoreDbUtilsKt.delete(db, TABLE_BUDGETS, KEY_ROWID + " = " + uri.getLastPathSegment() + prefixAnd(where), whereArgs); + case PAYEES -> count = MoreDbUtilsKt.delete(db, TABLE_PAYEES, where, whereArgs); + case TAG_ID -> count = MoreDbUtilsKt.delete(db, TABLE_TAGS, KEY_ROWID + " = " + uri.getLastPathSegment() + prefixAnd(where), whereArgs); case DUAL -> { if ("1".equals(uri.getQueryParameter(QUERY_PARAMETER_SYNC_END))) { @@ -1202,7 +1202,7 @@ public int delete(@NonNull Uri uri, String where, String[] whereArgs) { throw new IllegalArgumentException("Can only delete custom currencies"); } try { - count = db.delete(TABLE_CURRENCIES, String.format("%s = '%s'%s", KEY_CODE, + count = MoreDbUtilsKt.delete(db, TABLE_CURRENCIES, String.format("%s = '%s'%s", KEY_CODE, currency, prefixAnd(where)), whereArgs); } catch (SQLiteConstraintException e) { return 0; @@ -1210,26 +1210,26 @@ public int delete(@NonNull Uri uri, String where, String[] whereArgs) { } case TRANSACTIONS_TAGS -> { if (callerIsNotSyncAdapter(uri)) throw new IllegalArgumentException("Can only be called from sync adapter"); - count = db.delete(TABLE_TRANSACTIONS_TAGS, where, whereArgs); + count = MoreDbUtilsKt.delete(db, TABLE_TRANSACTIONS_TAGS, where, whereArgs); } case TEMPLATES_TAGS -> { - count = db.delete(TABLE_TEMPLATES_TAGS, where, whereArgs); + count = MoreDbUtilsKt.delete(db, TABLE_TEMPLATES_TAGS, where, whereArgs); } case ACCOUNTS_TAGS -> { - count = db.delete(TABLE_ACCOUNTS_TAGS, where, whereArgs); + count = MoreDbUtilsKt.delete(db, TABLE_ACCOUNTS_TAGS, where, whereArgs); } case DEBT_ID -> { - count = db.delete(TABLE_DEBTS, + count = MoreDbUtilsKt.delete(db, TABLE_DEBTS, KEY_ROWID + " = " + uri.getLastPathSegment() + prefixAnd(where), whereArgs); } case BANK_ID -> { - count = db.delete(TABLE_BANKS, + count = MoreDbUtilsKt.delete(db, TABLE_BANKS, KEY_ROWID + " = " + uri.getLastPathSegment() + prefixAnd(where), whereArgs); } case TRANSACTION_ID_ATTACHMENT_ID -> { String transactionId = uri.getPathSegments().get(2); String attachmentId = uri.getPathSegments().get(3); - count = db.delete(TABLE_TRANSACTION_ATTACHMENTS, + count = MoreDbUtilsKt.delete(db, TABLE_TRANSACTION_ATTACHMENTS, KEY_TRANSACTIONID + " = ? AND " + KEY_ATTACHMENT_ID + " = ?", new String[] { transactionId, attachmentId } ); @@ -1690,7 +1690,7 @@ public static ContentProviderOperation resumeChangeTrigger() { } static int resumeChangeTrigger(SupportSQLiteDatabase db) { - return db.delete(TABLE_SYNC_STATE, null, null); + return MoreDbUtilsKt.delete(db, TABLE_SYNC_STATE, null, null); } public static ContentProviderOperation pauseChangeTrigger() { diff --git a/sqlcrypt/src/main/java/org/totschnig/sqlcrypt/SQLiteOpenHelperFactory.kt b/sqlcrypt/src/main/java/org/totschnig/sqlcrypt/SQLiteOpenHelperFactory.kt index 5d6a89800e..b47e41178e 100644 --- a/sqlcrypt/src/main/java/org/totschnig/sqlcrypt/SQLiteOpenHelperFactory.kt +++ b/sqlcrypt/src/main/java/org/totschnig/sqlcrypt/SQLiteOpenHelperFactory.kt @@ -3,8 +3,8 @@ package org.totschnig.sqlcrypt import android.content.Context import android.os.Build import androidx.annotation.Keep -import net.sqlcipher.database.SQLiteDatabase -import net.sqlcipher.database.SupportFactory +import net.zetetic.database.sqlcipher.SQLiteDatabase +import net.zetetic.database.sqlcipher.SupportOpenHelperFactory import org.totschnig.myexpenses.di.SqlCryptProvider import org.totschnig.myexpenses.util.crypt.PassphraseRepository import java.io.File @@ -35,12 +35,16 @@ class SQLiteOpenHelperFactory : SqlCryptProvider { } .getPassphrase() - override fun provideEncryptedDatabase(context: Context) = SupportFactory(passPhrase(context)) + override fun provideEncryptedDatabase(context: Context): SupportOpenHelperFactory { + System.loadLibrary("sqlcipher") + return SupportOpenHelperFactory(passPhrase(context)) + } /** * https://commonsware.com/Room/pages/chap-sqlciphermgmt-001.html */ override fun decrypt(context: Context, encrypted: File, backupDb: File) { + System.loadLibrary("sqlcipher") val originalDb = SQLiteDatabase.openDatabase( encrypted.absolutePath, passPhrase(context), @@ -52,7 +56,6 @@ class SQLiteOpenHelperFactory : SqlCryptProvider { SQLiteDatabase.openOrCreateDatabase( backupDb.absolutePath, - "", null ).close() // create an empty database @@ -68,7 +71,6 @@ class SQLiteOpenHelperFactory : SqlCryptProvider { SQLiteDatabase.openOrCreateDatabase( backupDb.absolutePath, - "", null ).use { it.version = version @@ -79,14 +81,13 @@ class SQLiteOpenHelperFactory : SqlCryptProvider { * https://commonsware.com/Room/pages/chap-sqlciphermgmt-001.html */ override fun encrypt(context: Context, backupFile: File, currentDb: File) { - SQLiteDatabase.loadLibs(context) + System.loadLibrary("sqlcipher") if (currentDb.exists()) { if (!currentDb.delete()) throw IOException("File $currentDb exists and cannot be deleted.") } val version = SQLiteDatabase.openDatabase( backupFile.absolutePath, - "", null, SQLiteDatabase.OPEN_READWRITE ).use { @@ -96,6 +97,7 @@ class SQLiteOpenHelperFactory : SqlCryptProvider { SQLiteDatabase.openOrCreateDatabase( currentDb.absolutePath, passPhrase(context), + null, null ).use { db -> //language=text