From b95c9c218b3cee2a6e9946524797558d4c5dcecf Mon Sep 17 00:00:00 2001
From: Aryan <143314529+Aryan0577@users.noreply.github.com>
Date: Wed, 28 Jan 2026 22:09:25 +0530
Subject: [PATCH] feat: Remove Accessibility requirement for Notification and
Quick Settings gestures (#1784)
* Replace Accessibility Service with reflection for Notification and Quick Settings actions
* move reflection logic to GlobalActionsService with fallback
---
app/ui/src/main/AndroidManifest.xml | 1 +
.../scaffold/NotificationsComponent.kt | 30 ++-------------
.../scaffold/QuickSettingsComponent.kt | 37 +++++--------------
.../gestures/GestureSettingsScreen.kt | 7 ++--
.../globalactions/GlobalActionsService.kt | 30 +++++++++++++--
.../de/mm20/launcher2/globalactions/Module.kt | 3 +-
6 files changed, 45 insertions(+), 63 deletions(-)
diff --git a/app/ui/src/main/AndroidManifest.xml b/app/ui/src/main/AndroidManifest.xml
index d70660ba4..4eac699dc 100644
--- a/app/ui/src/main/AndroidManifest.xml
+++ b/app/ui/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/NotificationsComponent.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/NotificationsComponent.kt
index fa64d48d8..eb947ebce 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/NotificationsComponent.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/NotificationsComponent.kt
@@ -15,7 +15,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -28,21 +27,15 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import de.mm20.launcher2.globalactions.GlobalActionsService
-import de.mm20.launcher2.permissions.PermissionGroup
-import de.mm20.launcher2.permissions.PermissionsManager
-import de.mm20.launcher2.preferences.GestureAction
import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
internal object NotificationsComponent : ScaffoldComponent(), KoinComponent {
- private val permissionsManager: PermissionsManager by inject()
private val globalActionService: GlobalActionsService by inject()
- override val permanent: Boolean
- get() = !permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)
+ override val permanent: Boolean = false
override val showSearchBar: Boolean = false
@@ -56,19 +49,6 @@ internal object NotificationsComponent : ScaffoldComponent(), KoinComponent {
insets: PaddingValues,
state: LauncherScaffoldState
) {
- if (isActive) {
- val bottomSheetManager = LocalBottomSheetManager.current
- LaunchedEffect(Unit) {
- val gesture = state.currentGesture ?: return@LaunchedEffect
- if (!permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
- bottomSheetManager.showFailedGestureSheet(
- gesture = gesture,
- action = GestureAction.Notifications,
- )
- }
- }
- }
-
val scale by animateFloatAsState(
if (state.currentProgress >= 0.5f) 1.2f else 1f
)
@@ -105,16 +85,12 @@ internal object NotificationsComponent : ScaffoldComponent(), KoinComponent {
override suspend fun onPreActivate(state: LauncherScaffoldState) {
super.onPreActivate(state)
- if (permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
- globalActionService.openNotificationDrawer()
- }
+ globalActionService.openNotificationDrawer()
}
override suspend fun onActivate(state: LauncherScaffoldState) {
super.onActivate(state)
- if (!permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
- state.navigateBack(true)
- }
+ state.navigateBack(false)
}
@SuppressLint("ModifierFactoryExtensionFunction")
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/QuickSettingsComponent.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/QuickSettingsComponent.kt
index be5666727..20703897a 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/QuickSettingsComponent.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/scaffold/QuickSettingsComponent.kt
@@ -15,7 +15,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -28,21 +27,15 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import de.mm20.launcher2.globalactions.GlobalActionsService
-import de.mm20.launcher2.permissions.PermissionGroup
-import de.mm20.launcher2.permissions.PermissionsManager
-import de.mm20.launcher2.preferences.GestureAction
import de.mm20.launcher2.ui.R
-import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
internal object QuickSettingsComponent : ScaffoldComponent(), KoinComponent {
- private val permissionsManager: PermissionsManager by inject()
private val globalActionService: GlobalActionsService by inject()
- override val permanent: Boolean
- get() = !permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)
+ override val permanent: Boolean = false
override val showSearchBar: Boolean = false
@@ -56,19 +49,6 @@ internal object QuickSettingsComponent : ScaffoldComponent(), KoinComponent {
insets: PaddingValues,
state: LauncherScaffoldState
) {
- if (isActive) {
- val bottomSheetManager = LocalBottomSheetManager.current
- LaunchedEffect(Unit) {
- val gesture = state.currentGesture ?: return@LaunchedEffect
- if (!permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
- bottomSheetManager.showFailedGestureSheet(
- gesture = gesture,
- action = GestureAction.QuickSettings,
- )
- }
- }
- }
-
val scale by animateFloatAsState(
if (state.currentProgress >= 0.5f) 1.2f else 1f
)
@@ -85,7 +65,10 @@ internal object QuickSettingsComponent : ScaffoldComponent(), KoinComponent {
.systemBarsPadding()
.padding(16.dp)
.size(64.dp)
- .offset(y = -134.dp * interpolator.getInterpolation(1f - state.currentProgress * 2f).coerceAtLeast(0f))
+ .offset(
+ y = -134.dp * interpolator.getInterpolation(1f - state.currentProgress * 2f)
+ .coerceAtLeast(0f)
+ )
.scale(scale)
.shadow(4.dp, CircleShape)
.background(MaterialTheme.colorScheme.secondaryContainer, CircleShape),
@@ -102,16 +85,14 @@ internal object QuickSettingsComponent : ScaffoldComponent(), KoinComponent {
override suspend fun onPreActivate(state: LauncherScaffoldState) {
super.onPreActivate(state)
- if (permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
- globalActionService.openQuickSettings()
- }
+
+ globalActionService.openQuickSettings()
+
}
override suspend fun onActivate(state: LauncherScaffoldState) {
super.onActivate(state)
- if (!permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
- state.navigateBack(true)
- }
+ state.navigateBack(false)
}
@SuppressLint("ModifierFactoryExtensionFunction")
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/gestures/GestureSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/gestures/GestureSettingsScreen.kt
index bff5b5944..69b90494e 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/gestures/GestureSettingsScreen.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/gestures/GestureSettingsScreen.kt
@@ -40,7 +40,7 @@ import de.mm20.launcher2.ui.ktx.toPixels
import kotlinx.serialization.Serializable
@Serializable
-data object GesturesSettingsRoute: NavKey
+data object GesturesSettingsRoute : NavKey
@Composable
fun GestureSettingsScreen() {
@@ -61,7 +61,8 @@ fun GestureSettingsScreen() {
add(stringResource(R.string.gesture_action_launch_app) to GestureAction.Launch(null))
}
- val optionsWithFeed = options + (stringResource(R.string.gesture_action_feed) to GestureAction.Feed)
+ val optionsWithFeed =
+ options + (stringResource(R.string.gesture_action_feed) to GestureAction.Feed)
val context = LocalContext.current
@@ -229,9 +230,7 @@ fun GestureSettingsScreen() {
fun requiresAccessibilityService(action: GestureAction?): Boolean {
return when (action) {
- is GestureAction.Notifications,
is GestureAction.ScreenLock,
- is GestureAction.QuickSettings,
is GestureAction.Recents,
is GestureAction.PowerMenu -> true
diff --git a/services/global-actions/src/main/java/de/mm20/launcher2/globalactions/GlobalActionsService.kt b/services/global-actions/src/main/java/de/mm20/launcher2/globalactions/GlobalActionsService.kt
index 0a1b04428..0c8a7ad02 100644
--- a/services/global-actions/src/main/java/de/mm20/launcher2/globalactions/GlobalActionsService.kt
+++ b/services/global-actions/src/main/java/de/mm20/launcher2/globalactions/GlobalActionsService.kt
@@ -1,10 +1,15 @@
package de.mm20.launcher2.globalactions
import android.accessibilityservice.AccessibilityService
+import android.content.Context
-class GlobalActionsService {
+class GlobalActionsService(private val context: Context) {
fun openNotificationDrawer() {
- LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS)
+ try {
+ expandNotificationPanel()
+ } catch (e: Exception) {
+ LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS)
+ }
}
fun lockScreen() {
@@ -12,7 +17,11 @@ class GlobalActionsService {
}
fun openQuickSettings() {
- LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS)
+ try {
+ expandQuickSettings()
+ } catch (e: Exception) {
+ LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS)
+ }
}
fun openPowerDialog() {
@@ -22,4 +31,19 @@ class GlobalActionsService {
fun openRecents() {
LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
}
+
+ private fun expandNotificationPanel() {
+ val statusBarService = context.getSystemService("statusbar")
+ val statusBarManager = Class.forName("android.app.StatusBarManager")
+ val method = statusBarManager.getMethod("expandNotificationsPanel")
+ method.invoke(statusBarService)
+ }
+
+ private fun expandQuickSettings() {
+ val statusBarService = context.getSystemService("statusbar")
+ val statusBarManager = Class.forName("android.app.StatusBarManager")
+ val method = statusBarManager.getMethod("expandSettingsPanel")
+ method.invoke(statusBarService)
+ }
+
}
\ No newline at end of file
diff --git a/services/global-actions/src/main/java/de/mm20/launcher2/globalactions/Module.kt b/services/global-actions/src/main/java/de/mm20/launcher2/globalactions/Module.kt
index 4219d0619..86de12c37 100644
--- a/services/global-actions/src/main/java/de/mm20/launcher2/globalactions/Module.kt
+++ b/services/global-actions/src/main/java/de/mm20/launcher2/globalactions/Module.kt
@@ -1,7 +1,8 @@
package de.mm20.launcher2.globalactions
+import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val globalActionsModule = module {
- single { GlobalActionsService() }
+ single { GlobalActionsService(androidContext()) }
}
\ No newline at end of file