Repository navigation
fix: make eye tracking work and fix issues found in device testing - #5
Merged
Merged
Conversation
…ehavior Device testing of feat/camera-inference on a Redmi Note 13 (Android 16) and a Lenovo Tab M11 (Android 15) found that eye tracking stopped on every second frame and that release builds could not start tracking at all. Vision - MPImage.close() recycled the bitmap CameraTracker reuses for every frame, so the next frame failed. MediaPipe now gets a per-frame copy. - R8 broke MediaPipe (protobuf-lite fields, Flogger stack lookup). Add keep rules for MediaPipe, protobuf-lite messages, Flogger and ONNX Runtime. - Tracker failures are logged with their exception and reported with language-pack keys; a failed tracker is cleared so one press retries. - Remove ONNX Runtime's launch-time telemetry provider and disable its telemetry. MediaPipe's datatransport logger must stay (FaceLandmarker needs it); INTERNET remains removed. - Fail Vision builds when face_landmarker.task is missing or its SHA-256 differs. App - Handle Escape/Space/Enter before focus handling (pre-IME on a focusable root): Android consumed the first switch press after a touch, and focused buttons swallowed Escape. - Row/column scanning with a pinned message preview. - Restart the camera on return, keep calibration across backgrounding and app restarts, keep the screen on while tracking, don't pause for the permission prompt, persist the access mode and keep pause state across rotation. - Landscape-only, with a two-column board on wide screens so it fits without scrolling; restart the camera when the device is flipped. - Eye-tracking pointer that follows the calibrated gaze and shows dwell progress. - Honest camera status (off / starting / no face / tracking + provider), App info link after a permanent camera denial, Settings value readouts, readable voice names with a preview, an Install voices button and a muted-media warning. - UI rebuilds keep the scroll position and Settings; rows no longer misalign on wrapped labels; Delete removes combining signs one at a time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
…lated text
Greek had 56 of 70 UI strings in English. Translate them and add the new camera,
calibration, scanning and voice strings to all six packs. The validator now fails
on strings left identical to English (with an allowlist for words spelled the
same) and on translations that drop a {placeholder}. Android-specific strings and
the Greek UI are machine-drafted and need native-speaker review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
android-actions/setup-android@v3 installs 'tools platform-tools' by default and current cmdline-tools reject the obsolete 'tools' package, so both Android jobs failed before Gradle ran. Request only platform-tools, and install NDK 28.2.13676358 to match app/build.gradle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
Add the 1 October 2026 device validation (both test devices, debug and R8 release), document landscape mode, row/column scanning, switch keys, the gaze pointer, camera lifecycle and calibration persistence, the dependency telemetry that remains, and the Vision model build check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
Lite builds reach preVisionDebugBuild through the CMake build shared by both flavors, so the model check failed CI's Lite job, which downloads no model. Gate it on mergeVision*Assets instead. Verified locally: Lite builds and lints without the model; Vision fails at verifyVisionModel without it and builds with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
The phone was disconnected before the fixes were made, so it only ran the original branch and the one-line diagnostic patch; every fix was verified on the tablet. Say so, and list the fixes on the phone as untested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
Testing on the tablet showed calibration failing with "not accurate enough", so the app stayed in touch mode and the pointer never appeared. There was also no way to see what the camera saw. - Live camera preview (what the model receives, with iris and eye-corner dots) in the header and as the first calibration step: calibration starts once the face has been found for a second. - Nine calibration targets with a progress ring; time only counts while the face is tracked; per-target medians. The fit reports its error and the app accepts anything under 25% of the screen, telling the user the typical error. - Capture near 640x480 instead of 320x240, detection confidence 0.5, and logged diagnostics (detection rate, feature spread, per-target samples, fit error). - Smooth the pointer in screen space after mapping, and ease it between samples. - Settings is its own two-column page; wide screens fold the navigation row into the header. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
…ght rotation On the tablet the tracker never found a face (0 frames with eyes), so calibration never started and there was no pointer. - Follow the face with the camera's sensor crop (up to 3x): the eyes fill more of the frame at full sensor detail, and the crop only re-centres when the face drifts, which keeps the face frame steady. It zooms out when the face is lost. - Use face-priority exposure (with +0.5 EV) so a bright lamp behind the user does not leave the face too dark to detect. - If no face is found, try the other image rotations, since some devices report the wrong sensor orientation; log the rotation that works. - Gaze features are now iris position plus a head-turn proxy; calibration fits any number of features (tests cover 2 and 4) and stored calibrations record theirs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
…era restart
The face was found on the tablet, but setting a sensor crop that followed it made
the MediaTek MT6768 ISP fail ("support no arbitrary cropping"), crash the camera
HAL and stop tracking with CameraDevice error 4.
- Capture at ~1280x960 and crop around the face in software, easing the crop
after the face; no SCALER_CROP_REGION is used any more.
- Show the round pointer as soon as a face is tracked: before calibration it
follows head turns from the starting pose, afterwards the calibrated gaze.
Dwell selection stays limited to Eye tracking mode.
- Restart the camera after a camera-service failure (up to 3 times a minute).
- Make the rotation search conservative (a false detection on the ceiling locked
the wrong rotation): more time and less evidence for the sensor rotation, eight
consecutive faces for any other, and implausible eye/head readings rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
…e features - Fix the pointer never showing: an operator-precedence bug treated "no calibration" as a changed screen size, and every blink or dropped frame re-centred the head pointer and hid it for ~1.5 s. Dropouts now keep the last gaze for 500 ms and the pointer shows whenever a face is tracked. - Blink to select (on by default, 600 ms): using MediaPipe's eyeBlink blendshapes, closing both eyes for 0.6-2.5 s presses the button under the pointer, or acts as the switch while scanning. Natural blinks and resting the eyes are ignored. - Wide landscape board: message beside its actions, one phrase row, and a full-width keyboard in real rows that fills the remaining height (keys ~2.5x larger on the tablet). English uses QWERTY rows; other packs are balanced into at most four rows. - Calibration measured on the tablet: horizontal eye position worked (r = 0.98) but the lid-relative vertical feature had no signal. Features are now eyeX, eyeY against the eye corners, head x/y, blendshape look x/y and lid opening, fitted per axis; calibration dots stay inside the system bars with the text and Cancel between rows. - Debug builds accept a DEBUG_GAZE broadcast to drive the pointer and blinks over adb, so pointer, dwell and blink can be tested without a face. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
- Magnetic lock: when the pointer rests on a button, or within 28 dp of one, for 120 ms it locks on, turns green and settles at the button's centre. It stays locked until the gaze has been outside the button plus a margin (a quarter of its smaller side, at least 16 dp) for 150 ms. Dwell and blink act on the locked button, so jitter no longer resets a dwell; a blink presses the button that was locked when the eyes closed. - Momentum: the pointer is a slightly under-damped spring integrated every display frame (Choreographer), stiffer while locked, instead of an exponential ease at the 20 Hz tick. Verified on the tablet with injected gaze: a point in the gap between two buttons locked onto the nearer one; after a jump the pointer was mid-glide, then on target. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
Once the face has been steady for three frames the zoomed software crop freezes on it instead of easing after every movement. It re-frames only when the eyes near the edge of the frame (outside the middle half) or the eye distance changes by more than about a third, gliding to the new framing and locking again, and it holds the lock for 3 s through blinks and brief turns before zooming out. The preview shows a green border while locked, and lock/re-frame/release are logged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
Brings in PR #4's CI fix and its rewritten validation record. The workflow keeps this branch's version (same platform-tools fix, NDK 28.2.13676358 to match app/build.gradle). validation.md keeps PR #4's PR 1-4 section as written and adds this PR's device validation after it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM
manupawickramasinghe
changed the base branch from
docs/modernization-guide
to
main
October 1, 2026 19:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the issues found by testing the PR stack (#1–#4) on real hardware, plus two requested changes: landscape-only and an eye-tracking pointer.
This PR is stacked on #4 (
docs/modernization-guide). Merge #1 → #2 → #3 → #4 first, retargeting each next PR tomain, then retarget this one tomain.Critical fixes
MPImage.close()recycled the bitmap thatCameraTrackerreuses for every frame. MediaPipe now gets a per-frame copy.Field platform_ … not found) and then broke Flogger's caller lookup. Added keep rules for MediaPipe, protobuf-lite messages, Flogger and ONNX Runtime.setup-android@v3failed on the obsoletetoolspackage before Gradle ran.Other fixes
{placeholders}.datatransportlogger has to stay (FaceLandmarker fails without it), but it cannot upload withoutINTERNET; this is documented.Requested changes
sensorLandscape). Wide screens use a two-column board, so the whole keyboard and phrases fit without scrolling. Flipping the device restarts the camera, and calibration is tied to the screen size and rotation.Verified on device
The original bugs were reproduced on a Redmi Note 13 (Android 16) and a Lenovo Tab M11 (Android 15). The phone was disconnected before the fixes, so every fix here was verified on the tablet only, debug and R8 release:
Code-only (not exercised on a device)
sensorLandscapeapp).Not tested
The Greek strings and the new Android strings in other languages are machine-drafted and need native-speaker review.
🤖 Generated with Claude Code
https://claude.ai/code/session_014Csi3Kyo14x29WV5eKMdNM