Skip to content

[feat] add a thermal status row to the debug overlay panel#248

Merged
Manabu-GT merged 3 commits into
mainfrom
feat/issue-246
May 19, 2026
Merged

[feat] add a thermal status row to the debug overlay panel#248
Manabu-GT merged 3 commits into
mainfrom
feat/issue-246

Conversation

@Manabu-GT
Copy link
Copy Markdown
Owner

@Manabu-GT Manabu-GT commented May 18, 2026

Summary by CodeRabbit

  • New Features

    • Added optional thermal status row to the compact debug overlay (Android 11+ with thermal HAL support). Enable via OverlayMode.FullMetrics(showThermal = true). Displays device thermal throttling states: None, Light, Moderate, Severe, Critical, Emergency, and Shutdown.
  • Documentation

    • Updated README and CHANGELOG with thermal status row configuration and usage instructions.
  • Tests

    • Added comprehensive test coverage for thermal status monitoring.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Warning

Rate limit exceeded

@Manabu-GT has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 38 minutes and 42 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38e3be18-23de-447a-964c-db611180e39a

📥 Commits

Reviewing files that changed from the base of the PR and between cb07b5e and c848b90.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • README.md
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/OverlayMode.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/model/ThermalState.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSource.kt
  • debugoverlay-core/src/test/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSourceTest.kt
  • sample/src/debugOverlay/kotlin/com/ms/square/debugoverlay/sample/DebugOverlaySetup.kt
📝 Walkthrough

Walkthrough

This PR introduces an optional thermal-status row to the debug overlay's compact metrics panel. It adds thermal data collection via Android thermal APIs (API 30+), opt-in configuration through OverlayMode.FullMetrics(showThermal = true), heuristic thermal-level derivation, and conditional UI rendering that hides the row on unsupported devices. Sample app and documentation are updated accordingly.

Changes

Thermal Status Display Feature

Layer / File(s) Summary
Thermal state data model
debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/model/ThermalState.kt
ThermalState data class and ThermalStatus enum mirror platform thermal levels (NONE through SHUTDOWN) plus UNSUPPORTED for devices lacking thermal HAL support.
Thermal data source and tests
debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSource.kt, debugoverlay-core/src/test/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSourceTest.kt
ThermalDataSource combines push-based OnThermalStatusChangedListener updates with 10-second headroom polling fallback. deriveThermalStatus maps platform status or synthesizes status from headroom thresholds. API-level gating returns UNSUPPORTED on pre-R devices. Comprehensive test coverage validates API gating, status mapping, headroom thresholding, and NaN fallback.
Thermal metrics source integration
debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/source/DebugOverlayPanelDataSource.kt
DebugOverlayPanelDataSource interface exposes separate thermalState: Flow<ThermalState> property. Implementation instantiates ThermalDataSource and shares its flow with SharingStarted.WhileSubscribed and replay=1.
Public API: overlay mode configuration
debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/OverlayMode.kt
OverlayMode.FullMetrics gains optional showThermal: Boolean parameter (default false) with KDoc documenting Android 11+ requirement and behavior on unsupported devices.
Overlay view manager integration
debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/OverlayViewManager.kt
OverlayViewManager conditionally derives thermalFlow based on mode.showThermal, collects it as thermalState, and passes it to DraggableOverlayPanel.
Thermal row UI rendering and styling
debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/ui/DebugOverlayPanel.kt, debugoverlay-core/src/main/res/values/strings.xml
DraggableOverlayPanel and DebugOverlayPanel accept optional thermalState parameter. Metrics column conditionally renders ThermalRow composable with color and label mappings (toStatusColor(), compactLabelResId()) when status is not UNSUPPORTED. Eight string resources provide localized thermal level abbreviations.
Sample app integration and documentation
sample/src/debugOverlay/kotlin/com/ms/square/debugoverlay/sample/DebugOverlaySetup.kt, sample/src/main/kotlin/com/ms/square/debugoverlay/sample/ui/overlaytests/OverlayTestsScreen.kt, debugoverlay-core/src/debug/kotlin/com/ms/square/debugoverlay/internal/ui/DebugOverlayPanelPreviews.kt, CHANGELOG.md, README.md
DebugOverlaySetup configures thermal row with showThermal = true. OverlayTestsScreen adds isThermalOn state and toggle card to show/hide thermal row on demand. Preview composables and sample tests updated to include thermal state. CHANGELOG and README document feature, API requirements, and usage configuration.

Sequence Diagram

sequenceDiagram
  participant Config as User Configuration
  participant OVM as OverlayViewManager
  participant TDS as ThermalDataSource
  participant PM as PowerManager
  participant Panel as DebugOverlayPanel
  participant UI as ThermalRow Composable
  
  Config->>OVM: OverlayMode.FullMetrics(showThermal=true)
  OVM->>TDS: subscribe to thermalState flow
  par Push Updates
    PM->>TDS: OnThermalStatusChanged event
  and Periodic Polling
    TDS->>PM: getThermalHeadroom every 10s
  end
  TDS->>TDS: deriveThermalStatus(raw status, headroom)
  TDS->>OVM: emit ThermalState(status)
  OVM->>Panel: pass thermalState parameter
  Panel->>UI: render if status != UNSUPPORTED
  UI->>UI: apply toStatusColor + compactLabelResId
  UI-->>Config: display colored thermal indicator
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • Manabu-GT/DebugOverlay-Android#232: Both PRs touch the same OverlayMode.FullMetrics definition and corresponding overlay-render branching in OverlayViewManager, though one adds showThermal for a thermal row while the other introduces OverlayMode.Hidden and openPanel handling.
  • Manabu-GT/DebugOverlay-Android#213: Both PRs modify OverlayMode.FullMetrics and the overlay/panel rendering path that branches on FullMetrics; PR #213 added customTabs support while this PR adds showThermal and thermal state wiring at the same core dispatch points.

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the primary change: adding a thermal status row to the debug overlay panel, which is the main focus of this pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-246

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown

claude Bot commented May 18, 2026

Claude encountered an error after 2m 55s —— View job


Claude PR Review

Reviewing PR #248: thermal status row

  • Gather context (read changed files)
  • Analyze new ThermalDataSource for correctness
  • Review ThermalState model
  • Review UI changes (DebugOverlayPanel)
  • Review tests
  • Check OverlayMode and DataRepository wiring
  • Post final review
    · branch feat/issue-246

@Manabu-GT Manabu-GT added this to the v2.6.0 milestone May 18, 2026
@Manabu-GT Manabu-GT self-assigned this May 18, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 192: The README currently lists thermal statuses as
PowerManager.THERMAL_STATUS_* using UI abbreviations
(NONE/LIGHT/MOD/SEV/CRIT/EMER/SHUT); update this to avoid confusion with actual
Android API constants by either expanding the abbreviations to the official
constant names (e.g., THERMAL_STATUS_MODERATE, THERMAL_STATUS_SEVERE,
THERMAL_STATUS_CRITICAL, THERMAL_STATUS_EMERGENCY, THERMAL_STATUS_SHUTDOWN) or
explicitly note that MOD/SEV/CRIT/EMER/SHUT are UI labels and provide a
parenthetical mapping to the corresponding PowerManager.THERMAL_STATUS_*
constants so readers can unambiguously map UI labels to API constants.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b149842-8cc4-40f6-8ba3-c2af3b626c66

📥 Commits

Reviewing files that changed from the base of the PR and between 2bdfc2e and 82f3abd.

📒 Files selected for processing (14)
  • CHANGELOG.md
  • README.md
  • debugoverlay-core/src/debug/kotlin/com/ms/square/debugoverlay/internal/ui/DebugOverlayPanelPreviews.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/OverlayMode.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/OverlayViewManager.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/model/DebugOverlayPanelMetrics.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/model/ThermalState.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/source/DebugOverlayPanelDataSource.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSource.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/ui/DebugOverlayPanel.kt
  • debugoverlay-core/src/main/res/values/strings.xml
  • debugoverlay-core/src/test/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSourceTest.kt
  • sample/src/debugOverlay/kotlin/com/ms/square/debugoverlay/sample/DebugOverlaySetup.kt
  • sample/src/main/kotlin/com/ms/square/debugoverlay/sample/ui/overlaytests/OverlayTestsScreen.kt

Comment thread README.md Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 18, 2026

Qodana for JVM

It seems all right 👌

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ View the detailed Qodana report

Contact Qodana team

Contact us at qodana-support@jetbrains.com

@claude
Copy link
Copy Markdown

claude Bot commented May 18, 2026

Claude finished @Manabu-GT's task in 4m 3s —— View job


Claude PR Review

  • Read changed files
  • Analyze ThermalDataSource for correctness and concurrency
  • Review ThermalState model
  • Review UI changes (DebugOverlayPanel)
  • Review tests
  • Check OverlayMode + DataSource wiring
  • Post final review

Overall this is a well-scoped, well-tested feature with thoughtful API-level fallbacks (push + poll, headroom heuristic, UNSUPPORTED graceful degradation) and good doc references to Google's ADPF guidance. The bulk of the implementation is solid. Below are the items I'd flag.


🟠 Medium — ThermalDataSource runs on every supported device even when showThermal = false

DebugOverlayPanelDataSourceImpl always wires thermalDataSource.thermalState() into the combine block (DebugOverlayPanelDataSource.kt:23,36), regardless of OverlayMode.FullMetrics.showThermal. On any Android 11+ device with a working thermal HAL, this means the library will:

  • Register a PowerManager.OnThermalStatusChangedListener on the main executor
  • Poll getThermalHeadroom() every 10 seconds for the lifetime of the overlay subscription

…even for users who never enabled the row. Suggest gating this — e.g., expose showThermal to the data source and emit flowOf(ThermalState(UNSUPPORTED)) when the row is disabled, or skip the field entirely from DebugOverlayPanelMetrics. The wasted work is small per tick but it's measurable battery overhead the user didn't opt into.

Fix this →


🟠 Medium — isSupported is permanently latched at first access

In ThermalDataSource.kt:102-104:

val isSupported: Boolean by lazy {
  isApiLevelSupported() && hasUsableHeadroom()
}

The probe calls getThermalHeadroom(0) the first time isSupported is read and caches the verdict forever. The same ADPF doc you reference notes that getThermalHeadroom() may return NaN/0 if called too early — specifically before the system has accumulated thermal data after a cold boot. If the overlay starts up before the HAL is "warm" (e.g. shortly after device boot, or right at process launch on a cold start), the device gets permanently classified as UNSUPPORTED for the rest of the process lifetime, even though it actually supports the API.

Consider moving the probe into the flow itself (retry on each subscription or on first headroom emission), or expose a one-time "retry" mechanism. The simplest fix: drop by lazy and let headroomFlow() emit NaN until the HAL returns a real value, with deriveThermalStatus already handling NaN correctly. A subscriber would then see NONE until the HAL warms up — better than UNSUPPORTED forever.


🟡 Minor — callbackFlow for status uses default RENDEZVOUS buffer

ThermalDataSource.kt:136-144 uses callbackFlow { … } with default settings, which gives a rendezvous-channel-backed flow. trySend will return false (silently dropping the value) if there is no active collector slot. The seed trySend(powerManager.currentThermalStatus) immediately after addThermalStatusListener should normally succeed because combine is already collecting, but the listener callback could in theory drop intermediate transitions during slow downstream collection. The comment about "duplicate emission is harmless" addresses one direction but not drops.

Consider .buffer(Channel.CONFLATED) or bufferOverflow = BufferOverflow.DROP_OLDEST so that the most recent status always wins without dropping the latest update:

private fun statusFlow(): Flow<Int> = callbackFlow {
  …
}.conflate()

This is a minor robustness fix — the practical impact is small because the data flows into a combine that's downstream of shareIn(replay = 1).


🟡 Minor — UI inconsistencies in ThermalRow

In DebugOverlayPanel.kt:

  1. Line 247-275: The row uses a different visual structure than the other 4 rows (CpuRow/HeapRow/PssRow/FpsRow all delegate to MetricRow with a line graph; ThermalRow is bespoke). This is reasonable because thermal status is categorical, not a time-series, but the inconsistency is worth a one-line comment explaining the design choice. Right now it just reads as "we forgot the graph".
  2. Line 277-285: toStatusColor() maps MODERATE and SEVERE to the same STATUS_COLOR_WARNING. SEVERE already triggers throttling — arguably it deserves the same color as CRITICAL/EMERGENCY (red). Compare to the headroom heuristic above where >= 1.0 (SEVERE) is the throttling threshold per Android docs.
  3. Line 296: ThermalStatus.UNSUPPORTED -> R.string.debugoverlay_thermal_status_none_abbr is defensive but misleading if it ever leaked through. A "" or a dedicated _unsupported_abbr string would be safer than silently labeling an unsupported device as "None".
  4. Line 263: The "Therm" label is 5 characters where every other row uses 3 (Cpu/Heap/Pss/Fps). On the 120dp width constraint with status text like "Mod"/"Sev"/"Crit", this is fine, but "Tmp" would match the visual rhythm of the other rows.

🟡 Minor — ThermalState is @Serializable but never serialized

ThermalState.kt:13-14 and the enum at line 23 are both @Serializable. Only DeviceInfo is @Serializable in internal/data/model/, and it's used in bug reports. ThermalState doesn't appear to be referenced by any serializer in this PR. Either drop the annotation (and the dependency that comes with it) or wire it into the bug report (which seems valuable — thermal state during a bug repro window is exactly the kind of context that helps triage).


🟡 Minor — README API-constant naming (already flagged by CodeRabbit)

README.md:192 lists NONE/LIGHT/MOD/SEV/CRIT/EMER/SHUT as PowerManager.THERMAL_STATUS_* levels, but those abbreviated forms are UI labels — the actual constants are THERMAL_STATUS_MODERATE, THERMAL_STATUS_SEVERE, THERMAL_STATUS_CRITICAL, THERMAL_STATUS_EMERGENCY, THERMAL_STATUS_SHUTDOWN. Either expand them, or relabel them clearly as "compact UI labels". Concur with CodeRabbit's analysis here.


✅ Things I liked

  • The split between deriveThermalStatus (pure, unit-tested) and the IO side of ThermalDataSource is clean and pays off in the test file.
  • The listener-then-seed ordering with the explicit "duplicate emission is harmless" comment shows the right concurrency reasoning, even if I'd pair it with .conflate().
  • @ChecksSdkIntAtLeast / @RequiresApi annotations + @Suppress("NewApi") guards are correctly applied.
  • The NaN handling in deriveThermalStatus is explicit and unit-tested.
  • runCatchingNonCancellation correctly preserves structured concurrency around the getThermalHeadroom call.
  • Test names are descriptive and use the project's // region convention sparingly and well.
  • Backwards-compatible default showThermal = false keeps the change opt-in.

Test coverage gaps

ThermalDataSourceTest cannot exercise the supported path because Robolectric doesn't shadow getThermalHeadroom — that's a real limitation acknowledged in the file header. Consider an instrumented test on androidTest for at least the basic "supported device emits NONE initially" path. Optional; current coverage of the pure helper is already solid.


· branch feat/issue-246

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/model/ThermalState.kt`:
- Around line 14-16: Update the KDoc for the ThermalState sealed values to match
runtime behavior: change the description that currently states "API 30+ device
with an incomplete thermal HAL maps to UNSUPPORTED" so it instead documents that
path maps to NONE. Locate the ThermalState enum/sealed class and adjust the KDoc
for the UNSUPPORTED and NONE entries (references: ThermalState, UNSUPPORTED,
NONE) so UNPSUPPORTED remains for devices lacking API and NONE documents the
API30+ incomplete HAL case.

In
`@debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSource.kt`:
- Around line 80-82: The KDoc on ThermalDataSource incorrectly states that the
flow "stays open" for unsupported devices, but the implementation uses
flowOf(...) (at the creation in ThermalDataSource) which emits a single
ThermalStatus.UNSUPPORTED and completes; update the KDoc on the class or the
providing function to state that on Android < 11 the data source emits a single
ThermalStatus.UNSUPPORTED and completes (or alternatively change the
implementation to return a never-ending flow if you really need it to stay
open); reference ThermalDataSource and the flowOf(...) usage when making the
doc-change so the behavior and docs match.

In
`@debugoverlay-core/src/test/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSourceTest.kt`:
- Around line 20-21: The KDoc for the ThermalDataSourceTest currently states
that an incomplete thermal HAL should be treated as `UNSUPPORTED`, but the test
suite asserts `NONE`; update the KDoc in ThermalDataSourceTest (the
class/comment block above the test cases) to state that an incomplete HAL is
treated as `NONE` to match the asserted behavior in the tests (adjust the
wording in the comment near the tests that mention incomplete HAL / ADPF
guidance so it reflects `NONE` instead of `UNSUPPORTED`).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 191e2f84-6719-4421-9d41-4edf7a67daf5

📥 Commits

Reviewing files that changed from the base of the PR and between 82f3abd and cb07b5e.

📒 Files selected for processing (8)
  • README.md
  • debugoverlay-core/src/debug/kotlin/com/ms/square/debugoverlay/internal/ui/DebugOverlayPanelPreviews.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/OverlayViewManager.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/model/ThermalState.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/source/DebugOverlayPanelDataSource.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSource.kt
  • debugoverlay-core/src/main/kotlin/com/ms/square/debugoverlay/internal/ui/DebugOverlayPanel.kt
  • debugoverlay-core/src/test/kotlin/com/ms/square/debugoverlay/internal/data/source/ThermalDataSourceTest.kt
✅ Files skipped from review due to trivial changes (1)
  • README.md

@Manabu-GT Manabu-GT merged commit 57b9d70 into main May 19, 2026
5 checks passed
@Manabu-GT Manabu-GT mentioned this pull request May 19, 2026
2 tasks
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