Wave 4 Android polish: testable HID logic, unit tests + CI#1
Conversation
Extract the framework-independent HID logic out of MainActivity into a pure com.phonekb.HidMap class (charToHid keymap, modifier bitmask, mouse-delta clamp) and delegate to it. Behavior is unchanged — values match the original inline code exactly. - Add 13 JUnit 4 tests for HidMap (run on a plain JVM, no Android SDK). - Add run-tests.sh: self-contained test runner that fetches JUnit into .testlibs/ (gitignored) and compiles + runs the suite. - Add .github/workflows/ci.yml: runs unit tests and builds a debug APK via build.sh on push/PR, uploading the APK as an artifact. This is the first CI that compiles the app from source. - Harden build.sh: auto-detect newest installed build-tools/platform (overridable via BUILD_TOOLS_VERSION/COMPILE_SDK) instead of hardcoding 36.1.0/android-34, and fall back to `jar` when `zip` is unavailable. - Document test + CI in README; gitignore .testlibs/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Independent verification — verdict: solid ✅Reviewed Extraction is genuine, not a fake stub. The diff moves the inline keymap/clamp/modifier logic out of Tests pass for real. Ran CI config is sane. No Gradle in this repo — it's a hand-rolled aapt2+javac+d8+apksigner pipeline, and the workflow matches it:
No secrets / no overclaim. Only credential is the documented throwaway debug keystore ( Minor nits (non-blocking):
Net: faithful refactor + real green unit tests + a correct CI pipeline. Recommend merge. |
Summary
A proportionate polish pass on PhoneKB. The app is a hand-built Java project (aapt2 + javac + d8 + apksigner via
build.sh) — no Gradle — so this focuses on testable logic, CI, and build robustness rather than restyling working UI code.MainActivityinto a new framework-independent classcom.phonekb.HidMap: thecharToHidkeymap, the modifier bitmask (modByte), and the mouse-delta clamp (clampDelta).MainActivitynow delegates to it. Behavior is unchanged — the values match the original inline code exactly (the original used literal2for shift, which isMOD_SHIFT/0x02).test/com/phonekb/HidMapTest.java) covering the keymap, shifted symbols, the quirky digit row (0= usage 39), unmapped chars, the modifier bitmask, and delta saturation. They run on a plain JVM — no Android SDK, emulator, or device.run-tests.sh— a self-contained runner that fetches JUnit into.testlibs/(gitignored) then compiles and runs the suite..github/workflows/ci.yml(on: [push, pull_request]): a unit-test job (JDK 17 +run-tests.sh) and a build-APK job (JDK 17 + Android SDK +build.sh, uploading the APK as an artifact).build.sh: it auto-detects the newest installed build-tools/platform (overridable viaBUILD_TOOLS_VERSION/COMPILE_SDK) instead of hardcoding36.1.0/android-34, and falls back to the JDK'sjarwhenzipis missing. This is what was making the build non-portable..testlibs/.Verified locally vs. only in CI
Verified locally on this machine:
run-tests.sh— compiles and runs, 13/13 tests pass on OpenJDK 17.build.sh— runs end to end against a real Android platform jar (build-tools 36.0.0, platform android-36.1) and produces a signed 25KPhoneKB.apk.apksigner verifypasses andLcom/phonekb/HidMap;is confirmed present in the DEX, so the refactor compiles and ships.bash -nsyntax check on both scripts; YAML of the workflow parses.Only CI will verify (NOT run locally):
build-apkworkflow job on GitHub'subuntu-latestrunner. The local build used a different SDK (auto-detected 36.x); CI pinsbuild-tools;34.0.0+platforms;android-34, so the exact runner toolchain andandroid-actions/setup-android@v3step are unverified until the first CI run. This workflow is the first environment that builds the app from source in CI.Notes
build.shalready used$HOME(no hardcoded/home/raz); its debug keystore uses the standard Androidandroid/androiddebug convention and is generated at build time, not committed.🤖 Generated with Claude Code