DEV Community

Super Funicular
Super Funicular

Posted on

The Hardest Part of Running a Phone as a 24/7 Camera Isn't the Camera — It's Heat and the Battery

The problem nobody warns you about

I build Background Camera RemoteStream, an Android app that turns a spare phone into a local-only security camera — record with the screen off, view a live feed over a built-in web server, no cloud, no account. Most of the hard engineering I've written about is about keeping the camera open: the Camera2 session lifecycle with the screen off, the foreground-service-type contract Android 14 added, and embedding a Ktor server in the same process.

But once the camera stays open, a second problem shows up that none of those posts solve: a phone recording video continuously, plugged into a charger, day after day, is a small thermal engine sitting on top of a lithium-ion battery. Get the heat story wrong and one of two things happens — the OS throttles you until frames drop, or you slowly cook the battery of the device you're trying to keep alive for months.

This post is about that second half of the problem: what the platform actually gives you to manage heat (the Thermal API), how the battery physics really work, how Doze and App Standby interact with a long-running camera service, and the design decisions that fall out of taking all three seriously.

Two heat sources, stacked

A phone recording 1080p video is already working: the image sensor, the ISP, and the hardware H.264/HEVC encoder all run continuously. That alone warms the device. Now plug it in — because a 24/7 camera obviously can't run on battery — and you've added a second heat source directly adjacent to the first. Charging is exothermic, and the charge circuitry sits millimeters from the cell.

So the steady state of "old phone as a camera" is: encode heat + charge heat, with no screen-off idle periods to cool down, often in an enclosed spot (a shelf, a windowsill, a 3D-printed mount) with poor airflow. This is a fundamentally different thermal profile than the one phones are designed around, which assumes bursts of activity followed by cool-down.

You cannot make the heat go away. What you can do is measure it and respond to it before the OS responds for you.

The Thermal API: measure before you melt

Since Android 10 (Q), the platform exposes device thermal state through PowerManager, and it's the single most useful tool for this class of app. There are two complementary halves.

Discrete status via a listener. You register a PowerManager.OnThermalStatusChangedListener and get called when the device crosses a threshold. The statuses run THERMAL_STATUS_NONE → LIGHT → MODERATE → SEVERE → CRITICAL → EMERGENCY → SHUTDOWN. Anything at MODERATE or above means the system has already started mitigating — dropping clocks, and eventually killing your throughput.

val pm = getSystemService(Context.POWER_SERVICE) as PowerManager

val thermalListener = PowerManager.OnThermalStatusChangedListener { status ->
    when (status) {
        PowerManager.THERMAL_STATUS_NONE,
        PowerManager.THERMAL_STATUS_LIGHT -> { /* full quality */ }

        PowerManager.THERMAL_STATUS_MODERATE ->
            recorder.stepDownQuality()   // drop bitrate / resolution / fps

        PowerManager.THERMAL_STATUS_SEVERE,
        PowerManager.THERMAL_STATUS_CRITICAL ->
            recorder.enterSurvivalMode()  // minimum viable capture, pause charging if possible

        else -> recorder.pauseCapture()
    }
}

pm.addThermalStatusListener(mainExecutor, thermalListener)
Enter fullscreen mode Exit fullscreen mode

Continuous forecast via headroom. The status listener tells you where you are; getThermalHeadroom(forecastSeconds) tells you where you're heading. It returns a float where 1.0 corresponds to being throttled at THERMAL_STATUS_SEVERE, so a value creeping toward 0.9 is your early-warning signal. Two constraints the docs are explicit about: it only tracks slow-moving sensors like the skin-temperature sensor, and there is no benefit to polling it more than about once per second — call it much faster and it can return NaN. Android 16 adds a headroom-threshold callback (AThermal_HeadroomCallback) so you can be notified on crossings instead of polling, which is the direction to move if you target the newest APIs.

The architectural point: a screen-off camera should treat thermal headroom as a first-class input to its capture pipeline, exactly the way a game uses it to scale rendering. The app's job isn't to prevent heat — it's to degrade gracefully so the stream survives a hot afternoon instead of the OS force-killing the whole capture.

Graceful degradation beats getting killed

When headroom gets tight, you have a ladder of things to give up, cheapest first:

  1. Frame rate. Going from 30 fps to 15 fps roughly halves encoder work and is nearly invisible for a security feed, where you care about "is someone there," not cinematic motion.
  2. Bitrate. The hardware encoder's power draw scales with bitrate. Halving it is the second-cheapest quality dial.
  3. Resolution. Dropping 1080p → 720p is a large, immediate reduction in sensor readout, ISP, and encode load.
  4. Duty cycle. If you're still overheating at the floor, capture in intervals — a few seconds every N seconds — instead of continuously.

The reason to do this in the app rather than let the platform do it is that the platform's mitigation is blunt: it throttles the whole SoC, which can make your entire pipeline stutter, drop the network stack, and make the live view unwatchable. Your own step-down is surgical — you spend exactly the quality you need to spend to stay under the ceiling, and you keep the feed alive.

The battery is the part you're trying to save

Here's the counterintuitive bit. The whole pitch of the app is "reuse the phone you already own instead of buying a $40 camera and a subscription." That only works if the phone survives. And the fastest way to kill a lithium-ion cell is the exact environment a plugged-in camera creates: high temperature combined with high state of charge, held constantly.

Two mechanisms compound here:

  • Heat accelerates chemical aging. Elevated cell temperature speeds the side reactions that permanently reduce capacity. A cell held warm ages measurably faster than one at room temperature.
  • Sitting at 100% is its own stressor. A Li-ion cell parked at full charge, especially while warm, degrades faster than one cycled in a middle band. A 24/7 camera plugged in "forever" sits at 100% almost all the time — the worst-case combination.

This is why the honest engineering answer to "can I just leave it plugged in?" is yes, but you should manage the charge. Some OEMs now expose this directly — charge limits (cap at 80%), adaptive/optimized charging, or true "bypass charging" where the phone runs from the wall while holding the battery at a set level. Where the device supports it, capping charge at ~80% and improving airflow does more for battery longevity than anything an app can do in software.

What the app can do is avoid making it worse: keep writes local so there's no continuous cellular/Wi-Fi upload adding radio heat (this falls out of the local-only, no-cloud architecture for free), and back off capture load under thermal pressure so the SoC isn't dumping maximum heat into the pack. Zero-cloud isn't only a privacy choice here — a design that never uploads is also a design that runs cooler.

Doze, App Standby, and why a foreground service is the point

There's a common worry when you say "background camera": won't Android's battery optimizations just kill it? For this specific architecture, no — and understanding why is worth a paragraph.

Doze puts the device into deep sleep when the screen is off and it's been stationary and unused, deferring background work to batch windows. App Standby Buckets separately rank apps by usage — active, working_set, frequent, rare, restricted — and throttle the lower buckets harder. The saving grace: an app running a long-running foreground service is kept in the active bucket, and a properly-typed foreground camera service is exempt from the deferrals Doze imposes on ordinary background work. That's the entire reason the foreground-service-type contract matters so much: it's not just permission to hold the camera, it's what keeps the OS from treating your capture as deferrable background noise.

The 2026 caveat: Android 16 tightened this. Historically a foreground service had no execution time limit; that assumption is changing, with new job-execution quotas and a harsher restricted bucket that can strand an app that hasn't been opened in days. Camera foreground services aren't the type that got the new hard time limits (those landed on dataSync/mediaProcessing), but the direction of travel is clear: the platform is steadily less willing to let anything run unbounded. The defensive posture is to stay legitimately in the active bucket — which a live, user-initiated camera service does — and to not rely on undocumented "it just keeps running forever" behavior.

What this means for how you deploy one

The engineering distills into a short field guide, and it's worth stating plainly because it's the part users actually feel:

  • Give it air. The single biggest lever is passive: don't seal the phone in a case or a closed box. Airflow beats every software mitigation.
  • Cap the charge if your phone can. An 80% charge limit or bypass charging, where available, is the highest-impact thing you can do for battery lifespan.
  • Let it step down. A feed that drops to 15 fps / 720p on a hot day and recovers at night is working correctly; that's the Thermal API doing its job, not a bug.
  • Prefer local. No upload means no radio heat and no cloud dependency — cooler and more private at the same time.
  • Know the honest limits. This is an indoor, powered, room-temperature-ish setup. It is not a weatherproof outdoor camera, and a phone baking on a sunny windowsill in July will throttle no matter how good the software is.

The takeaway

The camera was never the hard part. Modern Camera2 and the foreground-service system make "hold the sensor open with the screen off" a solved problem. The hard part of turning an old phone into a durable 24/7 camera is thermodynamics: two stacked heat sources, no idle cool-down, and a battery that hates exactly the conditions you're subjecting it to. The platform hands you real tools — the Thermal API to measure and forecast, foreground-service typing to stay scheduled, and increasingly strict power management you have to design with rather than against. Treat heat and charge as first-class inputs to the capture pipeline, degrade gracefully instead of getting throttled, and the reused phone you're trying to save actually lasts.


Background Camera RemoteStream is a free, local-only Android camera app — record with the screen off, watch a live feed over a built-in web server, stream to YouTube Live, zero cloud dependency. Get it on Google Play or read more at superfunicular.com. Built in Kotlin with Camera2, Ktor, and a lot of AI-assisted sessions.

Top comments (0)