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:
@@ -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" />
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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()) }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user