diff --git a/app/src/main/java/de/jrpie/android/launcher/Functions.kt b/app/src/main/java/de/jrpie/android/launcher/Functions.kt index cf6a511..8df5390 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -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 = ArrayList() - -/* Variables containing settings */ -val displayMetrics = DisplayMetrics() +val appsList: MutableList = 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() + val loadList = mutableListOf() 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() } diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt b/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt index d35a157..b9cd9db 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/Action.kt @@ -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? { diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt index f1a377a..25af413 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/AppAction.kt @@ -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) { diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/AppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/actions/AppInfo.kt deleted file mode 100644 index 8e1e7f1..0000000 --- a/app/src/main/java/de/jrpie/android/launcher/actions/AppInfo.kt +++ /dev/null @@ -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 - } - - -} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt b/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt index 6d455eb..231ae46 100644 --- a/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt +++ b/app/src/main/java/de/jrpie/android/launcher/actions/LauncherAction.kt @@ -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 diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt new file mode 100644 index 0000000..2586cfe --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/apps/AppInfo.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/apps/DetailedAppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/apps/DetailedAppInfo.kt new file mode 100644 index 0000000..d618c68 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/apps/DetailedAppInfo.kt @@ -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) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt index c2d0c62..02abfe8 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/apps/AppsRecyclerAdapter.kt @@ -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() { - private val appsListDisplayed: MutableList + private val appsListDisplayed: MutableList 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 = ArrayList() + val appsSecondary: MutableList = 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 diff --git a/app/src/main/res/drawable/baseline_favorite_24.xml b/app/src/main/res/drawable/baseline_favorite_24.xml new file mode 100644 index 0000000..90fac96 --- /dev/null +++ b/app/src/main/res/drawable/baseline_favorite_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_favorite_border_24.xml b/app/src/main/res/drawable/baseline_favorite_border_24.xml new file mode 100644 index 0000000..c6f3cc3 --- /dev/null +++ b/app/src/main/res/drawable/baseline_favorite_border_24.xml @@ -0,0 +1,5 @@ + + + + +