Initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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"))
|
||||
|
||||
}
|
||||
Vendored
+21
@@ -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>
|
||||
Reference in New Issue
Block a user