[db] Handle users with a thousand apps or more installed in AppDao

This commit is contained in:
Torsten Grote 2023-01-02 16:52:27 -03:00 committed by Hans-Christoph Steiner
parent a474198c34
commit 7a2d0e31f3
2 changed files with 78 additions and 1 deletions

View File

@ -20,6 +20,7 @@ import kotlin.random.Random
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.fail
@ -426,6 +427,45 @@ internal class AppListItemsTest : AppTest() {
}
}
@Test
fun testGetInstalledAppListItemsMaxVars() {
// insert an app
val repoId = repoDao.insertOrReplace(getRandomRepo())
appDao.insert(repoId, packageName, app1, locales)
val packageInfoCreator = { name: String ->
@Suppress("DEPRECATION")
PackageInfo().apply {
packageName = name
versionName = name
versionCode = Random.nextInt(1, Int.MAX_VALUE)
}
}
val packageInfo = packageInfoCreator(packageName)
// sqlite has a maximum number of 999 variables that can be used in a query
val listPackageInfo = listOf(packageInfo)
val packageInfoOk = MutableList(999) { packageInfoCreator(getRandomString()) }
val packageInfoNotOk1 = MutableList(1000) { packageInfoCreator(getRandomString()) }
val packageInfoNotOk2 = MutableList(5000) { packageInfoCreator(getRandomString()) }
// app gets returned no matter how many packages are installed
every { pm.getInstalledPackages(0) } returns packageInfoOk + listPackageInfo
assertEquals(1, appDao.getInstalledAppListItems(pm).getOrFail().size)
every { pm.getInstalledPackages(0) } returns packageInfoNotOk1 + listPackageInfo
assertEquals(1, appDao.getInstalledAppListItems(pm).getOrFail().size)
every { pm.getInstalledPackages(0) } returns packageInfoNotOk2 + listPackageInfo
assertEquals(1, appDao.getInstalledAppListItems(pm).getOrFail().size)
// ensure they have version info set
every { pm.getInstalledPackages(0) } returns packageInfoOk + listPackageInfo
assertNotNull(appDao.getInstalledAppListItems(pm).getOrFail()[0].installedVersionName)
every { pm.getInstalledPackages(0) } returns packageInfoNotOk1 + listPackageInfo
assertNotNull(appDao.getInstalledAppListItems(pm).getOrFail()[0].installedVersionName)
every { pm.getInstalledPackages(0) } returns packageInfoNotOk2 + listPackageInfo
assertNotNull(appDao.getInstalledAppListItems(pm).getOrFail()[0].installedVersionName)
}
/**
* Runs the given block on all getAppListItems* methods.
* Uses category "A" as all apps should be in that.

View File

@ -8,6 +8,7 @@ import androidx.core.content.pm.PackageInfoCompat
import androidx.core.os.ConfigurationCompat.getLocales
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.map
import androidx.room.Dao
import androidx.room.Insert
@ -480,6 +481,9 @@ internal interface AppDaoInt : AppDao {
ORDER BY localizedName COLLATE NOCASE ASC""")
fun getAppListItemsByName(category: String): LiveData<List<AppListItem>>
/**
* Warning: Can not be called with more than 999 [packageNames].
*/
@Transaction
@SuppressWarnings(CURSOR_MISMATCH) // no anti-features needed here
@Query("""SELECT repoId, packageName, localizedName, localizedSummary, app.lastUpdated,
@ -497,7 +501,40 @@ internal interface AppDaoInt : AppDao {
val installedPackages = packageManager.getInstalledPackages(0)
.associateBy { packageInfo -> packageInfo.packageName }
val packageNames = installedPackages.keys.toList()
return getAppListItems(packageNames).map(packageManager, installedPackages)
return if (packageNames.size <= 999) {
getAppListItems(packageNames).map(packageManager, installedPackages)
} else {
AppListLiveData().apply {
packageNames.chunked(999) { addSource(getAppListItems(it)) }
}.map(packageManager, installedPackages)
}
}
private class AppListLiveData : MediatorLiveData<List<AppListItem>>() {
private val list = ArrayList<LiveData<List<AppListItem>>>()
/**
* Adds the given [liveData] and updates [getValue] with a union of all lists
* once all added [liveData]s changed to a non-null list value.
*/
fun addSource(liveData: LiveData<List<AppListItem>>) {
list.add(liveData)
addSource(liveData) {
var shouldUpdate = true
val result = list.flatMap {
it.value ?: run {
shouldUpdate = false
emptyList()
}
}
if (shouldUpdate) value = result.sortedWith { i1, i2 ->
// we need to re-sort the result, because each liveData is only sorted in itself
val n1 = i1.name ?: ""
val n2 = i2.name ?: ""
n1.compareTo(n2, ignoreCase = true)
}
}
}
}
//