Skip to content

Commit 2e4b058

Browse files
committed
feat: swipe action
1 parent a5ee78e commit 2e4b058

File tree

8 files changed

+252
-77
lines changed

8 files changed

+252
-77
lines changed

app/src/main/kotlin/li/songe/gkd/a11y/A11yRuleEngine.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,9 @@ class A11yRuleEngine(val service: A11yCommonImpl) {
442442
a, selector, MatchOption(fastQuery = gkdAction.fastQuery)
443443
) ?: throw RpcError("没有查询到节点")
444444
return withContext(Dispatchers.IO) {
445-
ActionPerformer.getAction(gkdAction.action ?: ActionPerformer.None.action)
446-
.perform(targetNode, gkdAction.position)
445+
ActionPerformer
446+
.getAction(gkdAction.action ?: ActionPerformer.None.action)
447+
.perform(targetNode, gkdAction)
447448
}
448449
}
449450

app/src/main/kotlin/li/songe/gkd/data/GkdAction.kt

Lines changed: 125 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,28 @@ data class GkdAction(
1616
val selector: String,
1717
val fastQuery: Boolean = false,
1818
val action: String? = null,
19-
val position: RawSubscription.Position? = null,
20-
)
19+
override val position: RawSubscription.Position? = null,
20+
override val swipeArg: RawSubscription.SwipeArg? = null,
21+
) : RawSubscription.LocationProps
2122

2223
@Serializable
2324
data class ActionResult(
2425
val action: String,
2526
val result: Boolean,
26-
val shizuku: Boolean = false,
27+
val shell: Boolean = false,
2728
val position: Pair<Float, Float>? = null,
2829
)
2930

3031
sealed class ActionPerformer(val action: String) {
3132
abstract fun perform(
3233
node: AccessibilityNodeInfo,
33-
position: RawSubscription.Position?,
34+
locationProps: RawSubscription.LocationProps,
3435
): ActionResult
3536

3637
data object ClickNode : ActionPerformer("clickNode") {
3738
override fun perform(
3839
node: AccessibilityNodeInfo,
39-
position: RawSubscription.Position?,
40+
locationProps: RawSubscription.LocationProps,
4041
): ActionResult {
4142
return ActionResult(
4243
action = action,
@@ -48,20 +49,24 @@ sealed class ActionPerformer(val action: String) {
4849
data object ClickCenter : ActionPerformer("clickCenter") {
4950
override fun perform(
5051
node: AccessibilityNodeInfo,
51-
position: RawSubscription.Position?,
52+
locationProps: RawSubscription.LocationProps,
5253
): ActionResult {
5354
val rect = node.casted.boundsInScreen
54-
val p = position?.calc(rect)
55+
val p = locationProps.position?.calc(rect)
5556
val x = p?.first ?: ((rect.right + rect.left) / 2f)
5657
val y = p?.second ?: ((rect.bottom + rect.top) / 2f)
58+
if (!ScreenUtils.inScreen(x, y)) {
59+
return ActionResult(
60+
action = action,
61+
result = false,
62+
position = x to y,
63+
)
64+
}
5765
return ActionResult(
5866
action = action,
59-
result = if (0 <= x && 0 <= y && x <= ScreenUtils.getScreenWidth() && y <= ScreenUtils.getScreenHeight()) {
60-
if (shizukuContextFlow.value.tap(x, y)) {
61-
return ActionResult(
62-
action = action, result = true, shizuku = true, position = x to y
63-
)
64-
}
67+
result = if (shizukuContextFlow.value.tap(x, y)) {
68+
true
69+
} else {
6570
val gestureDescription = GestureDescription.Builder()
6671
val path = Path()
6772
path.moveTo(x, y)
@@ -73,8 +78,6 @@ sealed class ActionPerformer(val action: String) {
7378
A11yService.instance?.dispatchGesture(
7479
gestureDescription.build(), null, null
7580
) != null
76-
} else {
77-
false
7881
},
7982
position = x to y
8083
)
@@ -84,65 +87,72 @@ sealed class ActionPerformer(val action: String) {
8487
data object Click : ActionPerformer("click") {
8588
override fun perform(
8689
node: AccessibilityNodeInfo,
87-
position: RawSubscription.Position?,
90+
locationProps: RawSubscription.LocationProps,
8891
): ActionResult {
8992
if (node.isClickable) {
90-
val result = ClickNode.perform(node, position)
93+
val result = ClickNode.perform(node, locationProps)
9194
if (result.result) {
9295
return result
9396
}
9497
}
95-
return ClickCenter.perform(node, position)
98+
return ClickCenter.perform(node, locationProps)
9699
}
97100
}
98101

99102
data object LongClickNode : ActionPerformer("longClickNode") {
100103
override fun perform(
101104
node: AccessibilityNodeInfo,
102-
position: RawSubscription.Position?,
105+
locationProps: RawSubscription.LocationProps,
103106
): ActionResult {
104107
return ActionResult(
105108
action = action,
106-
result = node.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK)
109+
result = node.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK).apply {
110+
if (this) {
111+
Thread.sleep(LongClickCenter.LONG_DURATION)
112+
}
113+
}
107114
)
108115
}
109116
}
110117

111118
data object LongClickCenter : ActionPerformer("longClickCenter") {
119+
const val LONG_DURATION = 500L
112120
override fun perform(
113121
node: AccessibilityNodeInfo,
114-
position: RawSubscription.Position?,
122+
locationProps: RawSubscription.LocationProps,
115123
): ActionResult {
116124
val rect = node.casted.boundsInScreen
117-
val p = position?.calc(rect)
125+
val p = locationProps.position?.calc(rect)
118126
val x = p?.first ?: ((rect.right + rect.left) / 2f)
119127
val y = p?.second ?: ((rect.bottom + rect.top) / 2f)
120128
// 某些系统的 ViewConfiguration.getLongPressTimeout() 返回 300 , 这将导致触发普通的 click 事件
121-
val longClickDuration = 500L
129+
if (!ScreenUtils.inScreen(x, y)) {
130+
return ActionResult(
131+
action = action,
132+
result = false,
133+
position = x to y,
134+
)
135+
}
122136
return ActionResult(
123137
action = action,
124-
result = if (0 <= x && 0 <= y && x <= ScreenUtils.getScreenWidth() && y <= ScreenUtils.getScreenHeight()) {
125-
if (shizukuContextFlow.value.tap(
126-
x, y, longClickDuration
127-
)
128-
) {
129-
return ActionResult(
130-
action = action, result = true, shizuku = true, position = x to y
131-
)
132-
}
138+
result = if (shizukuContextFlow.value.tap(x, y, LONG_DURATION)) {
139+
true
140+
} else {
133141
val gestureDescription = GestureDescription.Builder()
134142
val path = Path()
135143
path.moveTo(x, y)
136144
gestureDescription.addStroke(
137145
GestureDescription.StrokeDescription(
138-
path, 0, longClickDuration
146+
path, 0, LONG_DURATION
139147
)
140148
)
141-
A11yService.instance?.dispatchGesture(
149+
(A11yService.instance?.dispatchGesture(
142150
gestureDescription.build(), null, null
143-
) != null
144-
} else {
145-
false
151+
) != null).apply {
152+
if (this) {
153+
Thread.sleep(LONG_DURATION)
154+
}
155+
}
146156
},
147157
position = x to y
148158
)
@@ -152,22 +162,22 @@ sealed class ActionPerformer(val action: String) {
152162
data object LongClick : ActionPerformer("longClick") {
153163
override fun perform(
154164
node: AccessibilityNodeInfo,
155-
position: RawSubscription.Position?,
165+
locationProps: RawSubscription.LocationProps,
156166
): ActionResult {
157167
if (node.isLongClickable) {
158-
val result = LongClickNode.perform(node, position)
168+
val result = LongClickNode.perform(node, locationProps)
159169
if (result.result) {
160170
return result
161171
}
162172
}
163-
return LongClickCenter.perform(node, position)
173+
return LongClickCenter.perform(node, locationProps)
164174
}
165175
}
166176

167177
data object Back : ActionPerformer("back") {
168178
override fun perform(
169179
node: AccessibilityNodeInfo,
170-
position: RawSubscription.Position?,
180+
locationProps: RawSubscription.LocationProps,
171181
): ActionResult {
172182
return ActionResult(
173183
action = action,
@@ -179,18 +189,89 @@ sealed class ActionPerformer(val action: String) {
179189
data object None : ActionPerformer("none") {
180190
override fun perform(
181191
node: AccessibilityNodeInfo,
182-
position: RawSubscription.Position?,
192+
locationProps: RawSubscription.LocationProps,
183193
): ActionResult {
184194
return ActionResult(
185-
action = action, result = true
195+
action = action,
196+
result = true
186197
)
187198
}
188199
}
189200

201+
data object Swipe : ActionPerformer("swipe") {
202+
override fun perform(
203+
node: AccessibilityNodeInfo,
204+
locationProps: RawSubscription.LocationProps,
205+
): ActionResult {
206+
val rect = node.casted.boundsInScreen
207+
val swipeArg = locationProps.swipeArg ?: return None.perform(node, locationProps)
208+
val startP = swipeArg.start.calc(rect)
209+
val endP = swipeArg.end?.calc(rect) ?: startP
210+
if (startP == null || endP == null) {
211+
return None.perform(node, locationProps)
212+
}
213+
val startX = startP.first
214+
val startY = startP.second
215+
val endX = endP.first
216+
val endY = endP.second
217+
if (!(ScreenUtils.inScreen(startX, startY) && ScreenUtils.inScreen(endX, endY))) {
218+
return ActionResult(
219+
action = action,
220+
result = false,
221+
position = endX to endY,
222+
)
223+
}
224+
return if (shizukuContextFlow.value.swipe(
225+
startX,
226+
startY,
227+
endX,
228+
endY,
229+
swipeArg.duration
230+
)
231+
) {
232+
ActionResult(
233+
action = action,
234+
result = true,
235+
shell = true,
236+
position = endX to endY,
237+
)
238+
} else {
239+
val gestureDescription = GestureDescription.Builder()
240+
val path = Path()
241+
path.moveTo(startX, startY)
242+
path.lineTo(endX, endY)
243+
gestureDescription.addStroke(
244+
GestureDescription.StrokeDescription(
245+
path, 0, swipeArg.duration
246+
)
247+
)
248+
ActionResult(
249+
action = action,
250+
result = (A11yService.instance?.dispatchGesture(
251+
gestureDescription.build(), null, null
252+
) != null).apply {
253+
if (this) {
254+
Thread.sleep(swipeArg.duration)
255+
}
256+
},
257+
position = endX to endY,
258+
)
259+
}
260+
}
261+
}
262+
190263
companion object {
191264
private val allSubObjects by lazy {
192265
arrayOf(
193-
ClickNode, ClickCenter, Click, LongClickNode, LongClickCenter, LongClick, Back, None
266+
ClickNode,
267+
ClickCenter,
268+
Click,
269+
LongClickNode,
270+
LongClickCenter,
271+
LongClick,
272+
Back,
273+
None,
274+
Swipe,
194275
)
195276
}
196277

0 commit comments

Comments
 (0)