From 500062b29baedceb8064d3b36f77e2cfb39b5da7 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Fri, 2 Aug 2024 22:32:35 +0200 Subject: [PATCH] feature: work profile --- README.md | 1 + app/src/main/AndroidManifest.xml | 1 + .../de/jrpie/android/launcher/Functions.kt | 83 +++++++++++++++---- .../java/de/jrpie/android/launcher/Gesture.kt | 18 +++- .../de/jrpie/android/launcher/HomeActivity.kt | 2 +- .../android/launcher/list/apps/AppInfo.kt | 1 + .../launcher/list/apps/AppsRecyclerAdapter.kt | 12 ++- .../list/other/OtherRecyclerAdapter.kt | 6 +- .../SettingsFragmentActionsRecycler.kt | 5 +- 9 files changed, 100 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 2e25e3e..51ae856 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This is a fork of [finnmglas's app Launcher][original-repo]. ## Notable changes: * Edge gestures: There is a setting to allow distinguishing swiping at the edges of the screen from swiping in the center. +* Compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used. ### Visual * This app uses the system wallpaper instead of a custom solution. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ec99e9b..5df0438 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + 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 a9d7f6c..dd70db0 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -67,6 +67,8 @@ const val PREF_STARTED_TIME = "firstStartup" const val PREF_VERSION = "version" +const val INVALID_USER = -1 + /* Objects used by multiple activities */ val appsList: MutableList = ArrayList() @@ -153,14 +155,15 @@ private fun getIntent(packageName: String, context: Context): Intent? { } fun launch( - data: String, activity: Activity, + data: String, user: Int?, + activity: Activity, animationIn: Int = android.R.anim.fade_in, animationOut: Int = android.R.anim.fade_out ) { if (LauncherAction.isOtherAction(data)) { // [type]:[info] LauncherAction.byId(data)?.let {it.launch(activity) } } - else launchApp(data, activity) // app + else launchApp(data, user, activity) // app activity.overridePendingTransition(animationIn, animationOut) } @@ -217,7 +220,20 @@ fun audioVolumeDown(activity: Activity) { /* --- */ -fun launchApp(packageName: String, context: Context) { +fun launchApp(packageName: String, user: Int?, context: Context) { + Log.i("Launcher", "Starting: " + packageName + " (user " +user.toString()+ ")") + if (user != null) { + val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps + val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager + userManager.userProfiles.firstOrNull { it.hashCode() == user }?.let { + userHandle -> launcherApps.getActivityList(packageName, userHandle).firstOrNull()?.let { + app -> launcherApps.startMainActivity(app.componentName, userHandle, null, null) + return + + } + } + } + val intent = getIntent(packageName, context) if (intent != null) { @@ -327,25 +343,57 @@ fun openAppsList(activity: Activity){ activity.startActivity(intent) } +fun getAppIcon(context: Context, packageName: String, user: Int?): Drawable { + if (user != null) { + val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps + val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager + userManager.userProfiles.firstOrNull { it.hashCode() == user }?.let { + userHandle -> launcherApps.getActivityList(packageName, userHandle).firstOrNull()?.let { + app -> return app.getBadgedIcon(0) + } + } + } + return context.packageManager.getApplicationIcon(packageName) +} + /** * [loadApps] is used to speed up the [AppsRecyclerAdapter] loading time, * as it caches all the apps and allows for fast access to the data. */ -fun loadApps(packageManager: PackageManager) { +fun loadApps(packageManager: PackageManager, context: Context) { val loadList = mutableListOf() - val i = Intent(Intent.ACTION_MAIN, null) - 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) - } - loadList.sortBy { it.label.toString() } + val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps + val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager + // TODO: shortcuts - launcherApps.getShortcuts() + 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() + loadList.add(app) + } + } + + + // fallback option + if(loadList.isEmpty()){ + Log.i("Launcher", "using fallback option to load packages") + val i = Intent(Intent.ACTION_MAIN, null) + 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) + } + } appsList.clear() appsList.addAll(loadList) } @@ -404,9 +452,12 @@ fun setWindowFlags(window: Window) { // Used in Tutorial and Settings `ActivityOnResult` fun saveListActivityChoice(context: Context, data: Intent?) { val value = data?.getStringExtra("value") + var user = data?.getIntExtra("user", INVALID_USER) + user = user?.let{ if(it == INVALID_USER) null else it } + val forGesture = data?.getStringExtra("forGesture") ?: return - Gesture.byId(forGesture)?.setApp(context, value.toString()) + Gesture.byId(forGesture)?.setApp(context, value.toString(), user) loadSettings(context) } diff --git a/app/src/main/java/de/jrpie/android/launcher/Gesture.kt b/app/src/main/java/de/jrpie/android/launcher/Gesture.kt index 8d70340..fc48d1d 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Gesture.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Gesture.kt @@ -41,8 +41,12 @@ enum class Gesture (val id: String, private val labelResource: Int, TOP, BOTTOM, LEFT, RIGHT } - fun getApp(context: Context): String { - return getPreferences(context).getString(this.id, "")!! + fun getApp(context: Context): Pair { + val preferences = getPreferences(context) + var packageName = preferences.getString(this.id, "")!! + var u: Int? = preferences.getInt(this.id + "_user", INVALID_USER) + u = if(u == INVALID_USER) null else u + return Pair(packageName,u) } fun removeApp(context: Context) { @@ -51,10 +55,15 @@ enum class Gesture (val id: String, private val labelResource: Int, .apply() } - fun setApp(context: Context, app: String) { + fun setApp(context: Context, app: String, user: Int?) { getPreferences(context).edit() .putString(this.id, app) .apply() + + val u = user?: INVALID_USER + getPreferences(context).edit() + .putInt(this.id + "_user", u) + .apply() } fun getLabel(context: Context): String { @@ -132,7 +141,8 @@ enum class Gesture (val id: String, private val labelResource: Int, } operator fun invoke(activity: Activity) { - launch(this.getApp(activity), activity, this.animationIn, this.animationOut) + val app = this.getApp(activity) + launch(app.first, app.second, activity, this.animationIn, this.animationOut) } companion object { diff --git a/app/src/main/java/de/jrpie/android/launcher/HomeActivity.kt b/app/src/main/java/de/jrpie/android/launcher/HomeActivity.kt index 10529ed..fd20091 100644 --- a/app/src/main/java/de/jrpie/android/launcher/HomeActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/HomeActivity.kt @@ -87,7 +87,7 @@ class HomeActivity: UIObject, AppCompatActivity(), } // Preload apps to speed up the Apps Recycler - AsyncTask.execute { loadApps(packageManager) } + AsyncTask.execute { loadApps(packageManager, applicationContext) } // Initialise layout binding = HomeBinding.inflate(layoutInflater) diff --git a/app/src/main/java/de/jrpie/android/launcher/list/apps/AppInfo.kt b/app/src/main/java/de/jrpie/android/launcher/list/apps/AppInfo.kt index 0c99757..f19fd0e 100644 --- a/app/src/main/java/de/jrpie/android/launcher/list/apps/AppInfo.kt +++ b/app/src/main/java/de/jrpie/android/launcher/list/apps/AppInfo.kt @@ -8,6 +8,7 @@ import android.graphics.drawable.Drawable * Represents an app installed on the users device. */ class AppInfo { + var user: Int? = null var label: CharSequence? = null var packageName: CharSequence? = null var icon: Drawable? = null diff --git a/app/src/main/java/de/jrpie/android/launcher/list/apps/AppsRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/list/apps/AppsRecyclerAdapter.kt index a0c76da..d0d70b5 100644 --- a/app/src/main/java/de/jrpie/android/launcher/list/apps/AppsRecyclerAdapter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/list/apps/AppsRecyclerAdapter.kt @@ -21,6 +21,7 @@ import de.jrpie.android.launcher.appsList import de.jrpie.android.launcher.getPreferences import de.jrpie.android.launcher.getSavedTheme import de.jrpie.android.launcher.launch +import de.jrpie.android.launcher.launchApp import de.jrpie.android.launcher.list.ListActivity import de.jrpie.android.launcher.list.intendedChoosePause import de.jrpie.android.launcher.loadApps @@ -54,9 +55,10 @@ class AppsRecyclerAdapter(val activity: Activity, val pos = adapterPosition val context: Context = v.context val appPackageName = appsListDisplayed[pos].packageName.toString() - + val appUser = appsListDisplayed[pos].user when (intention){ ListActivity.ListActivityIntention.VIEW -> { + launchApp(appPackageName, appUser, activity) val launchIntent: Intent = context.packageManager .getLaunchIntentForPackage(appPackageName)!! context.startActivity(launchIntent) @@ -64,6 +66,7 @@ class AppsRecyclerAdapter(val activity: Activity, ListActivity.ListActivityIntention.PICK -> { val returnIntent = Intent() returnIntent.putExtra("value", appPackageName) + appUser?.let{ returnIntent.putExtra("user", it) } returnIntent.putExtra("forGesture", forGesture) activity.setResult(REQUEST_CHOOSE_APP, returnIntent) activity.finish() @@ -152,9 +155,9 @@ class AppsRecyclerAdapter(val activity: Activity, init { // Load the apps if (appsList.size == 0) - loadApps(activity.packageManager) + loadApps(activity.packageManager, activity) else { - AsyncTask.execute { loadApps(activity.packageManager) } + AsyncTask.execute { loadApps(activity.packageManager, activity) } notifyDataSetChanged() } @@ -193,7 +196,8 @@ class AppsRecyclerAdapter(val activity: Activity, // modifiable at some later point. if (appsListDisplayed.size == 1 && intention == ListActivity.ListActivityIntention.VIEW && getPreferences(activity).getBoolean(PREF_SEARCH_AUTO_LAUNCH, false)) { - launch(appsListDisplayed[0].packageName.toString(), activity) + val info = appsListDisplayed[0] + launch(info.packageName.toString(), info.user, activity) val inputMethodManager = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(View(activity).windowToken, 0) diff --git a/app/src/main/java/de/jrpie/android/launcher/list/other/OtherRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/list/other/OtherRecyclerAdapter.kt index 1e04a0d..0b54fa6 100644 --- a/app/src/main/java/de/jrpie/android/launcher/list/other/OtherRecyclerAdapter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/list/other/OtherRecyclerAdapter.kt @@ -8,6 +8,7 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView +import de.jrpie.android.launcher.INVALID_USER import de.jrpie.android.launcher.R import de.jrpie.android.launcher.REQUEST_CHOOSE_APP import de.jrpie.android.launcher.list.forGesture @@ -56,10 +57,11 @@ class OtherRecyclerAdapter(val activity: Activity): return ViewHolder(view) } - private fun returnChoiceIntent(forApp: String, value: String) { + private fun returnChoiceIntent(forGesture: String, value: String) { val returnIntent = Intent() returnIntent.putExtra("value", value) - returnIntent.putExtra("forGesture", forApp) + returnIntent.putExtra("forGesture", forGesture) + returnIntent.putExtra("user", INVALID_USER) activity.setResult(REQUEST_CHOOSE_APP, returnIntent) activity.finish() } diff --git a/app/src/main/java/de/jrpie/android/launcher/settings/actions/SettingsFragmentActionsRecycler.kt b/app/src/main/java/de/jrpie/android/launcher/settings/actions/SettingsFragmentActionsRecycler.kt index 0685a87..3e609c4 100644 --- a/app/src/main/java/de/jrpie/android/launcher/settings/actions/SettingsFragmentActionsRecycler.kt +++ b/app/src/main/java/de/jrpie/android/launcher/settings/actions/SettingsFragmentActionsRecycler.kt @@ -80,7 +80,8 @@ class ActionsRecyclerAdapter(val activity: Activity): viewHolder.img ) fun updateViewHolder() { - val content = gesture.getApp(activity) + val app = gesture.getApp(activity) + val content = app.first if (content == ""){ viewHolder.img.visibility = View.INVISIBLE viewHolder.removeAction.visibility = View.GONE @@ -93,7 +94,7 @@ class ActionsRecyclerAdapter(val activity: Activity): } else { // Set image icon (by packageName) try { - viewHolder.img.setImageDrawable(activity.packageManager.getApplicationIcon(content)) + viewHolder.img.setImageDrawable(getAppIcon(activity, content, app.second)) } catch (e : Exception) { // the button is shown, user asked to select an action viewHolder.img.visibility = View.INVISIBLE