Gestures
This commit is contained in:
parent
4fba96a75a
commit
c12c07fa45
@ -137,6 +137,7 @@ dependencies {
|
||||
implementation(project(":data:wikipedia"))
|
||||
implementation(project(":core:database"))
|
||||
implementation(project(":data:search-actions"))
|
||||
implementation(project(":services:global-actions"))
|
||||
|
||||
// Uncomment this if you want annoying notifications in your debug builds yelling at you how terrible your code is
|
||||
//debugImplementation(libs.leakcanary)
|
||||
|
||||
@ -24,6 +24,7 @@ import de.mm20.launcher2.websites.websitesModule
|
||||
import de.mm20.launcher2.widgets.widgetsModule
|
||||
import de.mm20.launcher2.wikipedia.wikipediaModule
|
||||
import de.mm20.launcher2.database.databaseModule
|
||||
import de.mm20.launcher2.globalactions.globalActionsModule
|
||||
import de.mm20.launcher2.notifications.notificationsModule
|
||||
import de.mm20.launcher2.permissions.permissionsModule
|
||||
import de.mm20.launcher2.preferences.preferencesModule
|
||||
@ -64,6 +65,7 @@ class LauncherApplication : Application(), CoroutineScope, ImageLoaderFactory {
|
||||
databaseModule,
|
||||
favoritesModule,
|
||||
filesModule,
|
||||
globalActionsModule,
|
||||
iconsModule,
|
||||
musicModule,
|
||||
notificationsModule,
|
||||
|
||||
@ -143,4 +143,5 @@ dependencies {
|
||||
implementation(project(":services:accounts"))
|
||||
implementation(project(":services:backup"))
|
||||
implementation(project(":data:search-actions"))
|
||||
implementation(project(":services:global-actions"))
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package de.mm20.launcher2.ui.gestures
|
||||
|
||||
enum class Gesture {
|
||||
DoubleTap,
|
||||
LongPress,
|
||||
SwipeDown,
|
||||
SwipeLeft,
|
||||
SwipeRight,
|
||||
}
|
||||
@ -1,12 +1,23 @@
|
||||
package de.mm20.launcher2.ui.launcher
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.globalactions.GlobalActionsService
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.preferences.Settings.GestureSettings
|
||||
import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
|
||||
import de.mm20.launcher2.ui.gestures.Gesture
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
@ -17,6 +28,19 @@ import org.koin.core.component.inject
|
||||
class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val globalActionsService: GlobalActionsService by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
|
||||
private var gestureSettings : GestureSettings? = null
|
||||
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
dataStore.data.map { it.gestures }.collectLatest {
|
||||
gestureSettings = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var isSystemInDarkMode = MutableStateFlow(false)
|
||||
|
||||
@ -84,4 +108,68 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
||||
val fillClockHeight = dataStore.data.map { it.clockWidget.fillHeight }.asLiveData()
|
||||
val searchBarColor = dataStore.data.map { it.searchBar.color }.asLiveData()
|
||||
val searchBarStyle = dataStore.data.map { it.searchBar.searchBarStyle }.asLiveData()
|
||||
}
|
||||
|
||||
|
||||
var failedGestureState by mutableStateOf<FailedGesture?>(null)
|
||||
fun handleGesture(gesture: Gesture): Boolean {
|
||||
val action = when (gesture) {
|
||||
Gesture.DoubleTap -> gestureSettings?.doubleTap
|
||||
Gesture.LongPress -> gestureSettings?.longPress
|
||||
Gesture.SwipeDown -> gestureSettings?.swipeDown?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PullDown }
|
||||
Gesture.SwipeLeft -> gestureSettings?.swipeLeft?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.Pager }
|
||||
Gesture.SwipeRight -> gestureSettings?.swipeRight?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PagerReversed }
|
||||
}
|
||||
val requiresAccessibilityService =
|
||||
action == GestureAction.OpenRecents
|
||||
|| action == GestureAction.OpenPowerDialog
|
||||
|| action == GestureAction.OpenQuickSettings
|
||||
|| action == GestureAction.OpenNotificationDrawer
|
||||
|| action == GestureAction.LockScreen
|
||||
|
||||
if (action != null && requiresAccessibilityService && !permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
|
||||
failedGestureState = FailedGesture(gesture, action)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
return when (action) {
|
||||
GestureAction.OpenSearch -> {
|
||||
openSearch()
|
||||
true
|
||||
}
|
||||
|
||||
GestureAction.OpenNotificationDrawer -> {
|
||||
globalActionsService.openNotificationDrawer()
|
||||
true
|
||||
}
|
||||
|
||||
GestureAction.OpenQuickSettings -> {
|
||||
globalActionsService.openQuickSettings()
|
||||
true
|
||||
}
|
||||
|
||||
GestureAction.LockScreen -> {
|
||||
globalActionsService.lockScreen()
|
||||
true
|
||||
}
|
||||
|
||||
GestureAction.OpenPowerDialog -> {
|
||||
globalActionsService.openPowerDialog()
|
||||
true
|
||||
}
|
||||
|
||||
GestureAction.OpenRecents -> {
|
||||
globalActionsService.openRecents()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissGestureFailedSheet() {
|
||||
failedGestureState = null
|
||||
}
|
||||
}
|
||||
|
||||
data class FailedGesture(val gesture: Gesture, val action: GestureAction)
|
||||
@ -4,7 +4,6 @@ import android.app.WallpaperManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.background
|
||||
@ -37,6 +36,7 @@ import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import de.mm20.launcher2.globalactions.GlobalActionsService
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.preferences.Settings.SystemBarsSettings.SystemBarColors
|
||||
import de.mm20.launcher2.ui.assistant.AssistantScaffold
|
||||
@ -44,13 +44,14 @@ import de.mm20.launcher2.ui.base.BaseActivity
|
||||
import de.mm20.launcher2.ui.base.ProvideCurrentTime
|
||||
import de.mm20.launcher2.ui.base.ProvideSettings
|
||||
import de.mm20.launcher2.ui.component.NavBarEffects
|
||||
import de.mm20.launcher2.ui.gestures.Gesture
|
||||
import de.mm20.launcher2.ui.gestures.GestureDetector
|
||||
import de.mm20.launcher2.ui.gestures.GestureHandler
|
||||
import de.mm20.launcher2.ui.gestures.LocalGestureDetector
|
||||
import de.mm20.launcher2.ui.ktx.animateTo
|
||||
import de.mm20.launcher2.ui.ktx.toPixels
|
||||
import de.mm20.launcher2.ui.launcher.sheets.CustomizeSearchableSheet
|
||||
import de.mm20.launcher2.ui.launcher.sheets.EditFavoritesSheet
|
||||
import de.mm20.launcher2.ui.launcher.sheets.FailedGestureSheet
|
||||
import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheets
|
||||
import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheetManager
|
||||
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
|
||||
import de.mm20.launcher2.ui.launcher.transitions.HomeTransition
|
||||
@ -62,6 +63,7 @@ import de.mm20.launcher2.ui.locals.LocalWallpaperColors
|
||||
import de.mm20.launcher2.ui.locals.LocalWindowSize
|
||||
import de.mm20.launcher2.ui.theme.LauncherTheme
|
||||
import de.mm20.launcher2.ui.theme.wallpaperColorsAsState
|
||||
import org.koin.android.ext.android.inject
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
@ -70,7 +72,9 @@ abstract class SharedLauncherActivity(
|
||||
private val mode: LauncherActivityMode
|
||||
) : BaseActivity() {
|
||||
|
||||
private val viewModel: LauncherActivityVM by viewModels()
|
||||
private val viewModel: LauncherScaffoldVM by viewModels()
|
||||
|
||||
private val globalActionsService: GlobalActionsService by inject()
|
||||
|
||||
internal val homeTransitionManager = HomeTransitionManager()
|
||||
|
||||
@ -256,52 +260,43 @@ abstract class SharedLauncherActivity(
|
||||
) { enterTransitionProgress.value }
|
||||
}
|
||||
}
|
||||
|
||||
bottomSheetManager.customizeSearchableSheetShown.value?.let {
|
||||
CustomizeSearchableSheet(
|
||||
searchable = it,
|
||||
onDismiss = { bottomSheetManager.dismissCustomizeSearchableModal() })
|
||||
}
|
||||
if (bottomSheetManager.editFavoritesSheetShown.value) {
|
||||
EditFavoritesSheet(onDismiss = { bottomSheetManager.dismissEditFavoritesSheet() })
|
||||
}
|
||||
LauncherBottomSheets()
|
||||
}
|
||||
|
||||
val swipeThreshold = 150.dp.toPixels()
|
||||
GestureHandler(
|
||||
detector = gestureDetector,
|
||||
onDoubleTap = {
|
||||
Log.d("MM20", "Double tap")
|
||||
viewModel.handleGesture(Gesture.DoubleTap)
|
||||
},
|
||||
onLongPress = {
|
||||
Log.d("MM20", "Long press")
|
||||
viewModel.handleGesture(Gesture.LongPress)
|
||||
},
|
||||
onDrag = {
|
||||
return@GestureHandler when {
|
||||
it.x > swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> {
|
||||
Log.d("MM20", "Swipe right")
|
||||
true
|
||||
viewModel.handleGesture(Gesture.SwipeRight)
|
||||
}
|
||||
|
||||
it.x < -swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> {
|
||||
Log.d("MM20", "Swipe left")
|
||||
true
|
||||
viewModel.handleGesture(Gesture.SwipeLeft)
|
||||
}
|
||||
|
||||
it.y > swipeThreshold && it.y.absoluteValue > it.x.absoluteValue * 2f -> {
|
||||
Log.d("MM20", "Swipe down")
|
||||
true
|
||||
viewModel.handleGesture(Gesture.SwipeDown)
|
||||
}
|
||||
|
||||
it.y < -swipeThreshold && it.y.absoluteValue > it.x.absoluteValue * 2f -> {
|
||||
Log.d("MM20", "Swipe up")
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
)
|
||||
if (viewModel.failedGestureState != null) {
|
||||
FailedGestureSheet(
|
||||
failedGesture = viewModel.failedGestureState!!,
|
||||
onDismiss = {
|
||||
viewModel.dismissGestureFailedSheet()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
package de.mm20.launcher2.ui.launcher.sheets
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.gestures.Gesture
|
||||
import de.mm20.launcher2.ui.launcher.FailedGesture
|
||||
|
||||
@Composable
|
||||
fun FailedGestureSheet(
|
||||
failedGesture: FailedGesture,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val viewModel: FailedGestureSheetVM = viewModel()
|
||||
|
||||
val actionName = stringResource(when(failedGesture.action) {
|
||||
Settings.GestureSettings.GestureAction.OpenSearch -> R.string.gesture_action_open_search
|
||||
Settings.GestureSettings.GestureAction.OpenNotificationDrawer -> R.string.gesture_action_notifications
|
||||
Settings.GestureSettings.GestureAction.LockScreen -> R.string.gesture_action_lock_screen
|
||||
Settings.GestureSettings.GestureAction.OpenQuickSettings -> R.string.gesture_action_quick_settings
|
||||
Settings.GestureSettings.GestureAction.OpenRecents -> R.string.gesture_action_recents
|
||||
Settings.GestureSettings.GestureAction.OpenPowerDialog -> R.string.gesture_action_power_menu
|
||||
else -> R.string.gesture_action_none
|
||||
})
|
||||
val gestureName = stringResource(when(failedGesture.gesture) {
|
||||
Gesture.DoubleTap -> R.string.preference_gesture_double_tap
|
||||
Gesture.LongPress -> R.string.preference_gesture_long_press
|
||||
Gesture.SwipeDown -> R.string.preference_gesture_swipe_down
|
||||
Gesture.SwipeLeft -> R.string.preference_gesture_swipe_left
|
||||
Gesture.SwipeRight -> R.string.preference_gesture_swipe_right
|
||||
})
|
||||
|
||||
BottomSheetDialog(
|
||||
title = { Text(actionName) },
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(it)
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.gesture_failed_message, gestureName, actionName),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
val context = LocalLifecycleOwner.current
|
||||
MissingPermissionBanner(
|
||||
modifier = Modifier.padding(vertical = 16.dp),
|
||||
text = stringResource(id = R.string.missing_permission_accessibility_gesture_failed),
|
||||
secondaryAction = {
|
||||
OutlinedButton(onClick = {
|
||||
viewModel.disableGesture(failedGesture.gesture)
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.turn_off))
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
viewModel.requestPermission(context as AppCompatActivity)
|
||||
onDismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package de.mm20.launcher2.ui.launcher.sheets
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
|
||||
import de.mm20.launcher2.ui.gestures.Gesture
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class FailedGestureSheetVM : ViewModel(), KoinComponent {
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
fun requestPermission(context: AppCompatActivity) {
|
||||
permissionsManager.requestPermission(context, PermissionGroup.Accessibility)
|
||||
}
|
||||
|
||||
fun disableGesture(gesture: Gesture) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder().setGestures(
|
||||
it.gestures.toBuilder().apply {
|
||||
when (gesture) {
|
||||
Gesture.SwipeDown -> swipeDown = GestureAction.None
|
||||
Gesture.SwipeLeft -> swipeLeft = GestureAction.None
|
||||
Gesture.SwipeRight -> swipeRight = GestureAction.None
|
||||
Gesture.DoubleTap -> doubleTap = GestureAction.None
|
||||
Gesture.LongPress -> longPress = GestureAction.None
|
||||
}
|
||||
}.build()
|
||||
).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package de.mm20.launcher2.ui.launcher.sheets
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun LauncherBottomSheets() {
|
||||
val bottomSheetManager = LocalBottomSheetManager.current
|
||||
bottomSheetManager.customizeSearchableSheetShown.value?.let {
|
||||
CustomizeSearchableSheet(
|
||||
searchable = it,
|
||||
onDismiss = { bottomSheetManager.dismissCustomizeSearchableModal() })
|
||||
}
|
||||
if (bottomSheetManager.editFavoritesSheetShown.value) {
|
||||
EditFavoritesSheet(onDismiss = { bottomSheetManager.dismissEditFavoritesSheet() })
|
||||
}
|
||||
}
|
||||
@ -39,6 +39,7 @@ import de.mm20.launcher2.ui.settings.debug.DebugSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.easteregg.EasterEggSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.favorites.FavoritesSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.filesearch.FileSearchSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.gestures.GestureSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.hiddenitems.HiddenItemsSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.layout.LayoutSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.license.LicenseScreen
|
||||
@ -115,6 +116,9 @@ class SettingsActivity : BaseActivity() {
|
||||
composable("settings/search") {
|
||||
SearchSettingsScreen()
|
||||
}
|
||||
composable("settings/gestures") {
|
||||
GestureSettingsScreen()
|
||||
}
|
||||
composable("settings/search/unitconverter") {
|
||||
UnitConverterSettingsScreen()
|
||||
}
|
||||
|
||||
@ -0,0 +1,131 @@
|
||||
package de.mm20.launcher2.ui.settings.gestures
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
|
||||
import de.mm20.launcher2.preferences.Settings.LayoutSettings.Layout
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.component.preferences.ListPreference
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
|
||||
@Composable
|
||||
fun GestureSettingsScreen() {
|
||||
val viewModel: GestureSettingsScreenVM = viewModel()
|
||||
|
||||
val layout by viewModel.layout.observeAsState()
|
||||
val hasPermission by viewModel.hasPermission.observeAsState()
|
||||
|
||||
val options = buildList {
|
||||
add(stringResource(R.string.gesture_action_none) to GestureAction.None)
|
||||
add(stringResource(R.string.gesture_action_notifications) to GestureAction.OpenNotificationDrawer)
|
||||
add(stringResource(R.string.gesture_action_quick_settings) to GestureAction.OpenQuickSettings)
|
||||
if (isAtLeastApiLevel(28)) add(stringResource(R.string.gesture_action_lock_screen) to GestureAction.LockScreen)
|
||||
add(stringResource(R.string.gesture_action_recents) to GestureAction.OpenRecents)
|
||||
add(stringResource(R.string.gesture_action_power_menu) to GestureAction.OpenPowerDialog)
|
||||
add(stringResource(R.string.gesture_action_open_search) to GestureAction.OpenSearch)
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
PreferenceScreen(title = stringResource(R.string.preference_screen_gestures)) {
|
||||
item {
|
||||
PreferenceCategory {
|
||||
val doubleTap by viewModel.doubleTap.observeAsState()
|
||||
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(doubleTap)) {
|
||||
MissingPermissionBanner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
|
||||
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
|
||||
)
|
||||
}
|
||||
ListPreference(
|
||||
title = stringResource(R.string.preference_gesture_double_tap),
|
||||
items = options,
|
||||
value = doubleTap,
|
||||
onValueChanged = { if (it != null) viewModel.setDoubleTap(it) }
|
||||
)
|
||||
val longPress by viewModel.longPress.observeAsState()
|
||||
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(longPress)) {
|
||||
MissingPermissionBanner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
|
||||
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
|
||||
)
|
||||
}
|
||||
ListPreference(
|
||||
title = stringResource(R.string.preference_gesture_long_press),
|
||||
items = options,
|
||||
value = longPress,
|
||||
onValueChanged = { if (it != null) viewModel.setLongPress(it) }
|
||||
)
|
||||
val swipeDown by viewModel.swipeDown.observeAsState()
|
||||
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeDown)) {
|
||||
MissingPermissionBanner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
|
||||
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
|
||||
)
|
||||
}
|
||||
ListPreference(
|
||||
title = stringResource(R.string.preference_gesture_swipe_down),
|
||||
enabled = layout != Layout.PullDown,
|
||||
items = options,
|
||||
value = if (layout == Layout.PullDown) GestureAction.OpenSearch else swipeDown,
|
||||
onValueChanged = { if (it != null) viewModel.setSwipeDown(it) }
|
||||
)
|
||||
val swipeLeft by viewModel.swipeLeft.observeAsState()
|
||||
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeLeft)) {
|
||||
MissingPermissionBanner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
|
||||
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
|
||||
)
|
||||
}
|
||||
ListPreference(
|
||||
title = stringResource(R.string.preference_gesture_swipe_left),
|
||||
enabled = layout != Layout.Pager,
|
||||
items = options,
|
||||
value = if (layout == Layout.Pager) GestureAction.OpenSearch else swipeLeft,
|
||||
onValueChanged = { if (it != null) viewModel.setSwipeLeft(it) }
|
||||
)
|
||||
val swipeRight by viewModel.swipeRight.observeAsState()
|
||||
AnimatedVisibility(hasPermission == false && requiresAccessibilityService(swipeRight)) {
|
||||
MissingPermissionBanner(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text = stringResource(R.string.missing_permission_accessibility_gesture_settings),
|
||||
onClick = { viewModel.requestPermission(context as AppCompatActivity) }
|
||||
)
|
||||
}
|
||||
ListPreference(
|
||||
title = stringResource(R.string.preference_gesture_swipe_right),
|
||||
enabled = layout != Layout.PagerReversed,
|
||||
items = options,
|
||||
value = if (layout == Layout.PagerReversed) GestureAction.OpenSearch else swipeRight,
|
||||
onValueChanged = { if (it != null) viewModel.setSwipeRight(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun requiresAccessibilityService(action: GestureAction?) : Boolean{
|
||||
return when(action) {
|
||||
GestureAction.OpenNotificationDrawer,
|
||||
GestureAction.LockScreen,
|
||||
GestureAction.OpenQuickSettings,
|
||||
GestureAction.OpenRecents,
|
||||
GestureAction.OpenPowerDialog -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package de.mm20.launcher2.ui.settings.gestures
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class GestureSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
|
||||
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Accessibility).asLiveData()
|
||||
|
||||
val layout = dataStore.data.map { it.layout.baseLayout }.asLiveData()
|
||||
|
||||
val swipeDown = dataStore.data.map { it.gestures.swipeDown }.asLiveData()
|
||||
val swipeLeft = dataStore.data.map { it.gestures.swipeLeft }.asLiveData()
|
||||
val swipeRight = dataStore.data.map { it.gestures.swipeRight }.asLiveData()
|
||||
val doubleTap = dataStore.data.map { it.gestures.doubleTap }.asLiveData()
|
||||
val longPress = dataStore.data.map { it.gestures.longPress }.asLiveData()
|
||||
|
||||
fun setSwipeDown(action: GestureAction) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder().setGestures(it.gestures.toBuilder().setSwipeDown(action).build())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSwipeLeft(action: GestureAction) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder().setGestures(it.gestures.toBuilder().setSwipeLeft(action).build())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSwipeRight(action: GestureAction) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder().setGestures(it.gestures.toBuilder().setSwipeRight(action).build())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setDoubleTap(action: GestureAction) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder().setGestures(it.gestures.toBuilder().setDoubleTap(action).build())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setLongPress(action: GestureAction) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder().setGestures(it.gestures.toBuilder().setLongPress(action).build())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun requestPermission(context: AppCompatActivity) {
|
||||
permissionsManager.requestPermission(context, PermissionGroup.Accessibility)
|
||||
}
|
||||
}
|
||||
@ -46,6 +46,14 @@ fun MainSettingsScreen() {
|
||||
navController?.navigate("settings/widgets")
|
||||
}
|
||||
)
|
||||
Preference(
|
||||
icon = Icons.Rounded.Gesture,
|
||||
title = stringResource(id = R.string.preference_screen_gestures),
|
||||
summary = stringResource(id = R.string.preference_screen_gestures_summary),
|
||||
onClick = {
|
||||
navController?.navigate("settings/gestures")
|
||||
}
|
||||
)
|
||||
Preference(
|
||||
icon = Icons.Rounded.NotificationBadge,
|
||||
title = stringResource(id = R.string.preference_screen_badges),
|
||||
|
||||
@ -732,4 +732,21 @@
|
||||
<string name="preference_layout_search_results">Arrangement of search results</string>
|
||||
<string name="search_results_order_top_down">Top-down</string>
|
||||
<string name="search_results_order_bottom_up">Bottom-up</string>
|
||||
<string name="preference_screen_gestures">Gestures</string>
|
||||
<string name="preference_screen_gestures_summary">Gestures</string>
|
||||
<string name="preference_gesture_swipe_down">Swipe down</string>
|
||||
<string name="preference_gesture_swipe_left">Swipe left</string>
|
||||
<string name="preference_gesture_swipe_right">Swipe right</string>
|
||||
<string name="preference_gesture_double_tap">Double tap</string>
|
||||
<string name="preference_gesture_long_press">Long press</string>
|
||||
<string name="gesture_action_none">None</string>
|
||||
<string name="gesture_action_open_search">Open search</string>
|
||||
<string name="gesture_action_notifications">Open notification drawer</string>
|
||||
<string name="gesture_action_lock_screen">Turn off screen</string>
|
||||
<string name="gesture_action_quick_settings">Open quick settings</string>
|
||||
<string name="gesture_action_power_menu">Show power menu</string>
|
||||
<string name="gesture_action_recents">Open recents</string>
|
||||
<string name="gesture_failed_message">You have performed a \"%1$s\" gesture. This gesture is currently set to trigger a \"%2$s\" action. However, the action could not be performed for the following reason:</string>
|
||||
<string name="missing_permission_accessibility_gesture_failed">The launcher\'s accessibility service needs to be enabled to perform this action.</string>
|
||||
<string name="missing_permission_accessibility_gesture_settings">This action requires the launcher\'s accessibility service to be enabled.</string>
|
||||
</resources>
|
||||
@ -47,6 +47,12 @@ interface PermissionsManager {
|
||||
* May not be called by anything else.
|
||||
*/
|
||||
fun reportNotificationListenerState(running: Boolean)
|
||||
|
||||
/**
|
||||
* Special function for the accessibility service to report its status.
|
||||
* May not be called by anything else.
|
||||
*/
|
||||
fun reportAccessibilityServiceState(running: Boolean)
|
||||
}
|
||||
|
||||
enum class PermissionGroup {
|
||||
@ -56,6 +62,7 @@ enum class PermissionGroup {
|
||||
ExternalStorage,
|
||||
Notifications,
|
||||
AppShortcuts,
|
||||
Accessibility,
|
||||
}
|
||||
|
||||
internal class PermissionsManagerImpl(
|
||||
@ -77,6 +84,7 @@ internal class PermissionsManagerImpl(
|
||||
checkPermissionOnce(PermissionGroup.Location)
|
||||
)
|
||||
private val notificationsPermissionState = MutableStateFlow(false)
|
||||
private val accessibilityPermissionState = MutableStateFlow(false)
|
||||
private val appShortcutsPermissionState = MutableStateFlow(
|
||||
checkPermissionOnce(PermissionGroup.AppShortcuts)
|
||||
)
|
||||
@ -90,6 +98,7 @@ internal class PermissionsManagerImpl(
|
||||
permissionGroup.ordinal
|
||||
)
|
||||
}
|
||||
|
||||
PermissionGroup.Location -> {
|
||||
ActivityCompat.requestPermissions(
|
||||
context,
|
||||
@ -97,6 +106,7 @@ internal class PermissionsManagerImpl(
|
||||
permissionGroup.ordinal
|
||||
)
|
||||
}
|
||||
|
||||
PermissionGroup.Contacts -> {
|
||||
ActivityCompat.requestPermissions(
|
||||
context,
|
||||
@ -104,6 +114,7 @@ internal class PermissionsManagerImpl(
|
||||
permissionGroup.ordinal
|
||||
)
|
||||
}
|
||||
|
||||
PermissionGroup.ExternalStorage -> {
|
||||
if (isAtLeastApiLevel(Build.VERSION_CODES.R)) {
|
||||
val intent =
|
||||
@ -120,6 +131,7 @@ internal class PermissionsManagerImpl(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PermissionGroup.Notifications -> {
|
||||
try {
|
||||
context.startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
|
||||
@ -127,10 +139,20 @@ internal class PermissionsManagerImpl(
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
|
||||
PermissionGroup.AppShortcuts -> {
|
||||
context.tryStartActivity(Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS))
|
||||
pendingPermissionRequests.add(PermissionGroup.AppShortcuts)
|
||||
}
|
||||
|
||||
PermissionGroup.Accessibility -> {
|
||||
try {
|
||||
context.tryStartActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
|
||||
pendingPermissionRequests.add(PermissionGroup.Accessibility)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,12 +161,15 @@ internal class PermissionsManagerImpl(
|
||||
PermissionGroup.Calendar -> {
|
||||
calendarPermissions.all { context.checkPermission(it) }
|
||||
}
|
||||
|
||||
PermissionGroup.Location -> {
|
||||
locationPermissions.all { context.checkPermission(it) }
|
||||
}
|
||||
|
||||
PermissionGroup.Contacts -> {
|
||||
contactPermissions.all { context.checkPermission(it) }
|
||||
}
|
||||
|
||||
PermissionGroup.ExternalStorage -> {
|
||||
if (isAtLeastApiLevel(Build.VERSION_CODES.R)) {
|
||||
Environment.isExternalStorageManager()
|
||||
@ -152,12 +177,18 @@ internal class PermissionsManagerImpl(
|
||||
externalStoragePermissions.all { context.checkPermission(it) }
|
||||
}
|
||||
}
|
||||
|
||||
PermissionGroup.Notifications -> {
|
||||
notificationsPermissionState.value
|
||||
}
|
||||
|
||||
PermissionGroup.AppShortcuts -> {
|
||||
context.getSystemService<LauncherApps>()?.hasShortcutHostPermission() == true
|
||||
}
|
||||
|
||||
PermissionGroup.Accessibility -> {
|
||||
accessibilityPermissionState.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,6 +200,7 @@ internal class PermissionsManagerImpl(
|
||||
PermissionGroup.ExternalStorage -> externalStoragePermissionState
|
||||
PermissionGroup.Notifications -> notificationsPermissionState
|
||||
PermissionGroup.AppShortcuts -> appShortcutsPermissionState
|
||||
PermissionGroup.Accessibility -> accessibilityPermissionState
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,6 +218,7 @@ internal class PermissionsManagerImpl(
|
||||
PermissionGroup.ExternalStorage -> externalStoragePermissionState.value = granted
|
||||
PermissionGroup.Notifications -> notificationsPermissionState.value = granted
|
||||
PermissionGroup.AppShortcuts -> appShortcutsPermissionState.value = granted
|
||||
PermissionGroup.Accessibility -> accessibilityPermissionState.value = granted
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,10 +230,12 @@ internal class PermissionsManagerImpl(
|
||||
externalStoragePermissionState.value =
|
||||
checkPermissionOnce(PermissionGroup.ExternalStorage)
|
||||
}
|
||||
|
||||
PermissionGroup.AppShortcuts -> {
|
||||
appShortcutsPermissionState.value =
|
||||
checkPermissionOnce(PermissionGroup.AppShortcuts)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
iterator.remove()
|
||||
@ -211,6 +246,10 @@ internal class PermissionsManagerImpl(
|
||||
notificationsPermissionState.value = running
|
||||
}
|
||||
|
||||
override fun reportAccessibilityServiceState(running: Boolean) {
|
||||
accessibilityPermissionState.value = running
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val calendarPermissions = arrayOf(Manifest.permission.READ_CALENDAR)
|
||||
private val locationPermissions = arrayOf(
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.mm20.launcher2.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchBarColors
|
||||
import scheme.Scheme
|
||||
|
||||
@ -14,11 +13,12 @@ fun createFactorySettings(context: Context): Settings {
|
||||
.setColorScheme(Settings.AppearanceSettings.ColorScheme.Default)
|
||||
.setDimWallpaper(false)
|
||||
.setBlurWallpaper(true)
|
||||
.setCustomColors(Settings.AppearanceSettings.CustomColors.newBuilder()
|
||||
.setAdvancedMode(false)
|
||||
.setBaseColors(DefaultCustomColorsBase)
|
||||
.setLightScheme(DefaultLightCustomColorScheme)
|
||||
.setDarkScheme(DefaultDarkCustomColorScheme)
|
||||
.setCustomColors(
|
||||
Settings.AppearanceSettings.CustomColors.newBuilder()
|
||||
.setAdvancedMode(false)
|
||||
.setBaseColors(DefaultCustomColorsBase)
|
||||
.setLightScheme(DefaultLightCustomColorScheme)
|
||||
.setDarkScheme(DefaultDarkCustomColorScheme)
|
||||
)
|
||||
.setFont(Settings.AppearanceSettings.Font.Outfit)
|
||||
.build()
|
||||
@ -168,21 +168,29 @@ fun createFactorySettings(context: Context): Settings {
|
||||
.setBottomSearchBar(false)
|
||||
.setReverseSearchResults(false)
|
||||
)
|
||||
.setGestures(
|
||||
Settings.GestureSettings.newBuilder()
|
||||
.setDoubleTap(Settings.GestureSettings.GestureAction.LockScreen)
|
||||
.setLongPress(Settings.GestureSettings.GestureAction.None)
|
||||
.setSwipeDown(Settings.GestureSettings.GestureAction.OpenNotificationDrawer)
|
||||
.setSwipeLeft(Settings.GestureSettings.GestureAction.None)
|
||||
.setSwipeRight(Settings.GestureSettings.GestureAction.None)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
internal val DefaultCustomColorsBase: Settings.AppearanceSettings.CustomColors.BaseColors
|
||||
get() {
|
||||
val scheme = Scheme.light(0xFFACE330.toInt())
|
||||
return Settings.AppearanceSettings.CustomColors.BaseColors.newBuilder()
|
||||
.setAccent1(scheme.primary)
|
||||
.setAccent2(scheme.secondary)
|
||||
.setAccent3(scheme.tertiary)
|
||||
.setNeutral1(scheme.surface)
|
||||
.setNeutral2(scheme.surfaceVariant)
|
||||
.setError(scheme.error)
|
||||
.build()
|
||||
}
|
||||
get() {
|
||||
val scheme = Scheme.light(0xFFACE330.toInt())
|
||||
return Settings.AppearanceSettings.CustomColors.BaseColors.newBuilder()
|
||||
.setAccent1(scheme.primary)
|
||||
.setAccent2(scheme.secondary)
|
||||
.setAccent3(scheme.tertiary)
|
||||
.setNeutral1(scheme.surface)
|
||||
.setNeutral2(scheme.surfaceVariant)
|
||||
.setError(scheme.error)
|
||||
.build()
|
||||
}
|
||||
|
||||
internal val DefaultLightCustomColorScheme: Settings.AppearanceSettings.CustomColors.Scheme
|
||||
get() {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package de.mm20.launcher2.preferences.migrations
|
||||
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.preferences.Settings.GestureSettings
|
||||
import de.mm20.launcher2.preferences.Settings.LayoutSettings
|
||||
|
||||
class Migration_11_12: VersionedMigration(11, 12) {
|
||||
@ -30,6 +31,14 @@ class Migration_11_12: VersionedMigration(11, 12) {
|
||||
.setBottomSearchBar(false)
|
||||
.setReverseSearchResults(false)
|
||||
)
|
||||
.setGestures(
|
||||
GestureSettings.newBuilder()
|
||||
.setDoubleTap(GestureSettings.GestureAction.LockScreen)
|
||||
.setLongPress(GestureSettings.GestureAction.None)
|
||||
.setSwipeDown(GestureSettings.GestureAction.OpenNotificationDrawer)
|
||||
.setSwipeLeft(GestureSettings.GestureAction.None)
|
||||
.setSwipeRight(GestureSettings.GestureAction.None)
|
||||
)
|
||||
}
|
||||
}
|
||||
return builder
|
||||
|
||||
@ -299,4 +299,22 @@ message Settings {
|
||||
bool reverse_search_results = 3;
|
||||
}
|
||||
LayoutSettings layout = 27;
|
||||
|
||||
message GestureSettings {
|
||||
enum GestureAction {
|
||||
None = 0;
|
||||
OpenSearch = 1;
|
||||
OpenNotificationDrawer = 2;
|
||||
LockScreen = 3;
|
||||
OpenQuickSettings = 4;
|
||||
OpenRecents = 5;
|
||||
OpenPowerDialog = 6;
|
||||
}
|
||||
GestureAction swipe_down = 1;
|
||||
GestureAction swipe_left = 2;
|
||||
GestureAction swipe_right = 3;
|
||||
GestureAction double_tap = 4;
|
||||
GestureAction long_press = 5;
|
||||
}
|
||||
GestureSettings gestures = 28;
|
||||
}
|
||||
1
services/global-actions/.gitignore
vendored
Normal file
1
services/global-actions/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
47
services/global-actions/build.gradle.kts
Normal file
47
services/global-actions/build.gradle.kts
Normal file
@ -0,0 +1,47 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = sdk.versions.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = sdk.versions.minSdk.get().toInt()
|
||||
targetSdk = sdk.versions.targetSdk.get().toInt()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
namespace = "de.mm20.launcher2.globalactions"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.bundles.kotlin)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.koin.android)
|
||||
|
||||
implementation(project(":core:preferences"))
|
||||
implementation(project(":core:base"))
|
||||
implementation(project(":core:i18n"))
|
||||
implementation(project(":core:permissions"))
|
||||
|
||||
}
|
||||
0
services/global-actions/consumer-rules.pro
Normal file
0
services/global-actions/consumer-rules.pro
Normal file
21
services/global-actions/proguard-rules.pro
vendored
Normal file
21
services/global-actions/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
17
services/global-actions/src/main/AndroidManifest.xml
Normal file
17
services/global-actions/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".LauncherAccessibilityService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accessibilityservice"
|
||||
android:resource="@xml/accessibility_service" />
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@ -0,0 +1,25 @@
|
||||
package de.mm20.launcher2.globalactions
|
||||
|
||||
import android.accessibilityservice.AccessibilityService
|
||||
|
||||
class GlobalActionsService {
|
||||
fun openNotificationDrawer() {
|
||||
LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS)
|
||||
}
|
||||
|
||||
fun lockScreen() {
|
||||
LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN)
|
||||
}
|
||||
|
||||
fun openQuickSettings() {
|
||||
LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS)
|
||||
}
|
||||
|
||||
fun openPowerDialog() {
|
||||
LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG)
|
||||
}
|
||||
|
||||
fun openRecents() {
|
||||
LauncherAccessibilityService.getInstance()?.performGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package de.mm20.launcher2.globalactions
|
||||
|
||||
import android.accessibilityservice.AccessibilityService
|
||||
import android.content.Intent
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class LauncherAccessibilityService: AccessibilityService() {
|
||||
|
||||
private val permissionManager: PermissionsManager by inject()
|
||||
|
||||
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onInterrupt() {
|
||||
|
||||
}
|
||||
|
||||
override fun onServiceConnected() {
|
||||
super.onServiceConnected()
|
||||
instance = WeakReference(this)
|
||||
permissionManager.reportAccessibilityServiceState(true)
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
permissionManager.reportAccessibilityServiceState(false)
|
||||
instance = null
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var instance: WeakReference<LauncherAccessibilityService>? = null
|
||||
internal fun getInstance(): LauncherAccessibilityService? {
|
||||
return instance?.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package de.mm20.launcher2.globalactions
|
||||
|
||||
import org.koin.dsl.module
|
||||
|
||||
val globalActionsModule = module {
|
||||
single { GlobalActionsService() }
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accessibilityEventTypes=""
|
||||
android:canRetrieveWindowContent="false"
|
||||
android:isAccessibilityTool="false" />
|
||||
@ -294,3 +294,4 @@ include(":libs:owncloud")
|
||||
include(":libs:webdav")
|
||||
include(":libs:g-services")
|
||||
include(":libs:ms-services")
|
||||
include(":services:global-actions")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user