Fix/android alerts and location - #544
Merged
Merged
Conversation
Arming the geofence cancelled the alarm. A Pixel 9 armed at 01:09 and 133
minutes later had reported nothing: `dumpsys alarm` held no pending alarm,
`geofence_armed` was true, `wake_geofence_n` was 1, and the only breadcrumb
was "arm: geofence live". A fence that registers is not a fence that fires,
and cancelling on `armed` made those two states the same observation.
So the alarm is a watchdog now and the fence is what pets it. Both proofs
count and they prove different halves: a fence that *arms* has reached Play
services, a fence that *fires* has reached us. Either restarts the hour, so
behind a working fence the alarm is perpetually pushed out and never runs,
and an hour of fence silence is what lets it through. An error broadcast
does not count — it is evidence the fence is gone, so it pulls the alarm in
instead.
Four states had no alarm at all, each of them permanent:
- a reboot or an app update clears every elapsed-realtime alarm, and the
boot receiver's success branch scheduled nothing behind the fence it had
just re-armed;
- enabling reporting waits on a fix and then on an asynchronous
registration, and scheduled nothing for that whole window — so a
first-ever enable, or any stop/start from the developer page, could die
in it with reporting on and nothing running;
- a geofence error re-armed without scheduling, from a branch that does
not hold the broadcast open, so its callback may never land;
- `ensure`, the "leave nothing unscheduled" fallback, consulted the stored
`armed` belief — which at every one of its eight call sites has just been
disproved. It is the adaptive interval again, unconditionally.
Sharper, in both levers the API gives:
- radius 200 m to 150 m, the tightest value inside Google's documented
100-150 m band, which is set by network-location accuracy because the
fence is positioned from Wi-Fi and cell rather than GNSS;
- responsiveness 5 min to 2 min. It is an entitlement to be late, not a
sampling rate, and five minutes was longer than the whole end-to-end
latency usually is. Not 0: the background limits put practical
responsiveness near two minutes whatever is asked for, and the reference
is explicit that a bigger value saves power, so 0 buys nothing and pays
for it.
New(zh-Hant): 背景定位改用鬧鐘看門狗守住地理圍欄,並讓圍欄更靈敏
New(en-US): background location watchdogs the geofence and triggers sooner
`TEXT_MESSAGE_APP` is a port number, not a promise. Anything can be sent on it, and the decoder ran `utf8.decode(payload, allowMalformed: true)` — which substitutes U+FFFD per bad sequence and drops the bytes inside the decoder. A binary body arrived in the chat as a row of that nothing could undo, because by then the only copy of it was gone. So the decode is strict, and a body that is not text becomes a hex dump at the one point that still has the bytes. The bubble labels it with its size and sets it in monospace; long-press copies the dump, which pastes straight into `xxd -r -p`. Valid UTF-8 is not on its own enough, and assuming it was would have left the symptom in place for the likeliest binary body of all. Every byte below 0x80 decodes, so a protobuf misdirected onto the text port — `08 01 10 05 18 02` — comes back as six control characters that draw as tofu and read as something a person typed. A C0 control byte that is not tab, newline or carriage return therefore means binary, which is the rule git, file(1) and grep -I use. Non-empty bytes that decode to nothing mean binary too: `utf8.decode` swallows a leading BOM, so a three-byte packet was being reported to the user as an empty message. Nothing wakes anybody for one. The notification body is built from the same text, and a hex dump posted under a sender's name in the shade says somebody typed it — the exact claim the bubble grew a label to avoid. The flag is stored rather than re-derived, because it cannot be re-derived: a hex dump is itself valid text, so by the time anything downstream sees the string the distinction is gone. The column arrives by ALTER and reads NULL as text, which is right — every row written before it existed came out of a decoder that could not represent anything else. New(zh-Hant): 二進位訊息在 Meshtastic 聊天中顯示為十六進位,不再是亂碼 New(en-US): a binary Meshtastic message is shown as hex instead of tofu
Optimization(zh-Hant): 本次更新頁全面美化,卡片更有層次與進場動畫 Optimization(en-US): Restyle the highlights page with layered cards and motion
It passed on 2026-08-18 and failed on the 19th, on nobody's change. The row it inserts is stamped `DateTime.utc(2026, 8, 18)`, and `flush` prunes anything older than `logRetention` in the same transaction as the insert — so once real time moved a day on, the row was deleted by the call that wrote it and `recent()` came back empty. The store already takes a clock, and log_store_test.dart already injects one. This one just did not.
The Apple Developer account was filling up with identical rows — TYPE "Development", NAME "Created via API", one or two per push to main — and every one of them was dead on arrival. `-allowProvisioningUpdates` does what its man page says: "for automatically signed targets, xcodebuild will create and update profiles, app IDs, and certificates". The runner is ephemeral and its keychain is empty, so the archive looks for a signing identity, finds none, and creates one. The private key it generates never leaves that runner, so the row it leaves behind in the account can never sign anything again. It is not a certificate; it is a tombstone. They accumulate until the team hits Apple's cap, and then every iOS release fails at once until a human revokes by hand. Dropping the flag is not the fix — the archive then dies on "No profiles for 'com.exptech.dpip.dpip' were found". Profile management is the half we want and certificate creation is the half we do not, and the flag does not split. Downloading an existing certificate is not the fix either: Apple never held the private key. `CertificateCreateRequest` takes a `csrContent`, so the caller keeps the key and sends only the public half. So the runner is handed one, in a keychain under `$RUNNER_TEMP` that the VM takes with it. Only the development half needs this: `-exportArchive`'s distribution signing already works, because cloud-managed certificates keep that private key on Apple's side. Cloud signing has no development counterpart, and that asymmetry is why half this pipeline was quietly fine. The script's one hard assertion is that a *valid* identity exists afterwards, because every likely way to get the secret wrong is otherwise silent — the release still goes green, still by minting. A .p12 exported from Keychain Access's "Certificates" category has no private key; an expired certificate imports cleanly; a chain that will not build imports cleanly. All three make `security find-identity` print "0 valid identities found" and exit 0. Everything else it reports is a warning: a release that dies because a diagnostic could not parse a date is worse than the problem being fixed. Needs two new repository secrets, APPLE_DEV_CERT_BASE64 and APPLE_DEV_CERT_PASSWORD, before the next release runs. Also corrects three files that pointed at `release-internal.yml`, which has not existed for some time, and the ExportOptions header, which described automatic signing as covering the whole job — the belief this bug lived in.
The LLM review is advice, and advice should never be the reason a pull request cannot merge. It could be: a required check that FAILS blocks, and this step fails for things that say nothing about the code under review — the endpoint is down, the token expired, the quota ran out, the diff was too big, the model returned something unparseable. So the review step carries `continue-on-error`, and its cap moved from the job to the step. That distinction is the whole change: a job-level timeout fails the job and nothing downstream can absorb it, while a step-level one is an error like any other. 15 minutes is well past a normal diff, so reaching it means something is wedged rather than slow. A green check that reviewed nothing is the obvious way for this to rot, so a following step reads `outcome` — the real result, before continue-on-error rewrote it — and turns anything but success into a warning that says the pull request has not been reviewed and how to retry. Also drops a stray `with:` from the action's input map. It parsed as an input named `with` with a null value, which the action ignored.
`noteWake(kind)` writes `wake_${kind}_at` and `wake_${kind}_n`, so passing
it an exception name built a preference key per exception class —
`wake_report failed: UnknownHostException_at` — in a namespace with no
bound, in the file every receiver reads on every wake.
It also claimed a wake that never happened. The wake counters exist to
answer "did the OS call us at all", which was the question that found the
133-minute silence, and a failed POST is not an answer to it.
`note` is what that line meant: one breadcrumb in the ring, which is where
the rest of the report outcomes already go. No changelog line — nothing
here is visible outside the developer page.
whes1015
enabled auto-merge (rebase)
August 19, 2026 14:19
whes1015
disabled auto-merge
August 19, 2026 16:18
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.
這個 PR 做了什麼
相關 issue
怎麼驗
檢查清單
tool/check/commits.sh origin/main..HEAD通過—— commit 訊息就是更新日誌,格式見 commit.md
mise exec -- flutter analyze與mise exec -- flutter test通過AppLocalizations,沒有寫死AppSpacing/AppRadius/AppMotion,深色模式看過,文字對比度可接受