Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## [4.2.0](https://github.com/sds100/KeyMapper/releases/tag/v4.2.0)

## Added

- #1980 Add "Notification panel is showing" and "Notification panel is not showing" constraints. Detection uses the accessibility service window list and is best-effort; accuracy may vary across OEMs and Android versions.

## Fixed

- #2074 Scrolling the action or trigger list no longer accidentally moves items; reordering by drag now only activates from the drag handle or via long-press.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,12 @@ class ChooseConstraintViewModel @Inject constructor(
ConstraintId.LOCK_SCREEN_NOT_SHOWING ->
returnResult.emit(ConstraintData.LockScreenNotShowing)

ConstraintId.NOTIFICATION_PANEL_SHOWING ->
returnResult.emit(ConstraintData.NotificationPanelShowing)

ConstraintId.NOTIFICATION_PANEL_NOT_SHOWING ->
returnResult.emit(ConstraintData.NotificationPanelNotShowing)

ConstraintId.TIME -> {
timeConstraintState = ConstraintData.Time(
startHour = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,16 @@ sealed class ConstraintData {
override val id: ConstraintId = ConstraintId.HINGE_OPEN
}

@Serializable
data object NotificationPanelShowing : ConstraintData() {
override val id: ConstraintId = ConstraintId.NOTIFICATION_PANEL_SHOWING
}

@Serializable
data object NotificationPanelNotShowing : ConstraintData() {
override val id: ConstraintId = ConstraintId.NOTIFICATION_PANEL_NOT_SHOWING
}

@Serializable
data class Time(
val startHour: Int,
Expand Down Expand Up @@ -388,6 +398,10 @@ object ConstraintEntityMapper {
ConstraintEntity.HINGE_CLOSED -> ConstraintData.HingeClosed
ConstraintEntity.HINGE_OPEN -> ConstraintData.HingeOpen

ConstraintEntity.NOTIFICATION_PANEL_SHOWING -> ConstraintData.NotificationPanelShowing
ConstraintEntity.NOTIFICATION_PANEL_NOT_SHOWING ->
ConstraintData.NotificationPanelNotShowing

ConstraintEntity.TIME -> {
val startTime =
entity.extras.getData(ConstraintEntity.EXTRA_START_TIME).valueOrNull()!!
Expand Down Expand Up @@ -693,6 +707,16 @@ object ConstraintEntityMapper {
ConstraintEntity.HINGE_OPEN,
)

is ConstraintData.NotificationPanelShowing -> ConstraintEntity(
uid = constraint.uid,
ConstraintEntity.NOTIFICATION_PANEL_SHOWING,
)

is ConstraintData.NotificationPanelNotShowing -> ConstraintEntity(
uid = constraint.uid,
ConstraintEntity.NOTIFICATION_PANEL_NOT_SHOWING,
)

is ConstraintData.Time -> ConstraintEntity(
uid = constraint.uid,
type = ConstraintEntity.TIME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ enum class ConstraintDependency {
PHONE_STATE,
CHARGING_STATE,
HINGE_STATE,
NOTIFICATION_PANEL_STATE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,8 @@ enum class ConstraintId {
HINGE_CLOSED,
HINGE_OPEN,

NOTIFICATION_PANEL_SHOWING,
NOTIFICATION_PANEL_NOT_SHOWING,

TIME,
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ class LazyConstraintSnapshot(
lockScreenAdapter.isLockScreenShowing()
}

private val isNotificationShadeExpanded: Boolean by lazy {
accessibilityService.isNotificationShadeExpanded.firstBlocking()
}

private val localTime = LocalTime.now()

private fun isMediaPlaying(): Boolean {
Expand Down Expand Up @@ -199,6 +203,9 @@ class LazyConstraintSnapshot(
!isLockscreenShowing ||
appInForeground != "com.android.systemui"

is ConstraintData.NotificationPanelShowing -> isNotificationShadeExpanded
is ConstraintData.NotificationPanelNotShowing -> !isNotificationShadeExpanded

is ConstraintData.Time ->
if (constraint.data.startTime.isAfter(constraint.data.endTime)) {
localTime.isAfter(constraint.data.startTime) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ class ConstraintUiHelper(
is ConstraintData.LockScreenNotShowing -> getString(
R.string.constraint_lock_screen_not_showing,
)
is ConstraintData.NotificationPanelShowing ->
getString(R.string.constraint_notification_panel_showing)
is ConstraintData.NotificationPanelNotShowing ->
getString(R.string.constraint_notification_panel_not_showing)
is ConstraintData.Time -> getString(
R.string.constraint_time_formatted,
arrayOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import androidx.compose.material.icons.outlined.SignalWifiStatusbarNull
import androidx.compose.material.icons.outlined.StayCurrentLandscape
import androidx.compose.material.icons.outlined.StayCurrentPortrait
import androidx.compose.material.icons.outlined.StopCircle
import androidx.compose.material.icons.outlined.Notifications
import androidx.compose.material.icons.outlined.NotificationsOff
import androidx.compose.material.icons.outlined.Timer
import androidx.compose.material.icons.outlined.Wifi
import androidx.compose.material.icons.outlined.WifiOff
Expand Down Expand Up @@ -111,6 +113,10 @@ object ConstraintUtils {
ConstraintId.HINGE_OPEN,
-> ConstraintCategory.DEVICE

ConstraintId.NOTIFICATION_PANEL_SHOWING,
ConstraintId.NOTIFICATION_PANEL_NOT_SHOWING,
-> ConstraintCategory.DISPLAY

ConstraintId.TIME -> ConstraintCategory.TIME
}

Expand Down Expand Up @@ -191,6 +197,12 @@ object ConstraintUtils {
Icons.Outlined.ScreenLockPortrait,
)
ConstraintId.LOCK_SCREEN_NOT_SHOWING -> ComposeIconInfo.Vector(Icons.Outlined.LockOpen)

ConstraintId.NOTIFICATION_PANEL_SHOWING ->
ComposeIconInfo.Vector(Icons.Outlined.Notifications)
ConstraintId.NOTIFICATION_PANEL_NOT_SHOWING ->
ComposeIconInfo.Vector(Icons.Outlined.NotificationsOff)

ConstraintId.TIME -> ComposeIconInfo.Vector(Icons.Outlined.Timer)
}

Expand Down Expand Up @@ -242,6 +254,10 @@ object ConstraintUtils {
ConstraintId.HINGE_OPEN -> R.string.constraint_hinge_open
ConstraintId.LOCK_SCREEN_SHOWING -> R.string.constraint_lock_screen_showing
ConstraintId.LOCK_SCREEN_NOT_SHOWING -> R.string.constraint_lock_screen_not_showing
ConstraintId.NOTIFICATION_PANEL_SHOWING ->
R.string.constraint_notification_panel_showing
ConstraintId.NOTIFICATION_PANEL_NOT_SHOWING ->
R.string.constraint_notification_panel_not_showing
ConstraintId.TIME -> R.string.constraint_time
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ class DetectConstraintsUseCaseImpl @AssistedInject constructor(
}
ConstraintDependency.KEYBOARD_VISIBLE ->
accessibilityService.isInputMethodVisible.map { dependency }
ConstraintDependency.NOTIFICATION_PANEL_STATE ->
accessibilityService.isNotificationShadeExpanded.map { dependency }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ class KeyMapConstraintsComparator(
is ConstraintData.PhysicalOrientation -> Success(
constraint.data.physicalOrientation.toString(),
)
ConstraintData.NotificationPanelShowing -> Success("")
ConstraintData.NotificationPanelNotShowing -> Success("")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ abstract class BaseAccessibilityService :
override val isInputMethodVisible: Flow<Boolean>
get() = _isInputMethodVisible

private val _isNotificationShadeExpanded by lazy {
MutableStateFlow(isNotificationShadeVisible())
}

override val isNotificationShadeExpanded: Flow<Boolean>
get() = _isNotificationShadeExpanded

override var serviceFlags: Int?
get() = serviceInfo?.flags
set(value) {
Expand Down Expand Up @@ -288,6 +295,7 @@ abstract class BaseAccessibilityService :

_activeWindowPackage.update { rootNode?.packageName?.toString() }
_isInputMethodVisible.update { isImeWindowVisible() }
_isNotificationShadeExpanded.update { isNotificationShadeVisible() }
}

getController()?.onAccessibilityEvent(event)
Expand Down Expand Up @@ -563,6 +571,14 @@ abstract class BaseAccessibilityService :
return imeWindow != null && imeWindow.root?.isVisibleToUser == true
}

private fun isNotificationShadeVisible(): Boolean {
return windows?.any { window ->
if (window.type != AccessibilityWindowInfo.TYPE_SYSTEM) return@any false
val root = window.root ?: return@any false
root.packageName?.toString() == "com.android.systemui" && root.isVisibleToUser
} ?: false
}

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun injectText(text: String) {
inputMethod?.currentInputConnection?.commitText(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ interface IAccessibilityService : SwitchImeInterface {
*/
val isInputMethodVisible: Flow<Boolean>

/**
* Whether the notification shade (notification panel / status bar) is expanded.
* Detection is best-effort; accuracy may vary across OEMs and Android versions.
*/
val isNotificationShadeExpanded: Flow<Boolean>

fun findFocussedNode(focus: Int): AccessibilityNodeModel?

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
Expand Down
3 changes: 3 additions & 0 deletions base/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@
<string name="constraint_hinge_closed_description">Hinge is closed</string>
<string name="constraint_hinge_open_description">Hinge is open</string>

<string name="constraint_notification_panel_showing">Notification panel is showing</string>
<string name="constraint_notification_panel_not_showing">Notification panel is not showing</string>

<string name="orientation_0">Portrait (0°)</string>
<string name="orientation_90">Landscape (90°)</string>
<string name="orientation_180">Portrait (180°)</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class TestConstraintSnapshot(
val isLockscreenShowing: Boolean = false,
val localTime: LocalTime = LocalTime.now(),
val hingeState: HingeState = HingeState.Unavailable,
val isNotificationPanelShowing: Boolean = false,
) : ConstraintSnapshot {

override fun isSatisfied(constraint: Constraint): Boolean {
Expand Down Expand Up @@ -126,6 +127,8 @@ class TestConstraintSnapshot(
hingeState is HingeState.Available && hingeState.isClosed()
ConstraintData.HingeOpen ->
hingeState is HingeState.Available && hingeState.isOpen()
ConstraintData.NotificationPanelShowing -> isNotificationPanelShowing
ConstraintData.NotificationPanelNotShowing -> !isNotificationPanelShowing
}

if (isSatisfied) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ data class ConstraintEntity(
const val HINGE_CLOSED = "hinge_closed"
const val HINGE_OPEN = "hinge_open"

const val NOTIFICATION_PANEL_SHOWING = "notification_panel_showing"
const val NOTIFICATION_PANEL_NOT_SHOWING = "notification_panel_not_showing"

const val TIME = "time"

const val EXTRA_PACKAGE_NAME = "extra_package_name"
Expand Down
Loading