[db] Handle users with a thousand apps or more installed in AppDao
This commit is contained in:
parent
a474198c34
commit
7a2d0e31f3
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue