Troubleshoot Battery Drain
Android Battery Drain Triage (adb)
Workflow for diagnosing unexplained battery drain, overheating, or sluggishness on Android devices (validated on a Samsung Galaxy S24+, One UI 7/8). Samsung's built-in battery UI is unreliable for this; these steps pull the real data.
Prerequisites
On the phone:
- Settings > About phone > Software information > tap Build number 7 times.
- Settings > Developer options > enable USB debugging.
If USB will not enumerate ("Couldn't switch" when tapping Connected device), use wireless debugging instead:
Settings > Developer options > Wireless debugging > Pair device with pairing code
adb pair <IP>:<PAIRING_PORT>
adb connect <IP>:<CONNECT_PORT>
adb devices
The pairing port and the connect port are different. Phone and workstation must be on the same subnet with client isolation off.
1. Arm the capture
adb devices
adb shell dumpsys batterystats --reset
adb shell dumpsys batterystats --enable full-wake-history
Unplug immediately after. Counters only accumulate while discharging.
Without --enable full-wake-history you get aggregate counters only. With it, you get individual acquire/release events tagged with the owning package.
2. Wait
| Window | Use case |
|---|---|
| 45 min, screen off, untouched | Quick idle check |
| Overnight (8+ hrs) | Best signal-to-noise |
| Full day + overnight | Catches movement/location-triggered apps |
Do not let the battery reach 100% during the window. The transition into full charge wipes the counters. Stop any top-up around 90%.
Normal use during the window is fine. batterystats buckets everything into screen-on, screen-off/doze, and not-on-battery separately.
3. Dump (before plugging in)
adb shell dumpsys batterystats > batterystats.txt
adb shell dumpsys deviceidle > deviceidle.txt
adb shell dumpsys power > power.txt
adb shell dumpsys bluetooth_manager > bt.txt
adb shell dumpsys alarm > alarm.txt
Use wireless debugging if the phone needs to stay unplugged while dumping. deviceidle is only meaningful on battery.
4. Read
grep -n -m 1 -A 8 "Time on battery" batterystats.txt
grep -n -A 50 "Estimated power use" batterystats.txt
grep -n -i -A 40 "All partial wake locks" batterystats.txt
grep -n -i "wake lock" power.txt
grep -n -i -E "mState|mLightState" deviceidle.txt
grep -n -i -B 2 -A 20 "Alarm Stats" alarm.txt
grep -n -i -E "scan|LE " bt.txt | head -40
| Source | What it answers |
|---|---|
Time on battery |
CPU awake ratio, whether Doze engaged |
Estimated power use |
Ranked mAh attribution by subsystem and uid |
All partial wake locks |
Cumulative hold time and acquisition count per package |
power.txt |
Wakelocks held right now, with owner |
deviceidle |
Doze state machine |
alarm |
Wakeup alarm counts per package |
bluetooth_manager |
BLE scan activity and bonded devices |
5. Resolve UIDs
Wakelocks are tagged u0aNNN. Add 10000 to get the real uid (u0a787 = uid 10787):
adb shell pm list packages -U | grep -E "uid:(10787|10368)"
Wakelock names are tags, not packages. com.google.firebase.iid.WakeLockHolder is the Firebase Cloud Messaging tag used by dozens of apps. Always look up the uid, never grep the tag string against the package list.
Interpretation
The key line
Time on battery screen off: 8h 41m realtime, 2h 46m uptime
Realtime vs uptime for the screen-off row is CPU awake time with the screen off.
| Awake ratio | Assessment |
|---|---|
| Under 10% | Healthy |
| 10-30% | Some background activity, worth a look |
| Over 50% | Something is blocking Doze |
Also check Screen doze discharge and Time on battery screen doze. If doze time is near zero, the device never slept.
Reference rates
| Metric | Healthy | Pathological (observed) |
|---|---|---|
| Screen-off drain | under 1%/hr | 4%/hr |
| CPU awake, screen off | under 10% | 84% |
Two patterns to flag in All partial wake locks
- Long single holds, especially
max=600000(the 10-minute framework cap). A job running to its ceiling is not completing, it is being killed. - Huge acquisition counts with tiny durations (thousands of ~19ms holds). Cumulative time looks trivial, but the acquire/release churn prevents deep sleep. Seen with Cast discovery failing to settle: 10,000+ acquisitions in 45 minutes.
Caveats
mState/mLightStateare only meaningful while unplugged. Charging blocks Doze by design, so a dump taken plugged in always readsACTIVE.Capacity: 4700, Rated: 4755in the power use header is the gauge's learned full-charge capacity vs nameplate. Use it to assess real battery health. Settings > Battery > Battery information often just prints the spec sheet.Computed drainmatchingactual drainmeans the fuel gauge and the accounting agree. A large mismatch suggests gauge miscalibration.- Sample in the condition where the suspect app is active. Movement-triggered apps (location, drive detection) look innocent in a stationary idle window.
Remediation
Settings > Apps > [app] > Battery > Restricted
Verify Doze exemptions:
adb shell dumpsys deviceidle whitelist
An app absent from the whitelist is already on Optimized. Switching Unrestricted to Optimized changes nothing for a foreground-service-backed job that holds long wakelocks; the only lever there is disabling the feature inside the app itself.
Never restrict apps that deliver safety-critical alerts (medical alarms, security systems, emergency notifications). Suppressing background execution can silence them.
Common culprits
| Pattern | Typical source |
|---|---|
| Long continuous accelerometer or location wakelock | Drive/activity detection apps |
AirshipWorker hitting the 600s cap |
Airship marketing SDK in retail apps |
Thousands of CastSocketMultiplexer acquisitions |
Cast endpoint flapping or unreachable |
| Persistent foreground service notification stuck | Hung backup or sync job |
com.evernote.android.job.JobRescheduleService |
Deprecated job library, self-rescheduling |
A stuck foreground service (e.g. a cloud backup that never progresses) blocks Doze entirely for as long as its notification is up, and survives reboots because the job re-registers on boot.
Optional: Battery Historian
Timeline GUI showing per-process wakelock, alarm, and radio activity.
adb bugreport bugreport.zip
docker run -p 9999:9999 gcr.io/android-battery-historian/stable:3.0 --port 9999
Upload the zip at http://localhost:9999.
Notes
- Samsung's Settings > Battery screen regularly accounts for under 15% of actual drain. If nothing there exceeds 6% while the device burns a full battery per day, the consumption is not in a user app and the UI will not find it.
*#0228#and similar service codes are blocked on recent Samsung firmware.- Capture a baseline once the device is behaving normally, so future runs have something to compare against.