Initial commit

This commit is contained in:
MM20
2021-09-18 23:37:52 +02:00
commit 749e4e3073
938 changed files with 50475 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+54
View File
@@ -0,0 +1,54 @@
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-android-extensions")
}
android {
compileSdk = sdk.versions.compileSdk.get().toInt()
defaultConfig {
minSdk = sdk.versions.minSdk.get().toInt()
targetSdk = sdk.versions.targetSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation(libs.bundles.kotlin)
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.bundles.androidx.lifecycle)
implementation(project(":search"))
implementation(project(":base"))
implementation(project(":icons"))
implementation(project(":preferences"))
implementation(project(":ktx"))
implementation(project(":badges"))
implementation(project(":hiddenitems"))
implementation(project(":compat"))
}
View File
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.kts.kts.kts.kts.kts.kts.kts.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.mm20.launcher2.applications">
/
</manifest>
@@ -0,0 +1,227 @@
package de.mm20.launcher2.applications
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.content.pm.PackageInstaller
import android.content.pm.ShortcutInfo
import android.os.Process
import android.os.UserHandle
import android.util.Log
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.BadgeProvider
import de.mm20.launcher2.hiddenitems.HiddenItemsRepository
import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.search.BaseSearchableRepository
import de.mm20.launcher2.search.SearchRepository
import de.mm20.launcher2.search.data.AppInstallation
import de.mm20.launcher2.search.data.Application
import de.mm20.launcher2.search.data.LauncherApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class AppRepository private constructor(val context: Context) : BaseSearchableRepository() {
private val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val applications = MediatorLiveData<List<Application>>()
private val installedApps = MutableLiveData<List<Application>>(emptyList())
private val installations = MutableLiveData<MutableList<AppInstallation>>(mutableListOf())
private val hiddenItemKeys = HiddenItemsRepository.getInstance(context).hiddenItemsKeys
private val installingPackages = mutableMapOf<Int, String>()
private val profiles: List<UserHandle> = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
launcherApps.profiles.takeIf { it.isNotEmpty() } ?: listOf(Process.myUserHandle())
} else {
listOf(Process.myUserHandle())
}
init {
applications.addSource(installedApps) {
launch { updateAppsForDisplay() }
}
applications.addSource(installations) {
launch { updateAppsForDisplay() }
}
applications.addSource(hiddenItemKeys) {
launch { updateAppsForDisplay() }
}
launcherApps.registerCallback(object : LauncherApps.Callback() {
override fun onPackagesUnavailable(packageNames: Array<out String>, user: UserHandle, replacing: Boolean) {
installedApps.value = installedApps.value?.filter { !packageNames.contains(it.`package`) }
}
override fun onPackageChanged(packageName: String, user: UserHandle) {
val apps = installedApps.value?.toMutableList() ?: return
apps.removeAll { packageName == it.`package` }
apps.addAll(getApplications(packageName))
installedApps.value = apps
}
override fun onPackagesAvailable(packageNames: Array<out String>, user: UserHandle, replacing: Boolean) {
val apps = installedApps.value?.toMutableList() ?: return
for (packageName in packageNames) {
apps.addAll(getApplications(packageName))
}
installedApps.value = apps
}
override fun onPackageAdded(packageName: String, user: UserHandle) {
Log.d("MM20", "App installed: $packageName")
val apps = installedApps.value?.toMutableList() ?: return
apps.addAll(getApplications(packageName))
installedApps.value = apps
}
override fun onPackageRemoved(packageName: String, user: UserHandle) {
installedApps.value = installedApps.value?.filter { packageName != (it.`package`) }
}
override fun onShortcutsChanged(packageName: String, shortcuts: MutableList<ShortcutInfo>, user: UserHandle) {
super.onShortcutsChanged(packageName, shortcuts, user)
onPackageChanged(packageName, user)
}
override fun onPackagesSuspended(packageNames: Array<out String>?, user: UserHandle?) {
super.onPackagesSuspended(packageNames, user)
packageNames?.forEach {
BadgeProvider.getInstance(context).setBadge("app://$it", Badge(iconRes = R.drawable.ic_badge_suspended))
}
}
override fun onPackagesUnsuspended(packageNames: Array<out String>?, user: UserHandle?) {
super.onPackagesUnsuspended(packageNames, user)
packageNames?.forEach {
BadgeProvider.getInstance(context).removeBadge("app://$it")
}
}
})
val packageInstaller = context.packageManager.packageInstaller
packageInstaller.registerSessionCallback(object : PackageInstaller.SessionCallback() {
override fun onProgressChanged(sessionId: Int, progress: Float) {
val session = packageInstaller.getSessionInfo(sessionId) ?: return
val pkg = session.appPackageName ?: return
BadgeProvider.getInstance(context).updateBadge("app://$pkg", Badge(progress = progress))
}
override fun onActiveChanged(sessionId: Int, active: Boolean) {
if (active) onCreated(sessionId)
else onFinished(sessionId, false)
}
override fun onFinished(sessionId: Int, success: Boolean) {
val pkg = installingPackages[sessionId]
installingPackages.remove(sessionId)
val key = "app://$pkg"
val badge = BadgeProvider.getInstance(context).getBadge(key)?.apply { progress = null }
?: Badge()
BadgeProvider.getInstance(context).setBadge(key, badge)
val inst = installations.value ?: return
inst.removeAll {
it.session.sessionId == sessionId
}
installations.postValue(inst)
}
override fun onBadgingChanged(sessionId: Int) {
val inst = installations.value ?: mutableListOf()
inst.removeAll {
if (it.session.sessionId == sessionId) {
IconRepository.getInstance(context).removeIconFromCache(it)
true
} else false
}
onCreated(sessionId)
}
override fun onCreated(sessionId: Int) {
val session = packageInstaller.getSessionInfo(sessionId) ?: return
installingPackages[sessionId] = session.appPackageName ?: return
if (installedApps.value?.any { it.`package` == session.appPackageName } == true) return
if (session.appLabel.isNullOrBlank() || !session.isActive) return
val appInstallation = AppInstallation(session)
val inst = installations.value ?: mutableListOf()
inst.add(appInstallation)
installations.postValue(inst)
}
})
val apps = profiles.map { p -> launcherApps.getActivityList(null, p).mapNotNull { getApplication(it, p) } }.flatten()
installedApps.value = apps
}
override suspend fun search(query: String) {
updateAppsForDisplay()
}
private suspend fun updateAppsForDisplay() {
val query = SearchRepository.getInstance().currentQuery.value ?: ""
val componentName = ComponentName.unflattenFromString(query)
val apps = withContext(Dispatchers.Default) {
val hiddenItems = hiddenItemKeys.value ?: emptyList()
val installed = installedApps.value ?: emptyList()
val installing = installations.value ?: emptyList<AppInstallation>()
val results = mutableListOf<Application>()
results.addAll(installed)
results.addAll(installing)
if (query.isNotEmpty()) {
results.removeAll { !it.label.contains(query, ignoreCase = true) }
getActivityByComponentName(componentName)?.let { results.add(it) }
}
results.removeAll { hiddenItems.contains(it.key) }
results.sort()
results
}
applications.value = apps
}
private fun getActivityByComponentName(componentName: ComponentName?): Application? {
if (!LauncherPreferences.instance.searchActivities) return null
componentName ?: return null
val intent = Intent().setComponent(componentName)
val lai = launcherApps.resolveActivity(intent, Process.myUserHandle())
return lai?.let {
LauncherApp(context, lai)
}
}
private fun getApplication(launcherActivityInfo: LauncherActivityInfo, profile: UserHandle): Application? {
if (launcherActivityInfo.applicationInfo.packageName == context.packageName && !context.packageName.endsWith(".debug")) return null
return LauncherApp(context, launcherActivityInfo)
}
private fun getApplications(packageName: String): List<Application> {
if (packageName == context.packageName) return emptyList()
return profiles.map { p -> launcherApps.getActivityList(packageName, p).mapNotNull { getApplication(it, p) } }.flatten()
}
companion object {
private lateinit var instance: AppRepository
fun getInstance(context: Context): AppRepository {
if (!::instance.isInitialized) instance = AppRepository(context.applicationContext)
return instance
}
}
}
@@ -0,0 +1,13 @@
package de.mm20.launcher2.applications
import android.app.Application as AndroidApp
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import de.mm20.launcher2.applications.AppRepository
import de.mm20.launcher2.search.data.Application
class AppViewModel(app: AndroidApp): AndroidViewModel(app) {
private val repository = AppRepository.getInstance(app)
val applications: LiveData<List<Application>> = repository.applications
}
@@ -0,0 +1,67 @@
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 android.graphics.drawable.ColorDrawable
import androidx.core.content.ContextCompat
import de.mm20.launcher2.applications.R
import de.mm20.launcher2.icons.LauncherIcon
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 val badgeKey: String
get() = "app://${session.appPackageName}"
override fun getLaunchIntent(context: Context): Intent? {
return session.createDetailsIntent()
}
override fun getPlaceholderIcon(context: Context): LauncherIcon {
return LauncherIcon(
foreground = ContextCompat.getDrawable(context, R.drawable.ic_app_placeholder)!!,
background = ColorDrawable(ContextCompat.getColor(context, R.color.grey)),
foregroundScale = 0.5f)
}
override suspend fun loadIconAsync(context: Context, size: Int): LauncherIcon? {
val icon = session.appIcon ?: return getPlaceholderIcon(context)
val foreground = BitmapDrawable(context.resources, icon)
foreground.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply {
setSaturation(0f)
})
return LauncherIcon(
foreground = foreground,
background = ColorDrawable(ContextCompat.getColor(context, R.color.grey))
)
}
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
}
}
}
@@ -0,0 +1,161 @@
package de.mm20.launcher2.search.data
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.ColorDrawable
import android.os.*
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import de.mm20.launcher2.applications.R
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.badges.BadgeProvider
import de.mm20.launcher2.graphics.BadgeDrawable
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.preferences.LauncherPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.lang.IllegalStateException
@RequiresApi(Build.VERSION_CODES.N_MR1)
class AppShortcut(
context: Context,
val launcherShortcut: ShortcutInfo,
val appName: String
) : Searchable() {
override val label: String
get() = launcherShortcut.shortLabel?.toString() ?: ""
private val userSerialNumber: Long = launcherShortcut.userHandle.getSerialNumber(context)
private val isMainProfile = launcherShortcut.userHandle == Process.myUserHandle()
override val key: String
get() = if (isMainProfile) {
"shortcut://${launcherShortcut.`package`}/${launcherShortcut.id}"
} else {
"shortcut://${launcherShortcut.`package`}/${launcherShortcut.id}:${userSerialNumber}"
}
override val badgeKey: String
get() {
return if (LauncherPreferences.instance.shortcutBadges) {
if (isMainProfile) "shortcut://${launcherShortcut.activity?.flattenToShortString()}" else "profile://$userSerialNumber"
} else {
"null"
}
}
override fun serialize(): String {
return jsonObjectOf(
"packagename" to launcherShortcut.`package`,
"id" to launcherShortcut.id,
"user" to userSerialNumber,
).toString()
}
override fun getLaunchIntent(context: Context): Intent? {
return launcherShortcut.intent
}
override fun launch(context: Context, options: Bundle?): Boolean {
val launcherApps = context.getSystemService<LauncherApps>()!!
try {
launcherApps.startShortcut(launcherShortcut, null, options)
} catch (e: IllegalStateException) {
return false
}
return true
}
override fun getPlaceholderIcon(context: Context): LauncherIcon {
return LauncherIcon(
foreground = ContextCompat.getDrawable(context, R.drawable.ic_app_placeholder)!!,
background = ColorDrawable(ContextCompat.getColor(context, R.color.green)),
foregroundScale = 0.5f)
}
override suspend fun loadIconAsync(context: Context, size: Int): LauncherIcon? {
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val icon = launcherApps.getShortcutIconDrawable(launcherShortcut, context.resources.displayMetrics.densityDpi)
icon ?: return null
if (isAtLeastApiLevel(Build.VERSION_CODES.O) && icon is AdaptiveIconDrawable) {
return LauncherIcon(
foreground = icon.foreground,
background = icon.background,
foregroundScale = 1.5f,
backgroundScale = 1.5f
)
}
return LauncherIcon(
foreground = icon,
foregroundScale = 1f,
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
)
}
companion object {
fun deserialize(context: Context, serialized: String): AppShortcut? {
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
if (!launcherApps.hasShortcutHostPermission()) return null
else {
val json = JSONObject(serialized)
val packageName = json.getString("packagename")
val id = json.getString("id")
val userSerial = json.optLong("user")
val query = LauncherApps.ShortcutQuery()
query.setPackage(packageName)
query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC or
LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST or
LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED)
query.setShortcutIds(mutableListOf(id))
val userManager = context.getSystemService<UserManager>()!!
val user = userManager.getUserForSerialNumber(userSerial) ?: Process.myUserHandle()
val shortcuts = try {
launcherApps.getShortcuts(query, user)
} catch (e: IllegalStateException) {
return null
}
val pm = context.packageManager
val appName = try {
pm.getApplicationInfo(packageName, 0).loadLabel(pm).toString()
} catch (e: PackageManager.NameNotFoundException) {
return null
}
if (shortcuts == null || shortcuts.isEmpty()) return null else {
GlobalScope.launch {
val activity = shortcuts[0].activity
withContext(Dispatchers.IO) {
val icon = try {
context.packageManager.getActivityIcon(activity
?: return@withContext)
} catch (e: PackageManager.NameNotFoundException) {
return@withContext
}
val badge = Badge(icon = BadgeDrawable(context, icon))
BadgeProvider.getInstance(context).setBadge("shortcut://${activity.flattenToShortString()}", badge)
}
}
return AppShortcut(
context = context,
launcherShortcut = shortcuts[0],
appName = appName
)
}
}
}
}
}
@@ -0,0 +1,88 @@
package de.mm20.launcher2.search.data
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.util.Log
import androidx.core.content.ContextCompat
import de.mm20.launcher2.applications.R
import de.mm20.launcher2.compat.PackageManagerCompat
import de.mm20.launcher2.icons.LauncherIcon
import org.json.JSONObject
abstract class Application(
override val label: String,
val `package`: String,
val activity: String,
val flags: Int,
val version: String?,
val shortcuts: List<AppShortcut> = emptyList()
) : Searchable() {
override val badgeKey: String
get() = "app://${`package`}"
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): LauncherIcon {
return LauncherIcon(
foreground = ContextCompat.getDrawable(context, R.drawable.ic_app_placeholder)!!,
background = ColorDrawable(ContextCompat.getColor(context, R.color.lightgreen)),
foregroundScale = 0.5f
)
}
open fun getStoreDetails(context: Context): StoreLink? {
val pm = context.packageManager
val installSourceInfo = PackageManagerCompat.getInstallSource(pm, `package`)
return getStoreLinkForInstaller(installSourceInfo.initiatingPackageName, `package`)
}
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
)
@@ -0,0 +1,123 @@
package de.mm20.launcher2.search.data
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.os.*
import androidx.core.content.getSystemService
import de.mm20.launcher2.icons.IconPackManager
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.getSerialNumber
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
/**
* An [Application] based on an [android.content.pm.LauncherActivityInfo]
*/
class LauncherApp(
context: Context,
private 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),
shortcuts = run {
val appShortcuts = mutableListOf<AppShortcut>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
val launcherApps = context.getSystemService<LauncherApps>()!!
if (!launcherApps.hasShortcutHostPermission()) return@run appShortcuts
val query = LauncherApps.ShortcutQuery()
.setPackage(launcherActivityInfo.applicationInfo.packageName)
.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC or LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST)
val shortcuts = try {
launcherApps.getShortcuts(query, launcherActivityInfo.user)
} catch (e: IllegalStateException) {
emptyList<ShortcutInfo>()
}
appShortcuts.addAll(shortcuts?.map { AppShortcut(context, it, launcherActivityInfo.label.toString()) }
?: emptyList())
}
appShortcuts
}
) {
private val userSerialNumber: Long = launcherActivityInfo.user.getSerialNumber(context)
private val isMainProfile = launcherActivityInfo.user == Process.myUserHandle()
override val badgeKey: String = if (isMainProfile) "app://${`package`}" else "profile://$userSerialNumber"
override val key: String
get() = if (isMainProfile) "app://$`package`:$activity" else "app://$`package`:$activity:${userSerialNumber}"
override fun serialize(): String {
val json = JSONObject()
json.put("package", `package`)
json.put("activity", activity)
json.put("user", userSerialNumber)
return json.toString()
}
fun getUser(): UserHandle? {
return launcherActivityInfo.user
}
override suspend fun loadIconAsync(context: Context, size: Int): LauncherIcon? {
return withContext(Dispatchers.IO) {
IconPackManager.getInstance(context).getIcon(context, launcherActivityInfo, size)
}
}
override fun launch(context: Context, options: Bundle?): Boolean {
val launcherApps = context.getSystemService<LauncherApps>()!!
if (isMainProfile) {
val intent = Intent()
intent.component = ComponentName(`package`, activity)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent, options)
} else {
try {
launcherApps.startMainActivity(
ComponentName(`package`, activity),
launcherActivityInfo.user,
null,
options
)
} catch (e: SecurityException) {
return false
}
}
return true
}
companion object {
fun deserialize(context: Context, serialized: String): LauncherApp? {
val json = JSONObject(serialized)
val launcherApps = context.getSystemService<LauncherApps>()!!
val userManager = context.getSystemService<UserManager>()!!
val userSerial = json.optLong("user")
val user = userManager.getUserForSerialNumber(userSerial) ?: Process.myUserHandle()
val pkg = json.getString("package")
val intent = Intent().also {
it.component = ComponentName(pkg, json.getString("activity"))
}
val launcherActivityInfo = launcherApps.resolveActivity(intent, user) ?: return null
return LauncherApp(context, launcherActivityInfo)
}
fun getPackageVersionName(context: Context, packageName: String): String? {
return try {
context.packageManager.getPackageInfo(packageName, 0).versionName
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
}
}
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M6,18c0,0.55 0.45,1 1,1h1v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L11,19h2v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L16,19h1c0.55,0 1,-0.45 1,-1L18,8L6,8v10zM3.5,8C2.67,8 2,8.67 2,9.5v7c0,0.83 0.67,1.5 1.5,1.5S5,17.33 5,16.5v-7C5,8.67 4.33,8 3.5,8zM20.5,8c-0.83,0 -1.5,0.67 -1.5,1.5v7c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5v-7c0,-0.83 -0.67,-1.5 -1.5,-1.5zM15.53,2.16l1.3,-1.3c0.2,-0.2 0.2,-0.51 0,-0.71 -0.2,-0.2 -0.51,-0.2 -0.71,0l-1.48,1.48C13.85,1.23 12.95,1 12,1c-0.96,0 -1.86,0.23 -2.66,0.63L7.85,0.15c-0.2,-0.2 -0.51,-0.2 -0.71,0 -0.2,0.2 -0.2,0.51 0,0.71l1.31,1.31C6.97,3.26 6,5.01 6,7h12c0,-1.99 -0.97,-3.75 -2.47,-4.84zM10,5L9,5L9,4h1v1zM15,5h-1L14,4h1v1z"/>
</vector>