Skip to content
Merged
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
21 changes: 18 additions & 3 deletions .specs/codebase/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,24 @@ DeckLock (actions fixadas por SHA, `permissions: contents: read`, evidência env
propriedade) → `app/build/reports/coverage/test/debug/`; `scripts/coverage-summary.py` resume por pacote. O CI faz isso no
job de unitários, depois de publicar os APKs (fora do caminho crítico), e põe a tabela no resumo do job. Mapa do que nenhum
teste de unidade toca, não meta: telas, teclado e serviços são cobertos pelos instrumentados.
- `scripts/ime-placement.py --ime keyflow|gboard [--cold]` mede de FORA do app (adb) onde a janela do teclado fica ao abrir
— sem teste no processo. A frio, no emulador lento: KeyFlow 6/8 aberturas em transição, Gboard 1/8 (desempenho da
abertura a frio é tarefa aberta).
- `scripts/ime-placement.py --ime keyflow|gboard [--cold] [--seconds 12]` mede de FORA do app (adb) onde a janela do teclado
fica ao abrir — sem teste no processo — e em quantos segundos ela fica visível e no lugar de vez. A frio, emulador com a
CPU a 300% e o app compilado (`cmd package compile -m speed -f com.keyflow`), 8 aberturas (2026-10-10): KeyFlow visível
em 2,0 s e no lugar de vez em 2,9 s, com a janela em transição nas 8; Gboard visível em 3,8 s e no lugar em 4,0 s, sem
transição. O KeyFlow é o mais rápido; a diferença é o tipo de janela — a do Gboard tem a altura da tela desde o início
(ele desenha 1080×2272), a nossa tem a altura do teclado (787 px) e passa pelos tamanhos de transição até o 1º quadro
sair. (A comparação antiga, "KeyFlow 6/8 × Gboard 1/8", estava errada: com 4 s de observação o Gboard a frio quase não
chegava a aparecer.)
- **Para onde vai o ~1 s do 1º quadro do teclado a frio no emulador** (traces do Perfetto do `OpeningBenchmarks`, 20 voltas,
e um trace do sistema do Gboard; 2026-10-10): 300–600 ms criando o contexto gráfico (`Creating EGLContext` na
RenderThread — só chamadas `rc… encode`, o protocolo do emulador com a GPU emulada; o Gboard paga 302–318 ms); 60–900 ms
compilando ~10 programas de GPU no 1º desenho (`shader_compile`, sempre `cache_miss`: a GPU do emulador, ANGLE sobre
SwiftShader, não tem `GL_OES_get_program_binary`, então o Android não consegue guardar os programas — `code_cache/` do
KeyFlow e do Gboard ficam vazios — e todo processo novo recompila tudo); ~110 ms esperando o buffer da janela; **20–65 ms
do nosso código** na thread principal. Não é lentidão do app, e o perfil de base não alcança isso. Não medido em aparelho
de verdade: para conferir no A32, `installDebug`, um `adb shell perfetto … gfx view` numa abertura a frio e olhar
`Creating EGLContext`/`shader_compile`; `adb shell run-as com.keyflow ls code_cache` deve mostrar
`com.android.skia.shaders_cache` (programas guardados).
- Teste que inicia gravação de verdade para o `OverlayService` no `@After` (a gravação vazava para o teste seguinte).
- Os 6 jobs de device sobem **junto** com o de unitários (sem `needs`): preparam o runner e ligam o
emulador enquanto ele compila, e então baixam o artefato `apks` da própria rodada (`gh run download`).
Expand Down
44 changes: 40 additions & 4 deletions scripts/ime-placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@

--cold encerra o processo do teclado antes de cada abertura: toda abertura começa a frio, como a 1ª depois de instalar
ou de o Android matar o processo. É a comparação justa — sem isso o Gboard está sempre quente e o KeyFlow, recém-instalado.

Cada abertura diz também em quantos segundos o teclado ficou visível e no lugar de vez. Leia isso antes de comparar dois teclados:
"0 deslocadas" de um teclado que nem apareceu na janela de observação não quer dizer nada — foi assim que o Gboard a frio
pareceu melhor que o KeyFlow (2026-10-10): no emulador lento ele leva ~3,3 s para aparecer e a janela era de 4 s. A frio, use
--seconds 12 ou mais. Para comparar com um app de sistema, compile o KeyFlow antes (`cmd package compile -m speed -f
com.keyflow`, como o gate faz): o APK de debug recém-instalado roda interpretado.
"""
import argparse
import re
import statistics
import subprocess
import sys
import time
Expand Down Expand Up @@ -65,33 +72,62 @@ def main():
adb("ime", "enable", ime)
adb("ime", "set", ime)
displaced, total = [], 0
visible_times, settled_times = [], []
for opening in range(1, args.openings + 1):
adb("input", "keyevent", "KEYCODE_HOME")
time.sleep(1.5)
if args.cold:
# O probe é do pacote do KeyFlow: com o KeyFlow, encerrar o pacote leva o probe junto (ele reabre abaixo).
adb("am", "force-stop", PACKAGES[args.ime])
if args.ime == "keyflow":
adb("ime", "set", ime) # force-stop tira o KeyFlow de teclado ativo
# O force-stop tira o KeyFlow de teclado ativo, e o `ime set` logo em seguida às vezes não pega: repete até o
# Android confirmar (com o Gboard já confirma na 1ª).
for _ in range(25):
adb("ime", "set", ime)
if adb("settings", "get", "secure", "default_input_method").strip() == ime:
break
time.sleep(0.2)
else:
sys.exit(f"{args.ime} não ficou como teclado ativo depois do force-stop")
time.sleep(1.0)
adb("am", "start", "-n", PROBES[args.probe], "-f", "0x10008000")
time.sleep(2.0)
adb("input", "tap", "540", "283")
streak, bad, end = 0, [], time.time() + args.seconds
tapped = time.time()
streak, bad, end = 0, [], tapped + args.seconds
visible_at = settled_at = None
while time.time() < end:
seen, wm = sample()
if not seen or not wm:
continue
total += 1
now = time.time() - tapped
if visible_at is None:
visible_at = now
if seen != wm and top(seen) < height:
bad.append(f"entrada={seen} wm={wm}")
streak += 1
settled_at = None # "no lugar" = de vez: uma leitura fora do lugar depois zera
if streak == 2:
displaced.append(f"abertura {opening}: {bad[-1]}")
else:
streak = 0
print(f"abertura {opening}/{args.openings}: {len(bad)} leituras fora do lugar", flush=True)
if seen == wm and settled_at is None:
settled_at = now
visible_times.append(visible_at)
settled_times.append(settled_at)
when = "NÃO apareceu na janela de observação" if visible_at is None else (
f"visível em {visible_at:.1f} s, " + ("não assentou" if settled_at is None else f"no lugar de vez em {settled_at:.1f} s")
)
print(f"abertura {opening}/{args.openings}: {len(bad)} leituras fora do lugar; {when}", flush=True)
print(f"{args.ime} ({args.probe}{', a frio' if args.cold else ''}): {len(displaced)} de {args.openings} aberturas deslocadas, {total} leituras — {displaced}")
shown = [t for t in visible_times if t is not None]
settled = [t for t in settled_times if t is not None]
print(
f"tempo desde o toque: visível mediana {statistics.median(shown):.1f} s" if shown else "tempo desde o toque: nunca visível",
f"; no lugar de vez mediana {statistics.median(settled):.1f} s" if settled else "; nunca no lugar",
f"; {args.openings - len(shown)} de {args.openings} aberturas sem aparecer em {args.seconds:.0f} s",
sep="",
)
return 1 if displaced else 0


Expand Down