Reorganize Searchable data types
This commit is contained in:
@@ -45,7 +45,6 @@ dependencies {
|
||||
|
||||
implementation(libs.commons.text)
|
||||
|
||||
implementation(project(":search"))
|
||||
implementation(project(":base"))
|
||||
implementation(project(":preferences"))
|
||||
implementation(project(":ktx"))
|
||||
|
||||
@@ -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
|
||||
)
|
||||
Reference in New Issue
Block a user