basic support for private space (#98)

* add an action to toggle private space lock
* hide apps from private space if private space is locked
This commit is contained in:
Josia Pietsch 2025-01-22 01:44:51 +01:00
parent 679c90130d
commit 785e024ddb
Signed by: jrpie
GPG key ID: E70B571D66986A2D
12 changed files with 159 additions and 28 deletions

View file

@ -1,5 +1,9 @@
package de.jrpie.android.launcher 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.SharedPreferences
import android.content.pm.LauncherApps import android.content.pm.LauncherApps
import android.content.pm.ShortcutInfo import android.content.pm.ShortcutInfo
@ -7,6 +11,7 @@ import android.os.AsyncTask
import android.os.Build import android.os.Build
import android.os.Build.VERSION_CODES import android.os.Build.VERSION_CODES
import android.os.UserHandle import android.os.UserHandle
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import de.jrpie.android.launcher.actions.TorchManager import de.jrpie.android.launcher.actions.TorchManager
@ -19,6 +24,14 @@ import de.jrpie.android.launcher.preferences.resetPreferences
class Application : android.app.Application() { class Application : android.app.Application() {
val apps = MutableLiveData<List<DetailedAppInfo>>() val apps = MutableLiveData<List<DetailedAppInfo>>()
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 // TODO: only update specific apps
private val launcherAppsCallback = object : LauncherApps.Callback() { private val launcherAppsCallback = object : LauncherApps.Callback() {
override fun onPackageRemoved(p0: String?, p1: UserHandle?) { override fun onPackageRemoved(p0: String?, p1: UserHandle?) {
@ -107,6 +120,21 @@ class Application : android.app.Application() {
val launcherApps = getSystemService(LAUNCHER_APPS_SERVICE) as LauncherApps val launcherApps = getSystemService(LAUNCHER_APPS_SERVICE) as LauncherApps
launcherApps.registerCallback(launcherAppsCallback) 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() loadApps()
} }

View file

@ -18,6 +18,7 @@ import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.actions.Gesture import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
@ -30,31 +31,36 @@ const val REQUEST_SET_DEFAULT_HOME = 42
const val LOG_TAG = "Launcher" const val LOG_TAG = "Launcher"
fun setDefaultHomeScreen(context: Context, checkDefault: Boolean = false) { fun isDefaultHomeScreen(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val roleManager = context.getSystemService(RoleManager::class.java)
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
return defaultHome == context.packageName
}
}
if (checkDefault fun setDefaultHomeScreen(context: Context, checkDefault: Boolean = false) {
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q if (checkDefault && isDefaultHomeScreen(context)) {
// Launcher is already the default home app
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
&& context is Activity && context is Activity
&& checkDefault // using role manager only works when µLauncher is not already the default.
) { ) {
val roleManager = context.getSystemService(RoleManager::class.java) val roleManager = context.getSystemService(RoleManager::class.java)
if (!roleManager.isRoleHeld(RoleManager.ROLE_HOME)) {
context.startActivityForResult( context.startActivityForResult(
roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME), roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME),
REQUEST_SET_DEFAULT_HOME REQUEST_SET_DEFAULT_HOME
) )
}
return return
} }
if (checkDefault) {
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
}
}
val intent = Intent(Settings.ACTION_HOME_SETTINGS) val intent = Intent(Settings.ACTION_HOME_SETTINGS)
context.startActivity(intent) context.startActivity(intent)
} }
@ -94,6 +100,21 @@ fun getApps(packageManager: PackageManager, context: Context): MutableList<Detai
// TODO: shortcuts - launcherApps.getShortcuts() // TODO: shortcuts - launcherApps.getShortcuts()
val users = userManager.userProfiles val users = userManager.userProfiles
for (user in users) { for (user in users) {
// don't load apps from a user profile that has quiet mode enabled
if (Build.VERSION.SDK_INT >= 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 { launcherApps.getActivityList(null, user).forEach {
loadList.add(DetailedAppInfo(it)) loadList.add(DetailedAppInfo(it))
} }

View file

@ -1,17 +1,22 @@
package de.jrpie.android.launcher.actions package de.jrpie.android.launcher.actions
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.LauncherApps
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.media.AudioManager import android.media.AudioManager
import android.os.Build import android.os.Build
import android.os.SystemClock import android.os.SystemClock
import android.os.UserManager
import android.provider.Settings
import android.view.KeyEvent import android.view.KeyEvent
import android.widget.Toast import android.widget.Toast
import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.apps.AppFilter 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.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.list.ListActivity import de.jrpie.android.launcher.ui.list.ListActivity
import de.jrpie.android.launcher.ui.settings.SettingsActivity import de.jrpie.android.launcher.ui.settings.SettingsActivity
@ -33,7 +38,8 @@ enum class LauncherAction(
val id: String, val id: String,
val label: Int, val label: Int,
val icon: Int, val icon: Int,
val launch: (Context) -> Unit val launch: (Context) -> Unit,
val available: (Context) -> Boolean = { true }
) : Action { ) : Action {
SETTINGS( SETTINGS(
"settings", "settings",
@ -53,6 +59,13 @@ enum class LauncherAction(
R.drawable.baseline_favorite_24, R.drawable.baseline_favorite_24,
{ context -> openAppsList(context, true) } { 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(
"volume_up", "volume_up",
R.string.list_other_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) { private fun expandSettingsPanel(context: Context) {
/* https://stackoverflow.com/a/31898506 */ /* https://stackoverflow.com/a/31898506 */
try { try {
@ -260,6 +313,7 @@ private class LauncherActionSerializer : KSerializer<LauncherAction> {
) { ) {
element("value", String.serializer().descriptor) element("value", String.serializer().descriptor)
} }
override fun deserialize(decoder: Decoder): LauncherAction { override fun deserialize(decoder: Decoder): LauncherAction {
val s = decoder.decodeStructure(descriptor) { val s = decoder.decodeStructure(descriptor) {
decodeElementIndex(descriptor) decodeElementIndex(descriptor)

View file

@ -5,8 +5,8 @@ import java.util.Set;
import de.jrpie.android.launcher.R; import de.jrpie.android.launcher.R;
import de.jrpie.android.launcher.actions.lock.LockMethod; 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.MapAppInfoStringPreferenceSerializer;
import de.jrpie.android.launcher.preferences.serialization.SetAppInfoPreferenceSerializer;
import de.jrpie.android.launcher.preferences.theme.Background; import de.jrpie.android.launcher.preferences.theme.Background;
import de.jrpie.android.launcher.preferences.theme.ColorTheme; import de.jrpie.android.launcher.preferences.theme.ColorTheme;
import de.jrpie.android.launcher.preferences.theme.Font; 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 = "hidden", type = Set.class, serializer = SetAppInfoPreferenceSerializer.class),
@Preference(name = "custom_names", type = HashMap.class, serializer = MapAppInfoStringPreferenceSerializer.class), @Preference(name = "custom_names", type = HashMap.class, serializer = MapAppInfoStringPreferenceSerializer.class),
@Preference(name = "hide_bound_apps", type = boolean.class, defaultValue = "false"), @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 = { @PreferenceGroup(name = "list", prefix = "settings_list_", suffix = "_key", value = {
@Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT") @Preference(name = "layout", type = ListLayout.class, defaultValue = "DEFAULT")

View file

@ -2,8 +2,8 @@ package de.jrpie.android.launcher.preferences.theme
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import de.jrpie.android.launcher.R
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import de.jrpie.android.launcher.R
@Suppress("unused") @Suppress("unused")
enum class ColorTheme( enum class ColorTheme(

View file

@ -3,7 +3,6 @@ package de.jrpie.android.launcher.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Resources import android.content.res.Resources
import android.os.AsyncTask
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics 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.Gesture
import de.jrpie.android.launcher.actions.LauncherAction import de.jrpie.android.launcher.actions.LauncherAction
import de.jrpie.android.launcher.databinding.HomeBinding 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.LauncherPreferences
import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion
import de.jrpie.android.launcher.preferences.resetPreferences
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date

View file

@ -23,7 +23,8 @@ import de.jrpie.android.launcher.ui.list.forGesture
class OtherRecyclerAdapter(val activity: Activity) : class OtherRecyclerAdapter(val activity: Activity) :
RecyclerView.Adapter<OtherRecyclerAdapter.ViewHolder>() { RecyclerView.Adapter<OtherRecyclerAdapter.ViewHolder>() {
private val othersList: Array<LauncherAction> = LauncherAction.values() private val othersList: Array<LauncherAction> =
LauncherAction.entries.filter { it.isAvailable(activity) }.toTypedArray()
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener { View.OnClickListener {

View file

@ -40,6 +40,11 @@ class SettingsFragmentLauncher : PreferenceFragmentCompat() {
) )
val lightTheme = LauncherPreferences.theme().colorTheme() == ColorTheme.LIGHT val lightTheme = LauncherPreferences.theme().colorTheme() == ColorTheme.LIGHT
background?.isVisible = !lightTheme background?.isVisible = !lightTheme
val hidePausedApps = findPreference<androidx.preference.Preference>(
LauncherPreferences.apps().keys().hidePausedApps()
)
hidePausedApps?.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
} }
override fun onStart() { override fun onStart() {

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path
android:fillColor="?android:textColor"
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM12,11.99h7c-0.53,4.12 -3.28,7.79 -7,8.94L12,12L5,12L5,6.3l7,-3.11v8.8z" />
</vector>

View file

@ -13,6 +13,7 @@
<string name="settings_apps_hidden_key" translatable="false">apps.hidden</string> <string name="settings_apps_hidden_key" translatable="false">apps.hidden</string>
<string name="settings_apps_custom_names_key" translatable="false">apps.custom_names</string> <string name="settings_apps_custom_names_key" translatable="false">apps.custom_names</string>
<string name="settings_apps_hide_bound_apps_key" translatable="false">apps.hide_bound_apps</string> <string name="settings_apps_hide_bound_apps_key" translatable="false">apps.hide_bound_apps</string>
<string name="settings_apps_hide_paused_apps_key" translatable="false">apps.hide_paused_apps</string>
<string name="settings_list_layout_key" translatable="false">list.layout</string> <string name="settings_list_layout_key" translatable="false">list.layout</string>
<string name="settings_general_choose_home_screen_key" translatable="false">general.select_launcher</string> <string name="settings_general_choose_home_screen_key" translatable="false">general.select_launcher</string>

View file

@ -145,6 +145,7 @@
<string name="settings_launcher_section_apps">Apps</string> <string name="settings_launcher_section_apps">Apps</string>
<string name="settings_apps_hidden">Hidden apps</string> <string name="settings_apps_hidden">Hidden apps</string>
<string name="settings_apps_hide_bound_apps">Don\'t show apps that are bound to a gesture in the app list</string> <string name="settings_apps_hide_bound_apps">Don\'t show apps that are bound to a gesture in the app list</string>
<string name="settings_apps_hide_paused_apps">Hide paused apps</string>
<string name="settings_list_layout">Layout of app list</string> <string name="settings_list_layout">Layout of app list</string>
<string name="settings_list_layout_item_default">Default</string> <string name="settings_list_layout_item_default">Default</string>
@ -207,6 +208,7 @@
<string name="list_other_settings">µLauncher Settings</string> <string name="list_other_settings">µLauncher Settings</string>
<string name="list_other_list">All Applications</string> <string name="list_other_list">All Applications</string>
<string name="list_other_list_favorites">Favorite Applications</string> <string name="list_other_list_favorites">Favorite Applications</string>
<string name="list_other_toggle_private_space_lock">Toggle Private Space Lock</string>
<string name="list_other_volume_up">Music: Louder</string> <string name="list_other_volume_up">Music: Louder</string>
<string name="list_other_volume_down">Music: Quieter</string> <string name="list_other_volume_down">Music: Quieter</string>
<string name="list_other_track_next">Music: Next</string> <string name="list_other_track_next">Music: Next</string>
@ -245,6 +247,7 @@
<string name="ic_menu_alt">More options</string> <string name="ic_menu_alt">More options</string>
<string name="alert_cant_expand_status_bar_panel">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.</string> <string name="alert_cant_expand_status_bar_panel">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.</string>
<string name="alert_requires_android_m">This functionality requires Android 6.0 or later.</string> <string name="alert_requires_android_m">This functionality requires Android 6.0 or later.</string>
<string name="alert_requires_android_v">This functionality requires Android 15.0 or later.</string>
<string name="snackbar_app_hidden">App hidden. You can make it visible again in settings.</string> <string name="snackbar_app_hidden">App hidden. You can make it visible again in settings.</string>
<string name="undo">Undo</string> <string name="undo">Undo</string>
<string name="list_other_expand_settings_panel">Quick Settings</string> <string name="list_other_expand_settings_panel">Quick Settings</string>
@ -255,6 +258,10 @@
<string name="alert_torch_access_exception">Error: Can\'t access torch.</string> <string name="alert_torch_access_exception">Error: Can\'t access torch.</string>
<string name="alert_lock_screen_failed">Error: Failed to lock screen. (If you just upgraded the app, try to disable and re-enable the accessibility service in phone settings)</string> <string name="alert_lock_screen_failed">Error: Failed to lock screen. (If you just upgraded the app, try to disable and re-enable the accessibility service in phone settings)</string>
<string name="toast_accessibility_service_not_enabled">μLauncher\'s accessibility service is not enabled. Please enable it in settings</string> <string name="toast_accessibility_service_not_enabled">μLauncher\'s accessibility service is not enabled. Please enable it in settings</string>
<string name="toast_private_space_locked">Private space locked</string>
<string name="toast_private_space_unlocked">Private space unlocked</string>
<string name="toast_private_space_not_available">Private space is not available</string>
<string name="toast_private_space_default_home_screen">µLauncher needs to be the default home screen to access private space.</string>
<string name="toast_lock_screen_not_supported">Error: Locking the screen using accessibility is not supported on this device. Please use device admin instead.</string> <string name="toast_lock_screen_not_supported">Error: Locking the screen using accessibility is not supported on this device. Please use device admin instead.</string>
<string name="accessibility_service_name">µLauncher - lock screen</string> <string name="accessibility_service_name">µLauncher - lock screen</string>
<string name="accessibility_service_description"> <string name="accessibility_service_description">

View file

@ -145,6 +145,11 @@
android:title="@string/settings_apps_hide_bound_apps" android:title="@string/settings_apps_hide_bound_apps"
android:defaultValue="false" /> android:defaultValue="false" />
<SwitchPreference
android:key="@string/settings_apps_hide_paused_apps_key"
android:title="@string/settings_apps_hide_paused_apps"
android:defaultValue="false" />
<DropDownPreference <DropDownPreference
android:key="@string/settings_list_layout_key" android:key="@string/settings_list_layout_key"
android:title="@string/settings_list_layout" android:title="@string/settings_list_layout"