From d05df487fbdb372ef514b68afb234ffde802e821 Mon Sep 17 00:00:00 2001 From: Yan Lucas Date: Sat, 10 Oct 2026 22:33:07 -0300 Subject: [PATCH] =?UTF-8?q?docs(teclado):=20para=20onde=20vai=20o=201=20s?= =?UTF-8?q?=20da=20abertura=20a=20frio=20no=20emulador=20=E2=80=94=20e=20a?= =?UTF-8?q?=20compara=C3=A7=C3=A3o=20com=20o=20Gboard=20corrigida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Traces do Perfetto do OpeningBenchmarks (20 voltas) e do Gboard: o 1º quadro do teclado a frio gasta 300–600 ms criando o contexto gráfico e 60–900 ms compilando ~10 programas de GPU, que a GPU emulada (ANGLE sobre SwiftShader, sem GL_OES_get_program_binary) não deixa o Android guardar — todo processo novo recompila, o do Gboard também. O nosso código soma 20–65 ms. A comparação "KeyFlow 6/8 × Gboard 1/8 aberturas em transição" estava errada: com 4 s de observação o Gboard a frio (3,8 s para aparecer) quase não era amostrado. O ime-placement.py agora diz em quantos segundos o teclado fica visível e no lugar de vez, avisa quando ele nem apareceu e repete o ime set depois do force-stop. Medido certo: KeyFlow 2,0 s / 2,9 s, Gboard 3,8 s / 4,0 s; a transição que sobra no KeyFlow vem do tipo de janela (altura do teclado, redimensionada depois de aparecer), não de lentidão. --- .specs/codebase/TESTING.md | 21 +++++++++++++++--- scripts/ime-placement.py | 44 ++++++++++++++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/.specs/codebase/TESTING.md b/.specs/codebase/TESTING.md index 83328f2c..c6ec0959 100644 --- a/.specs/codebase/TESTING.md +++ b/.specs/codebase/TESTING.md @@ -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`). diff --git a/scripts/ime-placement.py b/scripts/ime-placement.py index 2e370a66..5ad7bcd7 100644 --- a/scripts/ime-placement.py +++ b/scripts/ime-placement.py @@ -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 @@ -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