React to changing profile availability

This commit is contained in:
MM20
2024-07-07 14:29:42 +02:00
parent 84f3d9f825
commit 5a28eb584e
29 changed files with 424 additions and 77 deletions
+1
View File
@@ -47,5 +47,6 @@ dependencies {
implementation(project(":core:base"))
implementation(project(":core:ktx"))
implementation(project(":core:compat"))
implementation(project(":core:profiles"))
}
@@ -5,7 +5,6 @@ import android.content.Context
import android.os.Bundle
import android.os.Process
import android.os.UserHandle
import de.mm20.launcher2.search.AppProfile
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.search.NullSerializer
import de.mm20.launcher2.search.SavableSearchable
@@ -15,7 +14,6 @@ class FakeApp: Application {
override val componentName: ComponentName = ComponentName(randomString(), randomString())
override val isSystemApp: Boolean = false
override val isSuspended: Boolean = false
override val profile: AppProfile = AppProfile.Personal
override val user: UserHandle = Process.myUserHandle()
override val versionName: String = "1.0"
override val canUninstall: Boolean = false
@@ -11,6 +11,8 @@ import android.os.Looper
import android.os.Process
import android.os.UserHandle
import de.mm20.launcher2.ktx.normalize
import de.mm20.launcher2.profiles.Profile
import de.mm20.launcher2.profiles.ProfileManager
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.search.SearchableRepository
import kotlinx.collections.immutable.ImmutableList
@@ -20,7 +22,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -38,6 +42,7 @@ interface AppRepository : SearchableRepository<Application> {
internal class AppRepositoryImpl(
private val context: Context,
private val profileManager: ProfileManager,
) : AppRepository {
private val scope = CoroutineScope(Dispatchers.Default + Job())
@@ -45,11 +50,8 @@ internal class AppRepositoryImpl(
context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
private val installedApps = MutableStateFlow<List<LauncherApp>>(emptyList())
private val suspendedPackages = MutableStateFlow<List<String>>(emptyList())
private val profiles: List<UserHandle>
get() = launcherApps.profiles.takeIf { it.isNotEmpty() } ?: listOf(Process.myUserHandle())
private val profiles = profileManager.activeProfiles
private val mutex = Mutex()
@@ -163,21 +165,42 @@ internal class AppRepositoryImpl(
}
}, Handler(Looper.getMainLooper()))
scope.launch {
profiles.runningFold<List<Profile>, Pair<List<Profile>?, List<Profile>?>>(null to null) { acc, value ->
acc.second to value
}.collectLatest { (prev, curr) ->
if (curr == null) return@collectLatest
if (prev == null) {
curr.forEach { addProfile(it) }
} else {
val added = curr - prev
val removed = prev - curr
added.forEach { addProfile(it) }
removed.forEach { removeProfile(it) }
}
}
}
}
private suspend fun addProfile(profile: Profile) {
mutex.withLock {
val apps = installedApps.value.toMutableList()
apps.addAll(getApplications(null, profile.userHandle))
installedApps.value = apps
}
}
private fun removeProfile(profile: Profile) {
scope.launch {
mutex.withLock {
val apps = profiles.map { p ->
try {
launcherApps.getActivityList(null, p).mapNotNull { getApplication(it) }
} catch (e: SecurityException) {
emptyList()
}
}.flatten()
val apps = installedApps.value.toMutableList()
apps.removeAll { it.user == profile.userHandle }
installedApps.value = apps
}
}
}
private fun getApplications(packageName: String, userHandle: UserHandle): List<LauncherApp> {
private fun getApplications(packageName: String?, userHandle: UserHandle): List<LauncherApp> {
if (packageName == context.packageName) return emptyList()
return try {
@@ -27,7 +27,6 @@ import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.icons.TransparentLayer
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.AppProfile
import de.mm20.launcher2.search.Application
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.StoreLink
@@ -61,9 +60,6 @@ internal data class LauncherApp(
private val isMainProfile = launcherActivityInfo.user == Process.myUserHandle()
override val profile: AppProfile
get() = if (isMainProfile) AppProfile.Personal else AppProfile.Work
override val isSystemApp: Boolean = launcherActivityInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0
override val canUninstall: Boolean
@@ -8,7 +8,7 @@ import org.koin.core.qualifier.named
import org.koin.dsl.module
val applicationsModule = module {
factory<SearchableRepository<Application>>(named<Application>()) { AppRepositoryImpl(androidContext()) }
factory<AppRepository> { AppRepositoryImpl(androidContext()) }
factory<SearchableRepository<Application>>(named<Application>()) { get<AppRepository>() }
single<AppRepository> { AppRepositoryImpl(androidContext(), get()) }
factory<SearchableDeserializer>(named(LauncherApp.Domain)) { LauncherAppDeserializer(androidContext()) }
}
@@ -5,10 +5,8 @@ import android.content.IntentSender
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.graphics.drawable.Drawable
import android.os.Process
import androidx.core.content.getSystemService
import de.mm20.launcher2.ktx.romanize
import de.mm20.launcher2.search.AppProfile
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import java.text.Collator
@@ -17,7 +15,6 @@ class AppShortcutConfigActivity(
private val launcherActivityInfo: LauncherActivityInfo,
): Comparable<AppShortcutConfigActivity> {
val label = launcherActivityInfo.label.toString()
val profile: AppProfile = if (launcherActivityInfo.user == Process.myUserHandle()) AppProfile.Personal else AppProfile.Work
fun getIcon(context: Context): Flow<Drawable?> = flow {
val icon = launcherActivityInfo.getIcon(context.resources.displayMetrics.densityDpi)
@@ -18,7 +18,6 @@ import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.AppProfile
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.SearchableSerializer
import kotlinx.coroutines.Dispatchers
@@ -69,9 +68,6 @@ internal data class LauncherShortcut(
override val preferDetailsOverLaunch: Boolean = false
private val isMainProfile = launcherShortcut.userHandle == Process.myUserHandle()
override val profile: AppProfile
get() = if (isMainProfile) AppProfile.Personal else AppProfile.Work
override val key: String
get() = if (isMainProfile) {
@@ -11,7 +11,6 @@ import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getDrawableOrNull
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.AppProfile
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.SearchableSerializer
@@ -26,9 +25,6 @@ internal data class LegacyShortcut(
override val domain = Domain
override val key: String = "$domain://${intent.toUri(0)}"
override val profile: AppProfile
get() = AppProfile.Personal
override fun overrideLabel(label: String): LegacyShortcut {
return this.copy(labelOverride = label)
}
@@ -6,11 +6,9 @@ import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Process
import android.os.UserHandle
import android.os.UserManager
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.search.AppProfile
import de.mm20.launcher2.search.AppShortcut
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.SearchableSerializer
@@ -67,8 +65,6 @@ internal class UnavailableShortcut(
}
override val isUnavailable: Boolean = true
override val profile: AppProfile
get() = if (isMainProfile) AppProfile.Personal else AppProfile.Work
companion object {
internal operator fun invoke(context: Context, id: String, packageName: String, user: UserHandle, userSerial: Long): UnavailableShortcut? {