Three shell scripts to audit battery health, performance, and storage on an Android device over USB from your Mac.
Never run these scripts on a device you don't own or don't have explicit permission to audit. adb shell gives broad read access to the device's userspace.
| Script | What it does |
|---|---|
android_battery_check.sh |
Prints a live battery report: charge level, health, temperature, voltage, cycle count, and estimated capacity degradation |
android_speed_audit.sh |
Generates a full performance report on your Desktop: memory, CPU, packages, battery stats, and crash/ANR hints |
android_storage_diagnose.sh |
Generates a full storage report on your Desktop: biggest folders and files, media usage, SD card vs internal breakdown |
ADB (Android Debug Bridge) is the tool that lets your Mac talk to the phone over USB.
brew install android-platform-tools
Verify it installed correctly:
adb version
On some devices — Samsung, Xiaomi, OnePlus — Developer options may be under Settings → General management or Settings → Additional settings.
Plug your phone into your Mac with a USB cable. On the phone, a dialog will appear:
Allow USB debugging?
Tap Allow. Check Always allow from this computer to avoid being prompted again.
Verify the connection:
adb devices
You should see something like:
List of devices attached
R5CW30XXXXX device
If it shows unauthorized, unlock your phone and look for the authorization prompt. If it shows nothing at all, try a different cable — many cables are charge-only and carry no data.
Save the three scripts somewhere convenient, for example ~/scripts/:
mkdir -p ~/scripts
Then make them executable:
chmod +x ~/scripts/android_battery_check.sh
chmod +x ~/scripts/android_speed_audit.sh
chmod +x ~/scripts/android_storage_diagnose.sh
Prints a live battery summary directly in the terminal. Reads from dumpsys battery and dumpsys batteryproperties and maps raw integer codes to human-readable values.
Cycle count and estimated battery health depend on data exposed by your device's firmware. Many manufacturers don't expose this — those fields will show N/A. Pixel devices are the most reliable for complete data.
Run it:
~/scripts/android_battery_check.sh
Full script:
#!/usr/bin/env bash
set -Eeuo pipefail
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "Missing required command: $1" >&2
exit 1
}
}
require_cmd adb
require_cmd awk
require_cmd grep
require_cmd sed
require_cmd tr
require_cmd date
adb start-server >/dev/null
DEVICE_STATE="$(adb get-state 2>/dev/null || true)"
if [[ "$DEVICE_STATE" != "device" ]]; then
echo "No authorized Android device detected."
echo "Check USB debugging, cable, and phone authorization."
exit 1
fi
adbsh() {
adb shell "$@" 2>/dev/null | tr -d '\r'
}
get_prop() {
adbsh "getprop $1" | head -n 1
}
battery_dump="$(adbsh dumpsys battery)"
battery_props="$(adbsh dumpsys batteryproperties || true)"
extract_battery_field() {
local key="$1"
printf '%s\n' "$battery_dump" | awk -F': ' -v k="$key" '$1 ~ k {print $2; exit}'
}
extract_prop_field() {
local pattern="$1"
printf '%s\n' "$battery_props" | awk -F'=|: ' -v p="$pattern" '$0 ~ p {print $NF; exit}'
}
manufacturer="$(get_prop ro.product.manufacturer)"
model="$(get_prop ro.product.model)"
device="$(get_prop ro.product.device)"
android_version="$(get_prop ro.build.version.release)"
build_id="$(get_prop ro.build.display.id)"
level="$(extract_battery_field 'level')"
scale="$(extract_battery_field 'scale')"
status_raw="$(extract_battery_field 'status')"
health_raw="$(extract_battery_field 'health')"
present="$(extract_battery_field 'present')"
plugged_raw="$(extract_battery_field 'plugged')"
voltage_mv="$(extract_battery_field 'voltage')"
temp_tenths="$(extract_battery_field 'temperature')"
technology="$(extract_battery_field 'technology')"
charge_counter_uah="$(extract_battery_field 'charge counter')"
battery_cycle_count="$(extract_prop_field 'cycle|cycle_count|batteryCycleCount')"
battery_full_charge_uah="$(extract_prop_field 'full.*charge|chargeFull|batteryFullCharge')"
battery_design_uah="$(extract_prop_field 'design.*cap|batteryDesignCapacity')"
map_status() {
case "${1:-}" in
1) echo "Unknown" ;;
2) echo "Charging" ;;
3) echo "Discharging" ;;
4) echo "Not charging" ;;
5) echo "Full" ;;
*) echo "Unknown" ;;
esac
}
map_health() {
case "${1:-}" in
1) echo "Unknown" ;;
2) echo "Good" ;;
3) echo "Overheat" ;;
4) echo "Dead" ;;
5) echo "Over-voltage" ;;
6) echo "Unspecified failure" ;;
7) echo "Cold" ;;
*) echo "Unknown" ;;
esac
}
map_plugged() {
case "${1:-}" in
0) echo "Unplugged" ;;
1) echo "AC charger" ;;
2) echo "USB" ;;
4) echo "Wireless" ;;
8) echo "Dock" ;;
*) echo "Unknown" ;;
esac
}
status="$(map_status "$status_raw")"
health="$(map_health "$health_raw")"
plugged="$(map_plugged "$plugged_raw")"
temp_c="N/A"
if [[ -n "${temp_tenths:-}" && "${temp_tenths:-}" =~ ^-?[0-9]+$ ]]; then
temp_c="$(awk "BEGIN { printf \"%.1f\", $temp_tenths / 10 }") °C"
fi
voltage_v="N/A"
if [[ -n "${voltage_mv:-}" && "${voltage_mv:-}" =~ ^[0-9]+$ ]]; then
voltage_v="$(awk "BEGIN { printf \"%.3f\", $voltage_mv / 1000 }") V"
fi
charge_counter_mah="N/A"
if [[ -n "${charge_counter_uah:-}" && "${charge_counter_uah:-}" =~ ^-?[0-9]+$ ]]; then
charge_counter_mah="$(awk "BEGIN { printf \"%.0f\", $charge_counter_uah / 1000 }") mAh"
fi
full_charge_mah="N/A"
if [[ -n "${battery_full_charge_uah:-}" && "${battery_full_charge_uah:-}" =~ ^-?[0-9]+$ ]]; then
full_charge_mah="$(awk "BEGIN { printf \"%.0f\", $battery_full_charge_uah / 1000 }") mAh"
fi
design_capacity_mah="N/A"
if [[ -n "${battery_design_uah:-}" && "${battery_design_uah:-}" =~ ^-?[0-9]+$ ]]; then
design_capacity_mah="$(awk "BEGIN { printf \"%.0f\", $battery_design_uah / 1000 }") mAh"
fi
estimated_health_pct="N/A"
if [[ "$full_charge_mah" != "N/A" && "$design_capacity_mah" != "N/A" ]]; then
fc="$(printf '%s' "$full_charge_mah" | awk '{print $1}')"
dc="$(printf '%s' "$design_capacity_mah" | awk '{print $1}')"
if [[ "$fc" =~ ^[0-9]+$ && "$dc" =~ ^[0-9]+$ && "$dc" -gt 0 ]]; then
estimated_health_pct="$(awk "BEGIN { printf \"%.1f\", ($fc / $dc) * 100 }") %"
fi
fi
render_bar() {
local pct="${1:-0}"
local width=28
local filled=0
if [[ "$pct" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
filled="$(awk "BEGIN { printf \"%d\", ($pct / 100) * $width }")"
fi
local empty=$((width - filled))
[[ $empty -lt 0 ]] && empty=0
[[ $filled -lt 0 ]] && filled=0
printf "["
printf "%0.s#" $(seq 1 "$filled" 2>/dev/null || true)
printf "%0.s-" $(seq 1 "$empty" 2>/dev/null || true)
printf "]"
}
level_num="${level:-0}"
if ! [[ "$level_num" =~ ^[0-9]+$ ]]; then
level_num=0
fi
echo
echo "┌──────────────────────────────────────────────┐"
printf "│ %-44s │\n" "Android Battery Check"
echo "├──────────────────────────────────────────────┤"
printf "│ Device %-22s │\n" "$manufacturer $model"
printf "│ Codename %-22s │\n" "$device"
printf "│ Android %-22s │\n" "$android_version"
printf "│ Build %-22.22s │\n" "$build_id"
echo "├──────────────────────────────────────────────┤"
printf "│ Battery Charge %3s %% │\n" "${level:-N/A}"
printf "│ %-44s │\n" "$(render_bar "$level_num")"
printf "│ Status %-22s │\n" "$status"
printf "│ Health %-22s │\n" "$health"
printf "│ Present %-22s │\n" "${present:-N/A}"
printf "│ Charger %-22s │\n" "$plugged"
printf "│ Temperature %-22s │\n" "$temp_c"
printf "│ Voltage %-22s │\n" "$voltage_v"
printf "│ Technology %-22s │\n" "${technology:-N/A}"
printf "│ Charge Counter %-22s │\n" "$charge_counter_mah"
printf "│ Cycle Count %-22s │\n" "${battery_cycle_count:-N/A}"
printf "│ Full Charge Cap. %-22s │\n" "$full_charge_mah"
printf "│ Design Capacity %-22s │\n" "$design_capacity_mah"
printf "│ Est. Battery Health %-22s │\n" "$estimated_health_pct"
echo "├──────────────────────────────────────────────┤"
printf "│ Checked at %-22s │\n" "$(date '+%Y-%m-%d %H:%M:%S')"
echo "└──────────────────────────────────────────────┘"
echo
echo "Raw sources:"
echo " adb shell dumpsys battery"
if [[ -n "$battery_props" ]]; then
echo " adb shell dumpsys batteryproperties"
fi
Generates a detailed performance report saved to ~/Desktop/android_speed_audit_<timestamp>.txt. Covers device info, storage, the full package list, system memory, a CPU snapshot, battery stats, and recent crash/ANR hints from logcat. Takes 30–60 seconds to run.
Run it:
~/scripts/android_speed_audit.sh
Full script:
#!/usr/bin/env bash
set -Eeuo pipefail
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "Missing required command: $1" >&2
exit 1
}
}
require_cmd adb
require_cmd awk
require_cmd grep
require_cmd sed
require_cmd sort
require_cmd head
require_cmd tail
require_cmd tr
require_cmd date
adb start-server >/dev/null
DEVICE_STATE="$(adb get-state 2>/dev/null || true)"
if [[ "$DEVICE_STATE" != "device" ]]; then
echo "No authorized Android device detected."
echo "Check USB debugging, cable, and authorization prompt."
exit 1
fi
TS="$(date +"%Y-%m-%d_%H-%M-%S")"
REPORT="$HOME/Desktop/android_speed_audit_$TS.txt"
run_adb() {
adb shell "$@" 2>/dev/null | tr -d '\r'
}
section() {
{
echo
echo "=================================================================="
echo "$1"
echo "=================================================================="
} >> "$REPORT"
}
append_block() {
local title="$1"
shift
{
echo
echo "--- $title ---"
"$@" || true
} >> "$REPORT"
}
collect_device_info() {
run_adb '
echo "Manufacturer: $(getprop ro.product.manufacturer)"
echo "Model: $(getprop ro.product.model)"
echo "Device: $(getprop ro.product.device)"
echo "Android: $(getprop ro.build.version.release)"
echo "Build: $(getprop ro.build.display.id)"
echo "ABI: $(getprop ro.product.cpu.abi)"
'
}
collect_storage() {
run_adb '
echo "[df]"
df
echo
echo "[shared storage]"
du -sh /sdcard/* 2>/dev/null | sort -nr
'
}
collect_packages() {
run_adb '
echo "[all packages]"
pm list packages
echo
echo "[third-party packages]"
pm list packages -3
echo
echo "[package apk paths]"
pm list packages -f
'
}
collect_meminfo_summary() {
run_adb '
echo "[system meminfo]"
dumpsys meminfo
'
}
collect_cpu_snapshot() {
run_adb '
echo "[top snapshot]"
top -n 1 -m 40
'
}
collect_battery() {
run_adb '
echo "[battery]"
dumpsys battery
echo
echo "[batterystats]"
dumpsys batterystats | head -n 250
'
}
collect_package_candidates() {
run_adb '
pm list packages -3 | sed "s/^package://"
' | grep -Eiv 'fdroid|f-droid|camera|dialer|contacts|messaging|launcher|keyboard|inputmethod' | head -n 25
}
collect_package_details() {
while IFS= read -r pkg; do
[[ -n "$pkg" ]] || continue
{
echo
echo "### PACKAGE: $pkg ###"
echo
echo "[dumpsys package]"
run_adb "dumpsys package $pkg | head -n 220"
echo
echo "[dumpsys meminfo]"
run_adb "dumpsys meminfo $pkg"
} >> "$REPORT"
done
}
collect_log_hints() {
run_adb '
echo "[recent low-memory / crash hints]"
logcat -d -b main -b system -b crash -v brief 2>/dev/null | \
grep -Ei "low memory|anr|fatal exception|watchdog|am_anr|am_crash|lmk|lowmemorykiller" | \
tail -n 200
'
}
{
echo "Android Speed Audit Report"
echo "Generated: $(date)"
echo
} > "$REPORT"
section "DEVICE INFO"
append_block "Device properties" collect_device_info
section "STORAGE"
append_block "Filesystem and shared storage" collect_storage
section "PACKAGES"
append_block "Installed packages" collect_packages
section "MEMORY"
append_block "System memory" collect_meminfo_summary
section "CPU / PROCESS SNAPSHOT"
append_block "CPU snapshot" collect_cpu_snapshot
section "BATTERY / POWER"
append_block "Battery and batterystats" collect_battery
section "RECENT SYSTEM HEALTH HINTS"
append_block "Logcat hints" collect_log_hints
section "PACKAGE DEEP DIVE"
collect_package_candidates | collect_package_details
section "END"
echo "Report saved to: $REPORT" >> "$REPORT"
echo "Report written to:"
echo "$REPORT"
Generates a storage breakdown saved to ~/Desktop/android_storage_report_<timestamp>.txt. Detects internal storage and SD card separately, lists the largest folders and files down to 4 levels deep, and breaks down common media directories (DCIM, Downloads, WhatsApp, Telegram, Signal, etc.).
This script can take several minutes on a full device — it uses find over ADB to walk the filesystem, which is inherently slow. Let it run; the report is written incrementally.
Run it:
~/scripts/android_storage_diagnose.sh
Full script:
#!/usr/bin/env bash
set -Eeuo pipefail
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "Missing required command: $1" >&2
exit 1
}
}
require_cmd adb
require_cmd awk
require_cmd sed
require_cmd grep
require_cmd sort
require_cmd head
require_cmd tr
require_cmd date
adb start-server >/dev/null
DEVICE_STATE="$(adb get-state 2>/dev/null || true)"
if [[ "$DEVICE_STATE" != "device" ]]; then
echo "No authorized Android device detected."
echo "Check:"
echo " - USB cable"
echo " - phone unlocked"
echo " - USB debugging enabled"
echo " - authorization prompt accepted"
exit 1
fi
TS="$(date +"%Y-%m-%d_%H-%M-%S")"
REPORT="$HOME/Desktop/android_storage_report_$TS.txt"
run_adb_shell() {
adb shell "$@" 2>/dev/null | tr -d '\r'
}
section() {
{
echo
echo "=================================================================="
echo "$1"
echo "=================================================================="
} >> "$REPORT"
}
append_cmd_output() {
local title="$1"
shift
{
echo
echo "--- $title ---"
"$@" || true
} >> "$REPORT"
}
detect_storage_roots() {
run_adb_shell '
for p in \
/sdcard \
/storage/emulated/0 \
/storage/self/primary
do
if [ -d "$p" ]; then
echo "INTERNAL:$p"
fi
done
for p in /storage/*; do
case "$p" in
/storage/emulated|/storage/self) continue ;;
esac
if [ -d "$p" ]; then
if [ -d "$p/Android" ] || [ -d "$p/DCIM" ] || [ -d "$p/Download" ]; then
echo "EXTERNAL:$p"
fi
fi
done
' | awk '!seen[$0]++'
}
summarize_top_level_dirs() {
local root="$1"
run_adb_shell "
if [ -d \"$root\" ]; then
for d in \"$root\"/*; do
[ -e \"\$d\" ] || continue
du -sk \"\$d\" 2>/dev/null
done | sort -nr | head -n 50
fi
" | awk '
function human(kb) {
if (kb >= 1073741824) return sprintf("%.2f TB", kb/1073741824);
if (kb >= 1048576) return sprintf("%.2f GB", kb/1048576);
if (kb >= 1024) return sprintf("%.2f MB", kb/1024);
return sprintf("%d KB", kb);
}
{
size=$1; $1="";
sub(/^ +/, "", $0);
printf "%10s %s\n", human(size), $0
}
'
}
biggest_dirs_recursive() {
local root="$1"
local maxdepth="${2:-4}"
run_adb_shell "
if [ -d \"$root\" ]; then
find \"$root\" -mindepth 1 -maxdepth $maxdepth -type d 2>/dev/null | while read -r d; do
du -sk \"\$d\" 2>/dev/null
done | sort -nr | head -n 100
fi
" | awk '
function human(kb) {
if (kb >= 1073741824) return sprintf("%.2f TB", kb/1073741824);
if (kb >= 1048576) return sprintf("%.2f GB", kb/1048576);
if (kb >= 1024) return sprintf("%.2f MB", kb/1024);
return sprintf("%d KB", kb);
}
{
size=$1; $1="";
sub(/^ +/, "", $0);
printf "%10s %s\n", human(size), $0
}
'
}
biggest_files_recursive() {
local root="$1"
local limit="${2:-150}"
run_adb_shell "
if [ -d \"$root\" ]; then
find \"$root\" -type f 2>/dev/null | while read -r f; do
sz=\$(stat -c %s \"\$f\" 2>/dev/null || toybox stat -c %s \"\$f\" 2>/dev/null || echo '')
[ -n \"\$sz\" ] && printf '%s\t%s\n' \"\$sz\" \"\$f\"
done | sort -nr | head -n $limit
fi
" | awk -F '\t' '
function human(bytes) {
if (bytes >= 1099511627776) return sprintf("%.2f TB", bytes/1099511627776);
if (bytes >= 1073741824) return sprintf("%.2f GB", bytes/1073741824);
if (bytes >= 1048576) return sprintf("%.2f MB", bytes/1048576);
if (bytes >= 1024) return sprintf("%.2f KB", bytes/1024);
return sprintf("%d B", bytes);
}
NF >= 2 {
printf "%10s %s\n", human($1), $2
}
'
}
common_usage_summary() {
local root="$1"
run_adb_shell "
for p in \
\"$root/DCIM\" \
\"$root/Pictures\" \
\"$root/Movies\" \
\"$root/Download\" \
\"$root/Music\" \
\"$root/Documents\" \
\"$root/Android/media\" \
\"$root/WhatsApp\" \
\"$root/Android/media/com.whatsapp\" \
\"$root/Telegram\" \
\"$root/Android/media/org.telegram.messenger\" \
\"$root/Signal\" \
\"$root/Android/media/org.thoughtcrime.securesms\"; do
if [ -e \"\$p\" ]; then
du -sh \"\$p\" 2>/dev/null
fi
done
"
}
mount_and_df_info() {
run_adb_shell '
echo "[df]"
df 2>/dev/null
echo
echo "[mount]"
mount 2>/dev/null
'
}
device_info() {
run_adb_shell '
echo "Model: $(getprop ro.product.manufacturer) $(getprop ro.product.model)"
echo "Device: $(getprop ro.product.device)"
echo "Android: $(getprop ro.build.version.release)"
echo "Build: $(getprop ro.build.display.id)"
'
}
{
echo "Android Storage Diagnosis Report"
echo "Generated: $(date)"
echo
} > "$REPORT"
section "DEVICE INFORMATION"
append_cmd_output "ADB device info" device_info
section "FILESYSTEM / MOUNT OVERVIEW"
append_cmd_output "Filesystem and mounts" mount_and_df_info
ROOTS="$(detect_storage_roots || true)"
if [[ -z "$ROOTS" ]]; then
section "ERROR"
echo "Could not identify shared-storage roots." >> "$REPORT"
echo "Report written to: $REPORT"
exit 1
fi
section "DETECTED STORAGE ROOTS"
echo "$ROOTS" | while IFS= read -r line; do
echo "$line" >> "$REPORT"
done
echo "$ROOTS" | while IFS= read -r entry; do
[[ -n "$entry" ]] || continue
KIND="${entry%%:*}"
ROOT="${entry#*:}"
if [[ "$KIND" == "INTERNAL" ]]; then
section "INTERNAL STORAGE: $ROOT"
else
section "EXTERNAL SD / REMOVABLE STORAGE: $ROOT"
fi
append_cmd_output "Top-level folders by size" summarize_top_level_dirs "$ROOT"
append_cmd_output "Biggest common media/app areas" common_usage_summary "$ROOT"
append_cmd_output "Biggest folders recursively (depth <= 4)" biggest_dirs_recursive "$ROOT" 4
append_cmd_output "Biggest files recursively" biggest_files_recursive "$ROOT" 150
done
section "END OF REPORT"
echo "Report saved to: $REPORT" >> "$REPORT"
echo "Report written to:"
echo "$REPORT"
No authorized Android device detected — run adb devices to check the connection state. Unlock the phone and look for the authorization prompt.
Missing required command: adb — run brew install android-platform-tools.
Fields show N/A in the battery report — some manufacturers restrict what dumpsys exposes. This is normal, especially for cycle count and design capacity on non-Pixel devices.
If the device shows as unauthorized after reconnecting, go to Developer options → Revoke USB debugging authorizations on the phone, then reconnect and re-authorize.
Last updated: 19-05-2026 at 08:59