Add a hold-by-name gesture to ui-trace
A row held to select it was the one gesture that could only be written as a pair of coordinates: `tap`'s press is 60ms so nothing sees a long press, and `holddrag` takes numbers and always drags afterwards. `hold 'helper 3'` resolves the label the way `tap` does -- the resolution is now `locate`, shared by both -- and holds the press past the platform's 500ms threshold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
86cf4e8088
commit
c9be26c6d1
3 files changed
+54
-13
No files matched your search
+42
-11
@@ -33,6 +33,8 @@ public final class UiTrace {
|
||||
private static final int MAX_DEPTH = 60;
|
||||
/** How long `tap LABEL` keeps looking before it decides the label is not there. */
|
||||
private static final long FIND_TIMEOUT_MS = 3000;
|
||||
/** Comfortably past the platform's 500ms long-press threshold, for `hold`. */
|
||||
private static final long HOLD_MS = 800;
|
||||
|
||||
private final UiAutomation automation;
|
||||
private final PrintWriter out;
|
||||
@@ -218,6 +220,7 @@ public final class UiTrace {
|
||||
tapLabel(join(step));
|
||||
}
|
||||
}
|
||||
case "hold" -> holdLabel(join(step));
|
||||
case "swipe" -> swipe(Integer.parseInt(step[1]), Integer.parseInt(step[2]),
|
||||
Integer.parseInt(step[3]), Integer.parseInt(step[4]),
|
||||
step.length > 5 ? Long.parseLong(step[5]) : 300);
|
||||
@@ -253,21 +256,48 @@ public final class UiTrace {
|
||||
return label;
|
||||
}
|
||||
|
||||
/** Presses whatever is currently labelled `label`, wherever it is. */
|
||||
private void tapLabel(String label) {
|
||||
Rect box = locate(label, "tap");
|
||||
tap(box.centerX(), box.centerY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Presses whatever is currently labelled `label`, wherever it is.
|
||||
* Presses whatever is labelled `label` and holds it past the platform's long-press
|
||||
* threshold, then lets go without moving.
|
||||
*
|
||||
* This is the action to reach for, and `tap X Y` is the exception. A coordinate is a position
|
||||
* measured once by hand: anything that moves the thing being pressed -- a control added to the
|
||||
* row, a font size, a density, a different device -- makes the tap land on whatever now sits
|
||||
* there, and the script then reports a number that was never measured, which reads exactly
|
||||
* like a result. A name is what the control already carries for assistive technology, so it
|
||||
* survives all of that and fails loudly when it genuinely is not there.
|
||||
* `tap` cannot reach this: its press is 60ms, so nothing that distinguishes a press from a
|
||||
* long press ever sees one. `holddrag` can hold, but it takes coordinates and always drags
|
||||
* afterwards -- so the one gesture a list's "hold a row to select it" needs was the one
|
||||
* gesture that could only be written as a pair of numbers, which is exactly what a script
|
||||
* driving a UI must not contain.
|
||||
*/
|
||||
private void holdLabel(String label) {
|
||||
Rect box = locate(label, "hold");
|
||||
int x = box.centerX(), y = box.centerY();
|
||||
long down = SystemClock.uptimeMillis();
|
||||
send(MotionEvent.ACTION_DOWN, down, x, y);
|
||||
SystemClock.sleep(HOLD_MS);
|
||||
send(MotionEvent.ACTION_UP, down, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Where whatever is currently labelled `label` is on screen, for the `verb` about to press
|
||||
* it.
|
||||
*
|
||||
* Pressing by name is the action to reach for, and a coordinate is the exception. A
|
||||
* coordinate is a position measured once by hand: anything that moves the thing being
|
||||
* pressed -- a control added to the row, a font size, a density, a different device -- makes
|
||||
* the press land on whatever now sits there, and the script then reports a number that was
|
||||
* never measured, which reads exactly like a result. A name is what the control already
|
||||
* carries for assistive technology, so it survives all of that and fails loudly when it
|
||||
* genuinely is not there.
|
||||
*
|
||||
* The box is read from the tree at the moment of the gesture rather than from an earlier
|
||||
* frame, because the screen the gesture lands on is the only one that decides where the mark
|
||||
* is.
|
||||
*/
|
||||
private void tapLabel(String label) {
|
||||
private Rect locate(String label, String verb) {
|
||||
// Looked for repeatedly for a moment rather than once. Two things make a single look
|
||||
// wrong: the accessibility connection has no window at all for the first frames after it
|
||||
// is made, so an action at the very start of a trace found nothing to search; and a
|
||||
@@ -299,11 +329,12 @@ public final class UiTrace {
|
||||
throw new IllegalStateException("\"" + label + "\" has no size on screen");
|
||||
}
|
||||
synchronized (out) {
|
||||
out.println("# tap \"" + label + "\" at " + box.centerX() + "," + box.centerY()
|
||||
+ " in " + box.left + "," + box.top + "," + box.right + "," + box.bottom);
|
||||
out.println("# " + verb + " \"" + label + "\" at " + box.centerX() + ","
|
||||
+ box.centerY() + " in " + box.left + "," + box.top + "," + box.right + ","
|
||||
+ box.bottom);
|
||||
out.flush();
|
||||
}
|
||||
tap(box.centerX(), box.centerY());
|
||||
return box;
|
||||
}
|
||||
|
||||
/** Every visible node whose text or description is exactly `label`, in tree order. */
|
||||
|
||||
Reference in new issue
Block a user