Skip to content

NexFlow 1.5.1 — 背景 NFC,但只發給真的有 NFC 流程的人 - #10

Merged
ADSFAaron merged 3 commits into
masterfrom
feat/background-nfc-tech-discovered
Aug 31, 2026
Merged

NexFlow 1.5.1 — 背景 NFC,但只發給真的有 NFC 流程的人#10
ADSFAaron merged 3 commits into
masterfrom
feat/background-nfc-tech-discovered

Conversation

@ADSFAaron

Copy link
Copy Markdown
Owner

依賴 #9(base 是 feat/execution-visibility-nfc-exact-alarm)。#9 先合,這個再合。

1.5.0 拿掉了 manifest 的 TAG_DISCOVERED 攔截,因為它把每一個安裝都登記成「所有其他 App 不處理的標籤」的接收者——門禁卡、交通卡都會把 NexFlow 叫起來,即使使用者從來沒建過 NFC 流程。那修好了劫持,也一併把背景感應帶走了:關著 App 感應現在完全沒反應。

那個攔截不必是永久的

filter 改掛在預設關閉<activity-alias> 上,由 NfcBackgroundDispatch 只在「有啟用中的 NFC 流程」時打開。被停用的元件不參與 intent 解析,所以沒有 NFC 流程的使用者不是「排在很後面」,而是根本不在名單裡。

兩個方向都要寫:元件的啟用狀態會跨重開機與更新保留,所以刪掉最後一個 NFC 流程之後還開著,等於為一個已經不存在的功能繼續攔標籤。引擎停止時也會交還——自動化關掉時攔下感應卻什麼都不做,是白白吃掉使用者的一次感應。

為什麼是 TECH_DISCOVERED,以及為什麼 tech-list 只有 Ndef

TAG_DISCOVERED 在 API 37 已被 deprecated。tech-list 只列 NdefNdefFormatable

  • Ndef 是官方唯一保證的技術("mandatory for all Android devices with NFC to correctly enumerate"),所以這份清單在每支手機上匹配到的是同一批標籤
  • MifareClassicMifareUltralight 的實作是選配的("If it is not implemented, then MifareClassic will never be enumerated in getTechList")。列了它們會在非 NXP 晶片的手機上安靜地永遠不匹配——不報錯、沒有 log
  • 這也比原本的網子更緊:交通卡與門禁卡是 MifareClassic、感應支付是 IsoDep、Suica 是 NfcF,都不會列舉 Ndef,所以都碰不到我們

匹配語意是反直覺的——tech-list 是標籤所報技術的 subset 才算匹配,所以列愈少匹配愈廣。這點寫進 CLAUDE.md 了。

其他

  • 感應開啟的是透明、跑完即關的 Activity,畫面上不會蓋掉使用者正在做的事
  • NfcEventSource 會保留「在任何人訂閱之前抵達的標籤」。標籤喚醒的是已死的程序,啟動服務與 emit 發生在同一瞬間,replay = 0 讓那次 emit 落空——結果是 App 被叫醒、流程沒跑
  • alias 沒有自己的 Kotlin class,是用字串定址的,manifest 改名不會在編譯期失敗。改成記錄 error 而非吞掉,並由測試對合併後的 manifest 解析這個名字(getActivityInfo + MATCH_DISABLED_COMPONENTS),同時斷言它是 disabled 出貨的

驗證

  • 單元測試 + Robolectric + 截圖測試 + lintGithubDebuglintPlayDebug 全過
  • 新增 11 項測試:alias 開關的兩個方向、alias 在 manifest 中確實存在、NfcEventSource 的保留與不重放
  • 實機:全新安裝後 NexFlow 在 TECH_DISCOVEREDTAG_DISCOVERED 兩份分派名單裡都不存在

沒能驗到的:開啟後的那一半。shell 不能切換別的 App 的元件狀態(SecurityException),而且手邊沒有實體標籤。所以「關著 App 感應標籤真的會跑流程」目前只有單元測試涵蓋,發版前值得拿真的標籤實測一次

🤖 Generated with Claude Code

ADSFAaron and others added 2 commits August 30, 2026 21:38
Play caps "What's new" at 500 characters per language and rejects rather
than truncates, so the counts are recorded next to each block and the file
verifies them from its own fenced blocks.

Written for users, not as a second CHANGELOG. The schedule permission leads
because it is the only item here that needs the user to do something — every
other fix takes effect on install.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n NFC flow

1.5.0 removed the manifest TAG_DISCOVERED filter because it registered every
install as the handler for any tag no other app claimed — door badges and
transit cards launched NexFlow for people who had never built an NFC flow.
That fixed the hijacking and silently took background NFC with it: with the
app closed, a tag now does nothing at all.

The filter did not need to be permanent. It lives on an activity-alias that
ships disabled, and NfcBackgroundDispatch turns it on only while an enabled
flow has an NFC trigger. A disabled component is excluded from intent
resolution outright, so a user with no NFC flow does not rank low in tag
dispatch — they are not in it. Both directions are written: enabled state
survives reboots and updates, so leaving it on after the last NFC flow is
deleted would keep intercepting tags for a feature the user removed. The
engine hands dispatch back when it stops, rather than swallowing taps that
would find nothing listening.

TECH_DISCOVERED rather than TAG_DISCOVERED, which API 37 deprecates. The
tech-list is Ndef and NdefFormatable and nothing else. Ndef is the one
technology the platform guarantees ("mandatory for all Android devices with
NFC to correctly enumerate"), so the list matches the same tags on every
phone; MifareClassic and MifareUltralight are explicitly optional and would
have matched nothing at all on a non-NXP chipset, with no error and no log.
It is also a tighter net than what it replaces: transit passes and badges are
MifareClassic, payment cards are IsoDep, Suica is NfcF, and none of them
enumerate Ndef.

A tag opens a transparent activity that hands the id over and finishes —
nothing appears over what the user was doing. NfcEventSource now holds a tag
that arrives before anything is listening: a tag on a dead process starts the
service and emits in the same breath, and with replay = 0 that emit landed
nowhere, so the tag woke the app and then nothing happened.

The alias has no Kotlin class and is addressed by a string, so a manifest
rename cannot fail at compile time. It is logged rather than swallowed, and
the test resolves the name against the merged manifest.

Verified on device that a fresh install appears in neither TECH nor TAG
dispatch. The enabled half is not verifiable here — shell cannot toggle
another app's components, and there is no tag to scan — so a real tap with a
real tag is still worth doing before release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ADSFAaron
ADSFAaron changed the base branch from feat/execution-visibility-nfc-exact-alarm to master August 30, 2026 14:26
…alse

1.5.0 was never published, so the store's users go from 1.4.0 straight to
1.5.1 and the "What's new" has to cover both versions rather than only the
NFC work. Rewritten as one combined set, with the note in CHANGELOG so nobody
later reads the 1.5.0 entry as something that shipped.

English loses the volume-summary line to the 500-character cap — it is the
least dense of the four languages and that is the smallest of the four items.

README claimed "NFC 觸發僅在 App 前景時有效" in two places, which 1.5.1 makes
wrong, and understated the alarm fallback as "誤差數分鐘" when the documented
guarantee for an inexact alarm is an hour. Both now say what actually happens,
including which tags background dispatch deliberately does not cover and why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ADSFAaron
ADSFAaron merged commit d92d940 into master Aug 31, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant