From 785e024ddb81735cd91e538959ad87bcadc732e8 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Wed, 22 Jan 2025 01:44:51 +0100 Subject: [PATCH] basic support for private space (#98) * add an action to toggle private space lock * hide apps from private space if private space is locked --- .../de/jrpie/android/launcher/Application.kt | 28 +++++++++ .../de/jrpie/android/launcher/Functions.kt | 61 +++++++++++++------ .../launcher/actions/LauncherAction.kt | 56 ++++++++++++++++- .../LauncherPreferences$Config.java | 3 +- .../launcher/preferences/theme/ColorTheme.kt | 2 +- .../jrpie/android/launcher/ui/HomeActivity.kt | 4 -- .../ui/list/other/OtherRecyclerAdapter.kt | 3 +- .../launcher/SettingsFragmentLauncher.kt | 5 ++ .../res/drawable/baseline_security_24.xml | 12 ++++ app/src/main/res/values/donottranslate.xml | 1 + app/src/main/res/values/strings.xml | 7 +++ app/src/main/res/xml/preferences.xml | 5 ++ 12 files changed, 159 insertions(+), 28 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_security_24.xml diff --git a/app/src/main/java/de/jrpie/android/launcher/Application.kt b/app/src/main/java/de/jrpie/android/launcher/Application.kt index e3e5f7c..6e924c2 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Application.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Application.kt @@ -1,5 +1,9 @@ package de.jrpie.android.launcher +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.content.SharedPreferences import android.content.pm.LauncherApps import android.content.pm.ShortcutInfo @@ -7,6 +11,7 @@ import android.os.AsyncTask import android.os.Build import android.os.Build.VERSION_CODES import android.os.UserHandle +import androidx.core.content.ContextCompat import androidx.lifecycle.MutableLiveData import androidx.preference.PreferenceManager import de.jrpie.android.launcher.actions.TorchManager @@ -19,6 +24,14 @@ import de.jrpie.android.launcher.preferences.resetPreferences class Application : android.app.Application() { val apps = MutableLiveData>() + private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + // TODO: only update specific apps + // use Intent.EXTRA_USER + loadApps() + } + } + // TODO: only update specific apps private val launcherAppsCallback = object : LauncherApps.Callback() { override fun onPackageRemoved(p0: String?, p1: UserHandle?) { @@ -107,6 +120,21 @@ class Application : android.app.Application() { val launcherApps = getSystemService(LAUNCHER_APPS_SERVICE) as LauncherApps launcherApps.registerCallback(launcherAppsCallback) + if (Build.VERSION.SDK_INT >= VERSION_CODES.N) { + val filter = IntentFilter().also { + if (Build.VERSION.SDK_INT >= VERSION_CODES.VANILLA_ICE_CREAM) { + it.addAction(Intent.ACTION_PROFILE_AVAILABLE) + it.addAction(Intent.ACTION_PROFILE_UNAVAILABLE) + } else { + it.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) + it.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) + } + } + ContextCompat.registerReceiver(this, profileAvailabilityBroadcastReceiver, filter, + ContextCompat.RECEIVER_EXPORTED + ) + } + loadApps() } 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 e8d3851..48b5028 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -18,6 +18,7 @@ import de.jrpie.android.launcher.actions.Action 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.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.tutorial.TutorialActivity @@ -30,31 +31,36 @@ const val REQUEST_SET_DEFAULT_HOME = 42 const val LOG_TAG = "Launcher" -fun setDefaultHomeScreen(context: Context, checkDefault: Boolean = false) { - - if (checkDefault - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q - && context is Activity - ) { +fun isDefaultHomeScreen(context: Context): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val roleManager = context.getSystemService(RoleManager::class.java) - if (!roleManager.isRoleHeld(RoleManager.ROLE_HOME)) { - context.startActivityForResult( - roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME), - REQUEST_SET_DEFAULT_HOME - ) - } - return - } - - if (checkDefault) { + return roleManager.isRoleHeld(RoleManager.ROLE_HOME) + } else { val testIntent = Intent(Intent.ACTION_MAIN) testIntent.addCategory(Intent.CATEGORY_HOME) val defaultHome = testIntent.resolveActivity(context.packageManager)?.packageName - if (defaultHome == context.packageName) { - // Launcher is already the default home app - return - } + return defaultHome == context.packageName } +} + +fun setDefaultHomeScreen(context: Context, checkDefault: Boolean = false) { + if (checkDefault && isDefaultHomeScreen(context)) { + // Launcher is already the default home app + return + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + && context is Activity + && checkDefault // using role manager only works when µLauncher is not already the default. + ) { + val roleManager = context.getSystemService(RoleManager::class.java) + context.startActivityForResult( + roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME), + REQUEST_SET_DEFAULT_HOME + ) + return + } + val intent = Intent(Settings.ACTION_HOME_SETTINGS) context.startActivity(intent) } @@ -94,6 +100,21 @@ fun getApps(packageManager: PackageManager, context: Context): MutableList= Build.VERSION_CODES.N) { + if (userManager.isQuietModeEnabled(user)) { + // hide paused apps + if (LauncherPreferences.apps().hidePausedApps()) { + continue + } + // hide apps from private space + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && + launcherApps.getLauncherUserInfo(user)?.userType == UserManager.USER_TYPE_PROFILE_PRIVATE + ) { + continue + } + } + } launcherApps.getActivityList(null, user).forEach { loadList.add(DetailedAppInfo(it)) } 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 3c641ff..2eddf16 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 @@ -1,17 +1,22 @@ package de.jrpie.android.launcher.actions +import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent +import android.content.pm.LauncherApps import android.graphics.Rect import android.graphics.drawable.Drawable import android.media.AudioManager import android.os.Build import android.os.SystemClock +import android.os.UserManager +import android.provider.Settings import android.view.KeyEvent import android.widget.Toast import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.R import de.jrpie.android.launcher.apps.AppFilter +import de.jrpie.android.launcher.isDefaultHomeScreen import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.list.ListActivity import de.jrpie.android.launcher.ui.settings.SettingsActivity @@ -33,7 +38,8 @@ enum class LauncherAction( val id: String, val label: Int, val icon: Int, - val launch: (Context) -> Unit + val launch: (Context) -> Unit, + val available: (Context) -> Boolean = { true } ) : Action { SETTINGS( "settings", @@ -53,6 +59,13 @@ enum class LauncherAction( R.drawable.baseline_favorite_24, { context -> openAppsList(context, true) } ), + TOGGLE_PRIVATE_SPACE_LOCK( + "toggle_private_space_lock", + R.string.list_other_toggle_private_space_lock, + R.drawable.baseline_security_24, + ::togglePrivateSpaceLock, + available = { Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM } + ), VOLUME_UP( "volume_up", R.string.list_other_volume_up, @@ -207,6 +220,46 @@ private fun expandNotificationsPanel(context: Context) { } } +private fun togglePrivateSpaceLock(context: Context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { + Toast.makeText( + context, + context.getString(R.string.alert_requires_android_v), + Toast.LENGTH_LONG + ).show() + return + } + val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager + val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps + val privateSpaceUser = userManager.userProfiles.firstOrNull { u -> + launcherApps.getLauncherUserInfo(u)?.userType == UserManager.USER_TYPE_PROFILE_PRIVATE + } + if (privateSpaceUser == null) { + Toast.makeText(context, context.getString(R.string.toast_private_space_not_available), Toast.LENGTH_LONG).show() + + if (!isDefaultHomeScreen(context)) { + Toast.makeText(context, context.getString(R.string.toast_private_space_default_home_screen), Toast.LENGTH_LONG).show() + return + } + + try { + context.startActivity(Intent(Settings.ACTION_PRIVACY_SETTINGS)) + } catch (_: ActivityNotFoundException) {} + return + } + if (userManager.isQuietModeEnabled(privateSpaceUser)) { + userManager.requestQuietModeEnabled(false, privateSpaceUser) + Toast.makeText( + context, + context.getString(R.string.toast_private_space_unlocked), + Toast.LENGTH_LONG + ).show() + return + } + userManager.requestQuietModeEnabled(true, privateSpaceUser) + Toast.makeText(context, context.getString(R.string.toast_private_space_locked), Toast.LENGTH_LONG).show() +} + private fun expandSettingsPanel(context: Context) { /* https://stackoverflow.com/a/31898506 */ try { @@ -260,6 +313,7 @@ private class LauncherActionSerializer : KSerializer { ) { element("value", String.serializer().descriptor) } + override fun deserialize(decoder: Decoder): LauncherAction { val s = decoder.decodeStructure(descriptor) { decodeElementIndex(descriptor) diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java index 640e6ce..98496ce 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java @@ -5,8 +5,8 @@ import java.util.Set; import de.jrpie.android.launcher.R; import de.jrpie.android.launcher.actions.lock.LockMethod; -import de.jrpie.android.launcher.preferences.serialization.SetAppInfoPreferenceSerializer; import de.jrpie.android.launcher.preferences.serialization.MapAppInfoStringPreferenceSerializer; +import de.jrpie.android.launcher.preferences.serialization.SetAppInfoPreferenceSerializer; import de.jrpie.android.launcher.preferences.theme.Background; import de.jrpie.android.launcher.preferences.theme.ColorTheme; import de.jrpie.android.launcher.preferences.theme.Font; @@ -29,6 +29,7 @@ import eu.jonahbauer.android.preference.annotations.Preferences; @Preference(name = "hidden", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class), @Preference(name = "custom_names", type = HashMap.class, serializer = MapAppInfoStringPreferenceSerializer.class), @Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"), + @Preference(name = "hide_paused_apps", type = boolean.class, defaultValue = "false"), }), @PreferenceGroup(name = "list", prefix = "settings_list_", suffix = "_key", value = { @Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT") diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/theme/ColorTheme.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/theme/ColorTheme.kt index 8cef124..816d94f 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/theme/ColorTheme.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/theme/ColorTheme.kt @@ -2,8 +2,8 @@ package de.jrpie.android.launcher.preferences.theme import android.content.Context import android.content.res.Resources -import de.jrpie.android.launcher.R import com.google.android.material.color.DynamicColors +import de.jrpie.android.launcher.R @Suppress("unused") enum class ColorTheme( diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt index 5633113..b1a5cc0 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt @@ -3,7 +3,6 @@ package de.jrpie.android.launcher.ui import android.annotation.SuppressLint import android.content.SharedPreferences import android.content.res.Resources -import android.os.AsyncTask import android.os.Build import android.os.Bundle import android.util.DisplayMetrics @@ -20,10 +19,7 @@ import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.LauncherAction import de.jrpie.android.launcher.databinding.HomeBinding -import de.jrpie.android.launcher.openTutorial import de.jrpie.android.launcher.preferences.LauncherPreferences -import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion -import de.jrpie.android.launcher.preferences.resetPreferences import de.jrpie.android.launcher.ui.tutorial.TutorialActivity import java.text.SimpleDateFormat import java.util.Date diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt b/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt index ddfcfd1..97d1c84 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/list/other/OtherRecyclerAdapter.kt @@ -23,7 +23,8 @@ import de.jrpie.android.launcher.ui.list.forGesture class OtherRecyclerAdapter(val activity: Activity) : RecyclerView.Adapter() { - private val othersList: Array = LauncherAction.values() + private val othersList: Array = + LauncherAction.entries.filter { it.isAvailable(activity) }.toTypedArray() inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt b/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt index 0b054ce..a8efb43 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/settings/launcher/SettingsFragmentLauncher.kt @@ -40,6 +40,11 @@ class SettingsFragmentLauncher : PreferenceFragmentCompat() { ) val lightTheme = LauncherPreferences.theme().colorTheme() == ColorTheme.LIGHT background?.isVisible = !lightTheme + + val hidePausedApps = findPreference( + LauncherPreferences.apps().keys().hidePausedApps() + ) + hidePausedApps?.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N } override fun onStart() { diff --git a/app/src/main/res/drawable/baseline_security_24.xml b/app/src/main/res/drawable/baseline_security_24.xml new file mode 100644 index 0000000..3c260ff --- /dev/null +++ b/app/src/main/res/drawable/baseline_security_24.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 0a30fac..b2e69a1 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -13,6 +13,7 @@ apps.hidden apps.custom_names apps.hide_bound_apps + apps.hide_paused_apps list.layout general.select_launcher diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5bb2c61..4cdebb1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -145,6 +145,7 @@ Apps Hidden apps Don\'t show apps that are bound to a gesture in the app list + Hide paused apps Layout of app list Default @@ -207,6 +208,7 @@ µLauncher Settings All Applications Favorite Applications + Toggle Private Space Lock Music: Louder Music: Quieter Music: Next @@ -245,6 +247,7 @@ More options Error: Can\'t expand status bar. This action is using functionality that is not part of the published Android API. Unfortunately, it does not seem to work on your device. This functionality requires Android 6.0 or later. + This functionality requires Android 15.0 or later. App hidden. You can make it visible again in settings. Undo Quick Settings @@ -255,6 +258,10 @@ Error: Can\'t access torch. Error: Failed to lock screen. (If you just upgraded the app, try to disable and re-enable the accessibility service in phone settings) μLauncher\'s accessibility service is not enabled. Please enable it in settings + Private space locked + Private space unlocked + Private space is not available + µLauncher needs to be the default home screen to access private space. Error: Locking the screen using accessibility is not supported on this device. Please use device admin instead. µLauncher - lock screen diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index d1dcbe6..57e50df 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -145,6 +145,11 @@ android:title="@string/settings_apps_hide_bound_apps" android:defaultValue="false" /> + +