Index

Android ADB diagnostic scripts

Three shell scripts to audit battery health, performance, and storage on an Android device over USB from your Mac.

Danger

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.

The scripts

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

Prerequisites

1. Install ADB on your Mac

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

2. Enable USB debugging on your Android phone

  1. Open Settings → About phone
  2. Tap Build number 7 times until you see "You are now a developer"
  3. Go back to Settings → System → Developer options (location varies by manufacturer)
  4. Enable USB debugging
Note

On some devices — Samsung, Xiaomi, OnePlus — Developer options may be under Settings → General management or Settings → Additional settings.

3. Connect and authorize the device

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
Warning

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.

4. Place the scripts

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

Script 1 — Check battery health

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.

Note

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

Script 2 — Speed audit

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"

Script 3 — Storage diagnose

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.).

Warning

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"

Troubleshooting

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.

Tip

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