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
This commit is contained in:
Aryan
2026-01-28 22:09:25 +05:30
committed by GitHub
parent 6244869a76
commit b95c9c218b
6 changed files with 45 additions and 63 deletions

View File

@@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR"/>
<uses-permission android:name="com.kieronquinn.app.smartspacer.permission.ACCESS_SMARTSPACER"/> <uses-permission android:name="com.kieronquinn.app.smartspacer.permission.ACCESS_SMARTSPACER"/>
<uses-sdk tools:overrideLibrary="com.kieronquinn.app.smartspacer.sdk,com.kieronquinn.app.smartspacer.sdk.client" /> <uses-sdk tools:overrideLibrary="com.kieronquinn.app.smartspacer.sdk,com.kieronquinn.app.smartspacer.sdk.client" />

View File

@@ -15,7 +15,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import de.mm20.launcher2.globalactions.GlobalActionsService 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.R
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
internal object NotificationsComponent : ScaffoldComponent(), KoinComponent { internal object NotificationsComponent : ScaffoldComponent(), KoinComponent {
private val permissionsManager: PermissionsManager by inject()
private val globalActionService: GlobalActionsService by inject() private val globalActionService: GlobalActionsService by inject()
override val permanent: Boolean override val permanent: Boolean = false
get() = !permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)
override val showSearchBar: Boolean = false override val showSearchBar: Boolean = false
@@ -56,19 +49,6 @@ internal object NotificationsComponent : ScaffoldComponent(), KoinComponent {
insets: PaddingValues, insets: PaddingValues,
state: LauncherScaffoldState 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( val scale by animateFloatAsState(
if (state.currentProgress >= 0.5f) 1.2f else 1f if (state.currentProgress >= 0.5f) 1.2f else 1f
) )
@@ -105,16 +85,12 @@ internal object NotificationsComponent : ScaffoldComponent(), KoinComponent {
override suspend fun onPreActivate(state: LauncherScaffoldState) { override suspend fun onPreActivate(state: LauncherScaffoldState) {
super.onPreActivate(state) super.onPreActivate(state)
if (permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) { globalActionService.openNotificationDrawer()
globalActionService.openNotificationDrawer()
}
} }
override suspend fun onActivate(state: LauncherScaffoldState) { override suspend fun onActivate(state: LauncherScaffoldState) {
super.onActivate(state) super.onActivate(state)
if (!permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) { state.navigateBack(false)
state.navigateBack(true)
}
} }
@SuppressLint("ModifierFactoryExtensionFunction") @SuppressLint("ModifierFactoryExtensionFunction")

View File

@@ -15,7 +15,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import de.mm20.launcher2.globalactions.GlobalActionsService 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.R
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
internal object QuickSettingsComponent : ScaffoldComponent(), KoinComponent { internal object QuickSettingsComponent : ScaffoldComponent(), KoinComponent {
private val permissionsManager: PermissionsManager by inject()
private val globalActionService: GlobalActionsService by inject() private val globalActionService: GlobalActionsService by inject()
override val permanent: Boolean override val permanent: Boolean = false
get() = !permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)
override val showSearchBar: Boolean = false override val showSearchBar: Boolean = false
@@ -56,19 +49,6 @@ internal object QuickSettingsComponent : ScaffoldComponent(), KoinComponent {
insets: PaddingValues, insets: PaddingValues,
state: LauncherScaffoldState 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( val scale by animateFloatAsState(
if (state.currentProgress >= 0.5f) 1.2f else 1f if (state.currentProgress >= 0.5f) 1.2f else 1f
) )
@@ -85,7 +65,10 @@ internal object QuickSettingsComponent : ScaffoldComponent(), KoinComponent {
.systemBarsPadding() .systemBarsPadding()
.padding(16.dp) .padding(16.dp)
.size(64.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) .scale(scale)
.shadow(4.dp, CircleShape) .shadow(4.dp, CircleShape)
.background(MaterialTheme.colorScheme.secondaryContainer, CircleShape), .background(MaterialTheme.colorScheme.secondaryContainer, CircleShape),
@@ -102,16 +85,14 @@ internal object QuickSettingsComponent : ScaffoldComponent(), KoinComponent {
override suspend fun onPreActivate(state: LauncherScaffoldState) { override suspend fun onPreActivate(state: LauncherScaffoldState) {
super.onPreActivate(state) super.onPreActivate(state)
if (permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
globalActionService.openQuickSettings() globalActionService.openQuickSettings()
}
} }
override suspend fun onActivate(state: LauncherScaffoldState) { override suspend fun onActivate(state: LauncherScaffoldState) {
super.onActivate(state) super.onActivate(state)
if (!permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) { state.navigateBack(false)
state.navigateBack(true)
}
} }
@SuppressLint("ModifierFactoryExtensionFunction") @SuppressLint("ModifierFactoryExtensionFunction")

View File

@@ -40,7 +40,7 @@ import de.mm20.launcher2.ui.ktx.toPixels
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data object GesturesSettingsRoute: NavKey data object GesturesSettingsRoute : NavKey
@Composable @Composable
fun GestureSettingsScreen() { fun GestureSettingsScreen() {
@@ -61,7 +61,8 @@ fun GestureSettingsScreen() {
add(stringResource(R.string.gesture_action_launch_app) to GestureAction.Launch(null)) 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 val context = LocalContext.current
@@ -229,9 +230,7 @@ fun GestureSettingsScreen() {
fun requiresAccessibilityService(action: GestureAction?): Boolean { fun requiresAccessibilityService(action: GestureAction?): Boolean {
return when (action) { return when (action) {
is GestureAction.Notifications,
is GestureAction.ScreenLock, is GestureAction.ScreenLock,
is GestureAction.QuickSettings,
is GestureAction.Recents, is GestureAction.Recents,
is GestureAction.PowerMenu -> true is GestureAction.PowerMenu -> true

View File

@@ -1,10 +1,15 @@
package de.mm20.launcher2.globalactions package de.mm20.launcher2.globalactions
import android.accessibilityservice.AccessibilityService import android.accessibilityservice.AccessibilityService
import android.content.Context
class GlobalActionsService { class GlobalActionsService(private val context: Context) {
fun openNotificationDrawer() { fun openNotificationDrawer() {
LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS) try {
expandNotificationPanel()
} catch (e: Exception) {
LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS)
}
} }
fun lockScreen() { fun lockScreen() {
@@ -12,7 +17,11 @@ class GlobalActionsService {
} }
fun openQuickSettings() { 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() { fun openPowerDialog() {
@@ -22,4 +31,19 @@ class GlobalActionsService {
fun openRecents() { fun openRecents() {
LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS) 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)
}
} }

View File

@@ -1,7 +1,8 @@
package de.mm20.launcher2.globalactions package de.mm20.launcher2.globalactions
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
val globalActionsModule = module { val globalActionsModule = module {
single { GlobalActionsService() } single { GlobalActionsService(androidContext()) }
} }