Merge branch 'android-ime-targets' into trial-merge

This commit is contained in:
pj committed 2026-08-15 19:51:22 +05:30
commit 9583aa365f
2 files changed
+63

No files matched your search

@@ -779,6 +779,22 @@ internal fun typeChunks(
return typed return typed
} }
// dismissSoftKeyboard closes an open IME, and issues nothing when none is open.
//
// The keyboard is its own window over the bottom of the app, and the hierarchy
// carries only what is visible to the user, so every app node under it is
// absent from the tree the picker enumerates targets from. Typing raises it, so
// an IME left open hides a form's submit control for as long as the fuzzer
// keeps typing into that form, which is a state it cannot type its way out of.
//
// The mInputShown guard is load-bearing rather than an optimisation: BACK is
// what closes an open IME, and BACK with no IME open navigates out of the
// screen, so an unguarded dismissal would make every InputText a back press.
internal fun dismissSoftKeyboard(shell: (String) -> String) {
if (!shell("dumpsys input_method").contains("mInputShown=true")) return
shell("input keyevent 4")
}
// resumedActivityPackage matches a "package/activity" component, mirroring the // resumedActivityPackage matches a "package/activity" component, mirroring the
// Go scope guard's regex so both read the same dumpsys wording. // Go scope guard's regex so both read the same dumpsys wording.
private val resumedActivityPackage = private val resumedActivityPackage =
@@ -885,6 +901,11 @@ class MaestroDriverBackend(private val serial: String?) : DriverBackend {
} else { } else {
driver.inputText(text) driver.inputText(text)
} }
// A probe that fails reads as "no IME open", which is the safe way to be
// wrong: it skips the dismissal rather than sending a stray BACK.
dismissSoftKeyboard {
runCatching { dadb.shell(it).allOutput }.getOrDefault("")
}
} }
// typeShellSafe types shell-safe ASCII through adb `input text` in chunks, // typeShellSafe types shell-safe ASCII through adb `input text` in chunks,
@@ -212,4 +212,46 @@ class InputTextTest {
assertEquals(maestro.KeyCode.ENTER, maestroKeyFor("enter")) assertEquals(maestro.KeyCode.ENTER, maestroKeyFor("enter"))
assertFailsWith<IllegalArgumentException> { maestroKeyFor("zorp") } assertFailsWith<IllegalArgumentException> { maestroKeyFor("zorp") }
} }
// An IME left open hides every app node beneath it from the hierarchy, so a
// form whose submit button sits under the keyboard becomes unreachable for
// as long as the fuzzer keeps typing into it. Typing must close the
// keyboard it raised.
@Test fun dismissSoftKeyboardClosesAnOpenIme() {
val commands = mutableListOf<String>()
dismissSoftKeyboard {
commands.add(it)
IME_OPEN_DUMPSYS
}
assertEquals(
listOf("dumpsys input_method", "input keyevent 4"),
commands,
)
}
// The guard is the dangerous half: BACK is only swallowed by an open IME,
// so dismissing unconditionally would turn every InputText into a back
// press and walk the fuzzer straight out of the screen it was filling in.
@Test fun dismissSoftKeyboardSendsNoBackWhenNoImeIsOpen() {
val commands = mutableListOf<String>()
dismissSoftKeyboard {
commands.add(it)
IME_CLOSED_DUMPSYS
}
assertEquals(listOf("dumpsys input_method"), commands)
}
} }
private val IME_OPEN_DUMPSYS =
"""
mCurMethodId=com.google.android.inputmethod.latin/.LatinIME
mInputShown=true
mSystemReady=true mInteractive=true
""".trimIndent()
private val IME_CLOSED_DUMPSYS =
"""
mCurMethodId=com.google.android.inputmethod.latin/.LatinIME
mInputShown=false
mSystemReady=true mInteractive=true
""".trimIndent()