feat(android): 유선·무선 스위치를 자동으로 가려 쓴다 - #6
Open
wingwogus wants to merge 16 commits into
Open
Conversation
…id build - crates/hanbeon-core: platform-free scan/adapt/journal/profile/preset/host (from proven reference) - crates/hanbeon-jni: JNI bridge for future android host (reference port) - desktop feature flag: tray/global-shortcut/enigo/rodio/serialport behind --features desktop - android target compiles with NoopHost placeholder until accessibility plugin lands - fix @devup-ui broken workspace:^ releases via overrides; real typescript 6.0.3 - tauri android init scaffold in gen/
…ests - #[cfg_attr(android, tauri::mobile_entry_point)] on run() (JVM entry symbol) - desktop integration test requires desktop feature - clippy clean in both feature configurations
…read - prepare_floating gated behind desktop (webview eval before ready crashes on Android) - Registry::noop for Android: rustls-platform-verifier needs JNI init that only the desktop network stack provides; Hana Cloud preset sync stays desktop-only - verified on device: SM-A155N (Galaxy A15, Android 16), app alive 15s+ no panic
- port 7 Kotlin files from verified reference (ControllerView, OverlayService, HanbeonAccessibilityService, HighlightView, UsbSwitch, Core, DebugActionReceiver) - android_bridge.rs: JNI start_overlay_service via tao ndk_glue context - manifest: SYSTEM_ALERT_WINDOW, FGS specialUse, accessibility binding - device-verified on SM-A155N (Android 16): overlay windows above Chrome, accessibility focus move (next -> 됨), gesture select, failure logging
- findBoard: prefer Arduino VID (0x2341) and clones, exclude network adapters (Realtek 0x0BDA etc.) that also expose CDC and were mistaken for the board - dataInterface: read from the active configuration, not device.getInterface - claim retry with port reset; vendor-interface fallback removed in favor of strict CDC matching after Realtek misdetection - device-verified: switch press/release P/R events flow to core, gesture judgement works, interval adaptation kicks in on mistakes
The red FLOATING-INDEX-LOADED banner and manual button were verification scaffolding. OverlayService auto-start now runs from a useEffect timer on Android only; the debug button is gone.
OverlayService.openSettings now launches MainActivity with screen=settings extra; MainActivity navigates the wry WebView to settings/index.html via onWebViewCreate hook and returns to the floating controller when relaunched without the extra.
Same flow as desktop: app starts with the floating controller, the settings cell overlays the settings form on top (no URL navigation), and closing it keeps the scan state intact. SettingsForm gains an optional onClose for in-page dismissal.
Default screen is now settings/, but the overlay auto-start lived in the floating page which never mounts. Move the auto-start effect to settings/page.tsx so the controller comes up on launch regardless of which route loads first. Add bridge logging for future tracing.
Relative paths passed to loadUrlMainThread were resolved as http://index.html/ and failed with ERR_NAME_NOT_RESOLVED. Navigate to absolute http://tauri.localhost/... URLs instead.
USB가 붙어 있으면 USB를, 없으면 보호자가 고른 XIAO 하나에만 BLE로 붙는다. 전송이 바뀌어도 앞 4칸의 순서와 자리는 그대로다. - 전송 계약과 중재기를 분리해 USB 우선·BLE 대기를 한 곳에서 판정한다 - 누른 상태에서 전송이 끊기면 실행하지 않고 취소한다. 오선택 비용이 크다 - 임의 기기에 붙지 않는다. 보호자가 고른 이름과 신원이 정확히 맞아야 붙는다 - 상태를 화면에 드러낸다. 어느 전송이 살아 있는지 알 수 없으면 통제감을 잃는다 실기에서 찾아 고친 것: - USB 권한 허용이 거부로 읽혔다. FLAG_IMMUTABLE이 결과 값 채우기를 막았다 - Arduino VID만 허용해 이 프로젝트의 XIAO를 배제했다. CDC면 후보로 받고 신원은 핸드셰이크로 가린다 - bulkTransfer는 유휴 타임아웃과 실제 오류를 모두 음수로 준다. 리더가 이를 전부 연결 상실로 읽어, 스위치를 누르지 않는 것이 '빠짐'으로 판정됐다 - JNI 크레이트를 아무도 빌드하지 않아 오래된 .so가 배포됐고 앱이 시작하다 죽었다. Gradle이 ABI마다 빌드하게 해 다시 새지 않게 했다 - 디버그 정보로 APK가 333MB였다. line-tables만 남겨 170MB로 줄였다
컨트롤러만 떠서 보호자가 주사 속도나 스위치를 정할 수단이 없었다. 설정을 여는 코드가 오버레이의 설정 칸을 고르는 경로에만 있었기 때문이다. 시작할 때 프로필을 읽어 온보딩 전이면 온보딩을, 마쳤으면 설정을 띄운다.
보호자가 무선 스위치를 등록할 수 없었다. 실제 XIAO는 광고 패킷에 이름을 싣지 않고 NUS 서비스 UUID만 보내는데, 스캔이 이름으로 걸러냈기 때문이다. 맥에서 이름이 보이는 것은 GATT 연결로 캐시한 값이라 착각을 부른다. 광고에는 kCBAdvDataLocalName이 없고 폰에는 MAC만 뜬다. 데스크톱은 이름 또는 NUS UUID로 판정하는데 안드로이드만 이름을 요구했다. 같은 기준으로 맞춘다 — 이름이 없으면 NUS 광고로 받고, 이름이 있으면 여전히 정확히 일치해야 한다. 신원은 연결 후 핸드셰이크가 다시 가린다.
main이 펌웨어 설치기를 들여오면서 같은 파일을 건드렸다. 충돌 3곳을 풀고 안드로이드 빌드가 깨지지 않게 모듈 경계를 다시 맞췄다. - serialport는 main이 무조건 의존으로 바꿨다. 안드로이드도 쓰므로 그대로 두고 desktop 피처의 dep: 참조만 뺐다 - arduino·firmware·flasher와 show_floating은 데스크톱 전용이다. 안드로이드는 보드를 굽지 않고 floating 창도 없다 — 컨트롤러는 OverlayService가 올린다 - profile 모듈은 이 브랜치에서 hanbeon-core로 옮겼다. main이 들고 온 사본은 중복이라 지웠다 - 무선 스위치 등록은 온보딩 첫 칸에서 두 번째 칸으로 옮겼다. main이 펌웨어 안내를 맨 앞에 두었고, 유선을 마친 뒤 무선을 고르는 순서가 자연스럽다 - 재탐지 문구를 connectionCopy로 옮겼다. 조용히 두면 사용자 일시정지로 읽혀 길게 눌러 다시 시작하려 한다 알려진 문제: page 경계 테스트가 Onboarding 모듈을 프로세스 전역으로 모킹해 온보딩 단계 순서를 검증하는 테스트 2개가 전체 실행에서 실패한다. page에 온보딩 주입점을 만들어야 제대로 풀리므로 따로 다룬다.
병합 뒤 온보딩 단계 순서 테스트 2개가 전체 실행에서만 깨졌다. 파일을 따로 돌리면 통과해서 배치 문제로 보였지만 원인은 모킹이었다. bun은 모듈을 통째로 갈아끼운다. 두 스위트가 각자 @/lib/profile의 일부만 모킹하자 나중에 로드된 쪽이 상대가 쓰는 export를 지웠고, 실제 Onboarding이 closeSettings를 찾지 못해 터졌다. page 경계 스위트의 Onboarding 스텁이 그 오류를 가리고 있었을 뿐이다. profile과 invoke에 각각 하나의 이중체를 두고 스위트가 동작만 덮어쓰게 했다. 가리개였던 Onboarding 스텁은 지웠다 — 이제 실제 컴포넌트가 로드된다.
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.
Android에서 스위치를 유선·무선 어느 쪽으로 꽂아도 앱이 알아서 가려 쓴다.
USB가 붙어 있으면 USB를, 없으면 보호자가 고른 XIAO 하나에만 BLE로 붙는다.
지키는 선
실기에서 찾아 고친 결함
유닛 테스트만으로는 하나도 잡히지 않았던 것들이다. 각각 실패하는 테스트를 먼저 세웠다.
FLAG_IMMUTABLE이EXTRA_PERMISSION_GRANTED채우기를 막음bulkTransfer는 유휴 타임아웃과 실제 오류를 모두 음수로 주는데 리더가 전부 상실로 읽음UnsatisfiedLinkError마지막 것이 가장 오래 걸렸다. 맥에서 광고 패킷을 직접 뜯어
kCBAdvDataLocalName이 없는 것을 확인했다. 맥에 이름이 보이는 건 GATT 연결로 캐시한 값이라 오히려 진단을 흐렸고, 폰에는 MAC만 뜬다. 데스크톱은 이름 또는 NUS UUID로 판정하는데(||) 안드로이드만 이름을 요구했다.실기 검증
Samsung SM-A155N, XIAO nRF52840 Sense.
USB
BLE (USB 없이)
시나리오 14개 중 8개 통과: 실제 스위치 입력 도달, BLE 전용 동작, 블루투스 off/on 중 USB 유지, BLE 권한 회수/재부여 중 USB 유지, 오버레이 권한, 단일
OverlayService(포그라운드 1개), 상태 실시간 갱신, 오버레이 터치 통과(FLAG_NOT_FOCUSABLE|NOT_TOUCHABLE확인).기록은
.omo/evidence/android-auto-usb-ble/에 있다.게이트
bun test81 pass / 0 failbun run typecheck,bun run lint(clippy-D warnings+ fmt + oxlint) 통과빌드 파이프라인도 함께 고쳤다. Tauri의 rust 플러그인은
src-tauri만 빌드하고crates/hanbeon-jni는 아무도 빌드하지 않아 손으로 복사해온 상태였다. Gradle이 ABI마다 빌드하게 걸어 심볼 누락이 기기에 도달할 수 없게 했다. 디버그 APK도 333MB → 170MB로 줄였다(워크스페이스에 cargo 프로파일이 없어 전체 디버그 정보가 들어가던 것).남은 것
src-tauri/gen/이 gitignore 대상이라 스캐폴드를 재생성하면 이 구현이 지워진다. 추적 방식 결정 필요.참고
BLE 주변기기는 한 번에 하나의 중앙기기와만 GATT 세션을 유지한다. 폰이 스위치를 쓰는 동안 보드는 계속 광고하지만(맥에서 -52dBm) 맥은 연결할 수 없다. BLE 프로토콜 특성이고 제품상으로도 동시 사용은 의미가 없다.