chore: split AppInfo into AppInfo and DetailedAppInfo

This commit is contained in:
Josia Pietsch 2024-09-23 15:16:01 +02:00
parent 95e2f82736
commit e4b1bccf85
Signed by: jrpie
GPG key ID: E70B571D66986A2D
10 changed files with 130 additions and 123 deletions

View file

@ -6,7 +6,6 @@ import android.app.role.RoleManager
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.graphics.ColorMatrix
@ -18,7 +17,6 @@ import android.os.Bundle
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import android.util.DisplayMetrics
import android.util.Log
import android.view.View
import android.view.animation.AlphaAnimation
@ -26,25 +24,20 @@ import android.view.animation.Animation
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.actions.AppInfo
import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.ui.list.apps.AppsRecyclerAdapter
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
const val INVALID_USER = -1
/* Objects used by multiple activities */
val appsList: MutableList<AppInfo> = ArrayList()
/* Variables containing settings */
val displayMetrics = DisplayMetrics()
val appsList: MutableList<DetailedAppInfo> = ArrayList()
/* REQUEST CODES */
const val REQUEST_CHOOSE_APP = 1
const val REQUEST_CHOOSE_APP_FROM_FAVORITES = 2
const val REQUEST_UNINSTALL = 3
const val REQUEST_UNINSTALL = 2
const val REQUEST_SET_DEFAULT_HOME = 42
@ -109,16 +102,6 @@ fun getUserFromId(user: Int?, context: Context): UserHandle? {
return userManager.userProfiles.firstOrNull { it.hashCode() == user }
}
fun getLauncherActivityInfo(
packageName: String,
user: Int?,
context: Context
): LauncherActivityInfo? {
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
return getUserFromId(user, context)?.let { userHandle ->
launcherApps.getActivityList(packageName, userHandle).firstOrNull()
}
}
fun uninstallApp(appInfo: AppInfo, activity: Activity) {
val packageName = appInfo.packageName.toString()
@ -154,7 +137,7 @@ fun openAppSettings(
opts: Bundle? = null
) {
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
getLauncherActivityInfo(appInfo.packageName.toString(), appInfo.user, context)?.let { app ->
appInfo.getLauncherActivityInfo(context)?.let { app ->
launcherApps.startAppDetailsActivity(app.componentName, app.user, sourceBounds, opts)
}
}
@ -169,7 +152,7 @@ fun openTutorial(context: Context) {
* as it caches all the apps and allows for fast access to the data.
*/
fun loadApps(packageManager: PackageManager, context: Context) {
val loadList = mutableListOf<AppInfo>()
val loadList = mutableListOf<DetailedAppInfo>()
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager
@ -178,14 +161,14 @@ fun loadApps(packageManager: PackageManager, context: Context) {
val users = userManager.userProfiles
for (user in users) {
for (activityInfo in launcherApps.getActivityList(null, user)) {
val app = AppInfo()
app.label = activityInfo.label
app.packageName = activityInfo.applicationInfo.packageName
app.icon = activityInfo.getBadgedIcon(0)
app.user = user.hashCode()
app.isSystemApp =
val app = AppInfo(activityInfo.applicationInfo.packageName, user.hashCode())
val detailedAppInfo = DetailedAppInfo(
app,
activityInfo.label,
activityInfo.getBadgedIcon(0),
activityInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) != 0
loadList.add(app)
)
loadList.add(detailedAppInfo)
}
}
@ -197,11 +180,13 @@ fun loadApps(packageManager: PackageManager, context: Context) {
i.addCategory(Intent.CATEGORY_LAUNCHER)
val allApps = packageManager.queryIntentActivities(i, 0)
for (ri in allApps) {
val app = AppInfo()
app.label = ri.loadLabel(packageManager)
app.packageName = ri.activityInfo.packageName
app.icon = ri.activityInfo.loadIcon(packageManager)
loadList.add(app)
val app = AppInfo(ri.activityInfo.packageName, AppInfo.INVALID_USER)
val detailedAppInfo = DetailedAppInfo(
app,
ri.loadLabel(packageManager),
ri.activityInfo.loadIcon(packageManager)
)
loadList.add(detailedAppInfo)
}
}
loadList.sortBy { it.label.toString() }

View file

@ -7,8 +7,9 @@ import android.content.SharedPreferences.Editor
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.widget.Toast
import de.jrpie.android.launcher.INVALID_USER
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.preferences.LauncherPreferences
interface Action {
@ -29,7 +30,7 @@ interface Action {
return LauncherAction.byId(id)
}
return AppAction(AppInfo(id, user))
return AppAction(AppInfo(id, user ?: INVALID_USER))
}
fun forGesture(gesture: Gesture): Action? {

View file

@ -8,21 +8,22 @@ import android.content.SharedPreferences
import android.content.pm.LauncherApps
import android.graphics.Rect
import android.graphics.drawable.Drawable
import de.jrpie.android.launcher.INVALID_USER
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.getIntent
import de.jrpie.android.launcher.getLauncherActivityInfo
import de.jrpie.android.launcher.openAppSettings
class AppAction(private var appInfo: AppInfo) : Action {
override fun invoke(context: Context, rect: Rect?): Boolean {
val packageName = appInfo.packageName.toString()
val user = appInfo.user
if (user != null && user != INVALID_USER) {
if (appInfo.user != INVALID_USER) {
val launcherApps =
context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
getLauncherActivityInfo(packageName, user, context)?.let { app ->
appInfo.getLauncherActivityInfo(context)?.let { app ->
launcherApps.startMainActivity(app.componentName, app.user, rect, null)
return true
}
@ -35,7 +36,9 @@ class AppAction(private var appInfo: AppInfo) : Action {
return true
}
if (AppInfo(packageName).isInstalled(context)) {
/* check if app is installed */
if (isAvailable(context)) {
AlertDialog.Builder(
context,
R.style.AlertDialogCustom
@ -54,21 +57,16 @@ class AppAction(private var appInfo: AppInfo) : Action {
}
override fun label(context: Context): String {
return appInfo.label.toString()
return DetailedAppInfo.fromAppInfo(appInfo, context)?.label.toString()
}
override fun getIcon(context: Context): Drawable? {
var icon: Drawable? = null
try {
icon = appInfo.getAppIcon(context)
} catch (e: Exception) {
// probably the app was uninstalled
}
return icon
return DetailedAppInfo.fromAppInfo(appInfo, context)?.icon
}
override fun isAvailable(context: Context): Boolean {
return appInfo.isInstalled(context)
// check if app is installed
return DetailedAppInfo.fromAppInfo(appInfo, context) != null;
}
override fun bindToGesture(editor: SharedPreferences.Editor, id: String) {

View file

@ -1,49 +0,0 @@
package de.jrpie.android.launcher.actions
import android.app.Service
import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import de.jrpie.android.launcher.INVALID_USER
import de.jrpie.android.launcher.getUserFromId
/**
* Stores information used to create [AppsRecyclerAdapter] rows.
*
* Represents an app installed on the users device.
*/
class AppInfo(var packageName: CharSequence? = null, var user: Int? = null) {
var label: CharSequence? = null
var icon: Drawable? = null
var isSystemApp: Boolean = false
fun getAppIcon(context: Context): Drawable {
if (user != null && user != INVALID_USER) {
val launcherApps =
context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
getUserFromId(user, context)?.let { userHandle ->
launcherApps.getActivityList(packageName.toString(), userHandle).firstOrNull()
?.let { app ->
return app.getBadgedIcon(0)
}
}
}
return context.packageManager.getApplicationIcon(packageName.toString())
}
fun isInstalled(context: Context): Boolean {
/* TODO: this should also check the user */
try {
context.packageManager.getPackageInfo(
packageName.toString(),
PackageManager.GET_ACTIVITIES
)
return true
} catch (_: PackageManager.NameNotFoundException) {
}
return false
}
}

View file

@ -9,8 +9,8 @@ import android.media.AudioManager
import android.os.SystemClock
import android.view.KeyEvent
import android.widget.Toast
import de.jrpie.android.launcher.INVALID_USER
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.ui.list.ListActivity
import de.jrpie.android.launcher.ui.settings.SettingsActivity

View file

@ -0,0 +1,40 @@
package de.jrpie.android.launcher.apps
import android.app.Service
import android.content.Context
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import de.jrpie.android.launcher.getUserFromId
/**
* Represents an app installed on the users device.
* Contains the minimal amount of data required to identify the app.
*/
class AppInfo(val packageName: CharSequence, val user: Int = INVALID_USER) {
fun serialize(): String {
val u = user
return "$packageName;$u"
}
fun getLauncherActivityInfo(
context: Context
): LauncherActivityInfo? {
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
return getUserFromId(user, context)?.let { userHandle ->
launcherApps.getActivityList(packageName.toString(), userHandle).firstOrNull()
}
}
companion object {
const val INVALID_USER = -1
fun deserialize(serialized: String): AppInfo {
val values = serialized.split(";")
val packageName = values[0]
val user = Integer.valueOf(values[1])
return AppInfo(packageName, user)
}
}
}

View file

@ -0,0 +1,30 @@
package de.jrpie.android.launcher.apps
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo
import android.graphics.drawable.Drawable
/**
* Stores information used to create [AppsRecyclerAdapter] rows.
*/
class DetailedAppInfo(
val app: AppInfo,
val label: CharSequence,
val icon: Drawable,
val isSystemApp: Boolean = false,
) {
constructor(activityInfo: LauncherActivityInfo) : this(
AppInfo(activityInfo.applicationInfo.packageName, activityInfo.user.hashCode()),
activityInfo.label,
activityInfo.getBadgedIcon(0),
activityInfo.applicationInfo.flags.and(ApplicationInfo.FLAG_SYSTEM) != 0
)
companion object {
fun fromAppInfo(appInfo: AppInfo, context: Context): DetailedAppInfo? {
return appInfo.getLauncherActivityInfo(context)?.let { DetailedAppInfo(it) }
}
}
}

View file

@ -15,7 +15,8 @@ import androidx.recyclerview.widget.RecyclerView
import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.REQUEST_CHOOSE_APP
import de.jrpie.android.launcher.actions.AppAction
import de.jrpie.android.launcher.actions.AppInfo
import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.appsList
import de.jrpie.android.launcher.loadApps
import de.jrpie.android.launcher.openAppSettings
@ -42,7 +43,7 @@ class AppsRecyclerAdapter(
) :
RecyclerView.Adapter<AppsRecyclerAdapter.ViewHolder>() {
private val appsListDisplayed: MutableList<AppInfo>
private val appsListDisplayed: MutableList<DetailedAppInfo>
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
@ -62,10 +63,7 @@ class AppsRecyclerAdapter(
override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
val appLabel = appsListDisplayed[i].label.toString()
val appPackageName = appsListDisplayed[i].packageName.toString()
val appUser = appsListDisplayed[i].user
val appIcon = appsListDisplayed[i].icon
val isSystemApp = appsListDisplayed[i].isSystemApp
viewHolder.textView.text = appLabel
viewHolder.img.setImageDrawable(appIcon)
@ -79,17 +77,13 @@ class AppsRecyclerAdapter(
viewHolder.textView.setOnLongClickListener {
showOptionsPopup(
viewHolder,
appPackageName,
appUser,
isSystemApp
appsListDisplayed[i]
)
}
viewHolder.img.setOnLongClickListener {
showOptionsPopup(
viewHolder,
appPackageName,
appUser,
isSystemApp
appsListDisplayed[i]
)
}
// ensure onClicks are actually caught
@ -101,28 +95,26 @@ class AppsRecyclerAdapter(
@Suppress("SameReturnValue")
private fun showOptionsPopup(
viewHolder: ViewHolder,
appPackageName: String,
user: Int?,
isSystemApp: Boolean
appInfo: DetailedAppInfo
): Boolean {
//create the popup menu
val popup = PopupMenu(activity, viewHolder.img)
popup.inflate(R.menu.menu_app)
if (isSystemApp) {
if (appInfo.isSystemApp) {
popup.menu.findItem(R.id.app_menu_delete).setVisible(false)
}
popup.setOnMenuItemClickListener {
when (it.itemId) {
R.id.app_menu_delete -> {
uninstallApp(AppInfo(appPackageName, user), activity)
uninstallApp(appInfo.app, activity)
true
}
R.id.app_menu_info -> {
openAppSettings(AppInfo(appPackageName, user), activity)
openAppSettings(appInfo.app, activity)
true
}
@ -164,12 +156,12 @@ class AppsRecyclerAdapter(
val appInfo = appsListDisplayed[pos]
when (intention) {
ListActivity.ListActivityIntention.VIEW -> {
AppAction(appInfo).invoke(activity, rect)
AppAction(appInfo.app).invoke(activity, rect)
}
ListActivity.ListActivityIntention.PICK -> {
val returnIntent = Intent()
AppAction(appInfo).writeToIntent(returnIntent)
AppAction(appInfo.app).writeToIntent(returnIntent)
returnIntent.putExtra("forGesture", forGesture)
activity.setResult(REQUEST_CHOOSE_APP, returnIntent)
activity.finish()
@ -198,7 +190,7 @@ class AppsRecyclerAdapter(
if (text.isEmpty()) {
appsListDisplayed.addAll(appsList)
} else {
val appsSecondary: MutableList<AppInfo> = ArrayList()
val appsSecondary: MutableList<DetailedAppInfo> = ArrayList()
val normalizedText: String = normalize(text)
for (item in appsList) {
val itemLabel: String = normalize(item.label.toString())
@ -216,7 +208,7 @@ class AppsRecyclerAdapter(
&& LauncherPreferences.functionality().searchAutoLaunch()
) {
val info = appsListDisplayed[0]
AppAction(info).invoke(activity)
AppAction(info.app).invoke(activity)
val inputMethodManager =
activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
</vector>