Symptom (observed on the Kirin Mate 10 Pro)
The ongoing status notification intermittently shows "Discharging −1 mA", while the details table correctly shows no Current row at all. Reported 2026-07-12.
Diagnosis
The Kirin/HiSilicon fuel gauge reports BATTERY_PROPERTY_CURRENT_NOW in mA instead of µA — the sibling of its charge-counter unit bug (#69/#94). A real draw of ~800 mA arrives as raw -800, which we interpret as −800 µA = −0.8 mA.
The gates in BatteryRateTracker.computeRate then behave exactly as written, on garbage input:
isPlausibleCurrentMicroAmps only rejects too-large readings (wrong unit in the other direction); a 1000×-too-small value looks perfectly plausible.
hasCurrent requires Math.round(|µA|/1000f) >= 1, i.e. raw ≥ 500. Under load (real 0.5–1.5 A) raw lands in 500–1499 → the display shows "−1 mA".
Why the notification shows it but the table doesn't
NotificationService.statusWithRate falls back to mA whenever no %/h is available — notably during the source-B warm-up (~3 min after unplug / any window reset), exactly when the user tends to glance at the notification. On this device source A never runs (capacity reads 0), so every warm-up exposes the mA fallback.
- The notification refreshes on every broadcast (including screen-off/under-load moments where raw ≥ 500); the table only samples at app-open instants, where the reading is usually < 500 →
hasCurrent false → row hidden. Same gates, different sampling moments around a borderline value.
Not affected
The %/h rate itself on this device: capacity is 0 there, so source A (current ÷ capacity) is already skipped and the rate comes from level-over-time (source B), which is unit-independent. Only the Current row and the notification's mA fallback are wrong.
Also note the general-device implication: on any device, a genuine sub-1.5 mA reading displayed as "−1 mA" is glance-noise, not information.
Fix
v1 — display floor (small, safe, do first)
Treat currents below a minimum display magnitude as "not worth showing": add MIN_DISPLAY_CURRENT_MA = 10 (constant in BatteryRateTracker) and require Math.round(|µA|/1000f) >= MIN_DISPLAY_CURRENT_MA in the hasCurrent gate.
- Hides the Kirin nonsense entirely (misread values cap out at ~1–3 "mA").
- Negligible loss on genuine-µA devices: sub-10 mA discharge happens only in deep sleep, when nobody is reading the notification anyway.
- Update
BatteryRateTrackerTest accordingly (the negligibleRate_roundsToZeroAndHides / currentZero_isHiddenWhileRateStillShown neighborhood; add a boundary case at 9/10 mA).
v2 — unit auto-calibration (optional follow-up; needs on-device verification)
Persistently track the maximum |raw| ever observed (across sessions, including while charging). If after meaningful observation it stays below ~20,000, the device cannot be reporting µA (that would mean it never exceeded 20 mA even while charging) → conclude mA units, scale readings ×1000, and show the real current on Kirin instead of hiding it. Genuine µA devices routinely exceed 100,000+.
- Store the calibration flag + observed max in SharedPreferences; pure decision helper + tests.
- Verify on the Mate 10 Pro before merging (confirm raw magnitudes: expect ~200–2000 discharging, ~1000–2000 charging).
- If v2 lands, source A could also be enabled with scaled current on such devices where capacity is known — out of scope unless trivially free.
Affected code
Acceptance criteria (v1)
Symptom (observed on the Kirin Mate 10 Pro)
The ongoing status notification intermittently shows "Discharging −1 mA", while the details table correctly shows no Current row at all. Reported 2026-07-12.
Diagnosis
The Kirin/HiSilicon fuel gauge reports
BATTERY_PROPERTY_CURRENT_NOWin mA instead of µA — the sibling of its charge-counter unit bug (#69/#94). A real draw of ~800 mA arrives as raw-800, which we interpret as −800 µA = −0.8 mA.The gates in
BatteryRateTracker.computeRatethen behave exactly as written, on garbage input:isPlausibleCurrentMicroAmpsonly rejects too-large readings (wrong unit in the other direction); a 1000×-too-small value looks perfectly plausible.hasCurrentrequiresMath.round(|µA|/1000f) >= 1, i.e. raw ≥ 500. Under load (real 0.5–1.5 A) raw lands in 500–1499 → the display shows "−1 mA".Why the notification shows it but the table doesn't
NotificationService.statusWithRatefalls back to mA whenever no %/h is available — notably during the source-B warm-up (~3 min after unplug / any window reset), exactly when the user tends to glance at the notification. On this device source A never runs (capacity reads 0), so every warm-up exposes the mA fallback.hasCurrentfalse → row hidden. Same gates, different sampling moments around a borderline value.Not affected
The %/h rate itself on this device: capacity is 0 there, so source A (current ÷ capacity) is already skipped and the rate comes from level-over-time (source B), which is unit-independent. Only the Current row and the notification's mA fallback are wrong.
Also note the general-device implication: on any device, a genuine sub-1.5 mA reading displayed as "−1 mA" is glance-noise, not information.
Fix
v1 — display floor (small, safe, do first)
Treat currents below a minimum display magnitude as "not worth showing": add
MIN_DISPLAY_CURRENT_MA = 10(constant inBatteryRateTracker) and requireMath.round(|µA|/1000f) >= MIN_DISPLAY_CURRENT_MAin thehasCurrentgate.BatteryRateTrackerTestaccordingly (thenegligibleRate_roundsToZeroAndHides/currentZero_isHiddenWhileRateStillShownneighborhood; add a boundary case at 9/10 mA).v2 — unit auto-calibration (optional follow-up; needs on-device verification)
Persistently track the maximum |raw| ever observed (across sessions, including while charging). If after meaningful observation it stays below ~20,000, the device cannot be reporting µA (that would mean it never exceeded 20 mA even while charging) → conclude mA units, scale readings ×1000, and show the real current on Kirin instead of hiding it. Genuine µA devices routinely exceed 100,000+.
Affected code
service/BatteryRateTracker.java—computeRatehasCurrentgate + new constant (v1); calibration state (v2)service/BatteryRateTrackerTest.java— gate boundaries (v1); calibration decision helper (v2)Acceptance criteria (v1)