Reorganize Searchable data types

This commit is contained in:
MM20
2022-10-12 20:36:39 +02:00
parent a01b0aa03d
commit bcda89c211
98 changed files with 817 additions and 738 deletions
@@ -12,17 +12,18 @@ import android.os.Process
import android.os.UserHandle
import android.util.Log
import de.mm20.launcher2.ktx.normalize
import de.mm20.launcher2.search.data.Application
import de.mm20.launcher2.search.SearchableRepository
import de.mm20.launcher2.search.data.LauncherApp
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.FuzzyScore
import java.util.*
interface AppRepository {
fun search(query: String): Flow<List<Application>>
fun getAllInstalledApps(): Flow<List<Application>>
interface AppRepository: SearchableRepository<LauncherApp> {
fun getAllInstalledApps(): Flow<List<LauncherApp>>
fun getSuspendedPackages(): Flow<List<String>>
}
@@ -140,11 +141,11 @@ internal class AppRepositoryImpl(
return LauncherApp(context, launcherActivityInfo)
}
override fun search(query: String): Flow<List<Application>> = channelFlow {
override fun search(query: String): Flow<ImmutableList<LauncherApp>> = channelFlow {
installedApps.collectLatest { apps ->
withContext(Dispatchers.Default) {
val appResults = mutableListOf<Application>()
val appResults = mutableListOf<LauncherApp>()
if (query.isEmpty()) {
appResults.addAll(apps)
} else {
@@ -158,12 +159,12 @@ internal class AppRepositoryImpl(
appResults.sort()
send(appResults)
send(appResults.toImmutableList())
}
}
}
override fun getAllInstalledApps(): Flow<List<Application>> {
override fun getAllInstalledApps(): Flow<List<LauncherApp>> {
return installedApps
}
@@ -174,7 +175,7 @@ internal class AppRepositoryImpl(
fuzzyScore.fuzzyScore(normalizedLabel, query.normalize()) >= query.length * 1.5
}
private fun getActivityByComponentName(componentName: ComponentName?): Application? {
private fun getActivityByComponentName(componentName: ComponentName?): LauncherApp? {
componentName ?: return null
val intent = Intent().setComponent(componentName)
val lai = launcherApps.resolveActivity(intent, Process.myUserHandle())
@@ -1,73 +0,0 @@
package de.mm20.launcher2.search.data
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.drawable.BitmapDrawable
import androidx.core.content.ContextCompat
import de.mm20.launcher2.applications.R
import de.mm20.launcher2.icons.*
class AppInstallation(
val session: PackageInstaller.SessionInfo
) : Application(
label = session.appLabel?.toString() ?: "",
`package` = session.appPackageName ?: "",
activity = "",
flags = 0,
version = null
) {
override val key: String
get() = "installer://${session.installerPackageName}:${session.appPackageName}"
override fun getLaunchIntent(context: Context): Intent? {
return session.createDetailsIntent()
}
override fun getPlaceholderIcon(context: Context): StaticLauncherIcon {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!,
scale = 0.5f,
color = 0xFF757575.toInt()
),
backgroundLayer = ColorLayer(0xFF757575.toInt())
)
}
override suspend fun loadIcon(
context: Context,
size: Int,
themed: Boolean,
): LauncherIcon {
val icon = session.appIcon ?: return getPlaceholderIcon(context)
val foreground = BitmapDrawable(context.resources, icon)
foreground.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply {
setSaturation(0f)
})
return StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = foreground,
),
backgroundLayer = ColorLayer(0xFF757575.toInt())
)
}
override fun getStoreDetails(context: Context): StoreLink? {
return getStoreLinkForInstaller(session.installerPackageName, `package`)
}
companion object {
fun search(context: Context): List<AppInstallation> {
val installer = context.packageManager.packageInstaller
val sessions = installer.allSessions
val results = sessions.mapNotNull {
if (it.appLabel != null && it.isActive) AppInstallation(it) else null
}
return results
}
}
}
@@ -4,20 +4,17 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.os.Build
import android.os.Process
import android.os.UserManager
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import org.json.JSONObject
import org.koin.core.component.KoinComponent
class LauncherAppSerializer : SearchableSerializer {
override fun serialize(searchable: Searchable): String {
override fun serialize(searchable: PinnableSearchable): String {
searchable as LauncherApp
val json = JSONObject()
json.put("package", searchable.`package`)
@@ -31,7 +28,7 @@ class LauncherAppSerializer : SearchableSerializer {
}
class LauncherAppDeserializer(val context: Context) : SearchableDeserializer {
override fun deserialize(serialized: String): Searchable? {
override fun deserialize(serialized: String): PinnableSearchable? {
val json = JSONObject(serialized)
val launcherApps = context.getSystemService<LauncherApps>()!!
val userManager = context.getSystemService<UserManager>()!!
@@ -1,96 +0,0 @@
package de.mm20.launcher2.search.data
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import androidx.core.content.ContextCompat
import de.mm20.launcher2.applications.R
import de.mm20.launcher2.compat.PackageManagerCompat
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import org.json.JSONObject
abstract class Application(
override val label: String,
val `package`: String,
val activity: String,
val flags: Int,
val version: String?,
) : Searchable() {
override fun serialize(): String {
val json = JSONObject()
json.put("package", `package`)
json.put("activity", activity)
return json.toString()
}
override fun getLaunchIntent(context: Context): Intent? {
val intent = Intent()
intent.component = ComponentName(`package`, activity)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
return intent
}
override fun getPlaceholderIcon(context: Context): StaticLauncherIcon {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!,
scale = 0.5f,
color = 0xff3dda84.toInt(),
),
backgroundLayer = ColorLayer(0xff3dda84.toInt())
)
}
open fun getStoreDetails(context: Context): StoreLink? {
val pm = context.packageManager
return try {
val installSourceInfo = PackageManagerCompat.getInstallSource(pm, `package`)
getStoreLinkForInstaller(installSourceInfo.initiatingPackageName, `package`)
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
override val key: String
get() = "app://$`package`:$activity"
companion object {
internal fun getStoreLinkForInstaller(
installerPackage: String?,
packageName: String?
): StoreLink? {
if (packageName == null) return null
return when (installerPackage) {
"de.amazon.mShop.android", "com.amazon.venezia" -> {
StoreLink(
"Amazon App Shop",
"http://www.amazon.com/gp/mas/dl/android?p=${packageName}"
)
}
"com.android.vending" -> {
StoreLink(
"Google Play Store",
"https://play.google.com/store/apps/details?id=${packageName}"
)
}
"org.fdroid.fdroid", "com.aurora.adroid" -> {
StoreLink(
"F-Droid",
"https://f-droid.org/packages/${packageName}"
)
}
else -> null
}
}
}
}
data class StoreLink(
val label: String,
val url: String
)
@@ -10,37 +10,65 @@ import android.graphics.drawable.AdaptiveIconDrawable
import android.os.Bundle
import android.os.Process
import android.os.UserHandle
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import de.mm20.launcher2.applications.R
import de.mm20.launcher2.compat.PackageManagerCompat
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.PinnableSearchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
* An [Application] based on an [android.content.pm.LauncherActivityInfo]
*/
class LauncherApp(
context: Context,
val launcherActivityInfo: LauncherActivityInfo
) : Application(
label = launcherActivityInfo.label.toString(),
`package` = launcherActivityInfo.applicationInfo.packageName,
activity = launcherActivityInfo.name,
flags = launcherActivityInfo.applicationInfo.flags,
version = getPackageVersionName(context, launcherActivityInfo.applicationInfo.packageName),
) {
data class LauncherApp(
val launcherActivityInfo: LauncherActivityInfo,
override val label: String,
val `package`: String,
val activity: String,
val flags: Int,
val version: String?,
internal val userSerialNumber: Long,
override val labelOverride: String? = null,
) : PinnableSearchable {
constructor(context: Context, launcherActivityInfo: LauncherActivityInfo): this(
launcherActivityInfo,
label = launcherActivityInfo.label.toString(),
`package` = launcherActivityInfo.applicationInfo.packageName,
activity = launcherActivityInfo.name,
flags = launcherActivityInfo.applicationInfo.flags,
version = getPackageVersionName(context, launcherActivityInfo.applicationInfo.packageName),
userSerialNumber = launcherActivityInfo.user.getSerialNumber(context)
)
internal val userSerialNumber: Long = launcherActivityInfo.user.getSerialNumber(context)
val isMainProfile = launcherActivityInfo.user == Process.myUserHandle()
override val domain: String = Domain
override val preferDetailsOverLaunch: Boolean = false
override fun overrideLabel(label: String): LauncherApp {
return this.copy(labelOverride = label)
}
override val key: String
get() = if (isMainProfile) "app://$`package`:$activity" else "app://$`package`:$activity:${userSerialNumber}"
get() = if (isMainProfile) "${domain}://$`package`:$activity" else "${domain}://$`package`:$activity:${userSerialNumber}"
fun getUser(): UserHandle? {
return launcherActivityInfo.user
}
override fun getPlaceholderIcon(context: Context): StaticLauncherIcon {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!,
scale = 0.5f,
color = 0xff3dda84.toInt(),
),
backgroundLayer = ColorLayer(0xff3dda84.toInt())
)
}
override suspend fun loadIcon(
context: Context,
size: Int,
@@ -107,7 +135,46 @@ class LauncherApp(
return true
}
fun getStoreDetails(context: Context): StoreLink? {
val pm = context.packageManager
return try {
val installSourceInfo = PackageManagerCompat.getInstallSource(pm, `package`)
getStoreLinkForInstaller(installSourceInfo.initiatingPackageName, `package`)
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
companion object {
private fun getStoreLinkForInstaller(
installerPackage: String?,
packageName: String?
): StoreLink? {
if (packageName == null) return null
return when (installerPackage) {
"de.amazon.mShop.android", "com.amazon.venezia" -> {
StoreLink(
"Amazon App Shop",
"http://www.amazon.com/gp/mas/dl/android?p=${packageName}"
)
}
"com.android.vending" -> {
StoreLink(
"Google Play Store",
"https://play.google.com/store/apps/details?id=${packageName}"
)
}
"org.fdroid.fdroid", "com.aurora.adroid" -> {
StoreLink(
"F-Droid",
"https://f-droid.org/packages/${packageName}"
)
}
else -> null
}
}
fun getPackageVersionName(context: Context, packageName: String): String? {
return try {
@@ -116,5 +183,12 @@ class LauncherApp(
null
}
}
const val Domain = "app"
}
}
}
data class StoreLink(
val label: String,
val url: String
)