Skip to content

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:

  1. Settings > About phone > Software information > tap Build number 7 times.
  2. 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

  1. Long single holds, especially max=600000 (the 10-minute framework cap). A job running to its ceiling is not completing, it is being killed.
  2. 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 / mLightState are only meaningful while unplugged. Charging blocks Doze by design, so a dump taken plugged in always reads ACTIVE.
  • Capacity: 4700, Rated: 4755 in 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 drain matching actual drain means 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.