Skip to content

Instantly share code, notes, and snippets.

@jaredrummler
Created May 16, 2025 21:56
Show Gist options
  • Save jaredrummler/6f426f1e2b0cc54d48f26c78d57d1d5d to your computer and use it in GitHub Desktop.
Save jaredrummler/6f426f1e2b0cc54d48f26c78d57d1d5d to your computer and use it in GitHub Desktop.
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ ADB Power Tools ┃
# ┃ ┃
# ┃ Helper script for advanced ADB commands including: ┃
# ┃ β€’ Foreground activity resolution ┃
# ┃ β€’ APK extraction and decompilation with jadx-gui ┃
# ┃ β€’ App data clearing, restart, log filtering, and more ┃
# ┃ ┃
# ┃ Author: Jared Rummler ┃
# ┃ License: Apache License 2.0 ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
# adb_foreground_activity
#
# Prints the currently focused activity in the format: packageName/activityName
# Attempts to extract from `mCurrentFocus`, with fallback to `mFocusedApp`.
# Skips SystemUI overlays (e.g., lock screen, notification shade).
#
# Usage:
# adb_foreground_activity
#
# Example:
# adb_foreground_activity
# > com.example.myapp/.MainActivity
adb_foreground_activity() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
local packageName=""
local activityName=""
local line=""
# First try mCurrentFocus
line=$(adb shell dumpsys window | grep -m 1 'mCurrentFocus')
if [[ "$line" == *"/"* ]]; then
packageName=$(echo "$line" | cut -d'/' -f1 | awk '{print $NF}')
activityName=$(echo "$line" | cut -d'/' -f2 | cut -d'}' -f1)
fi
# Fallback to mFocusedApp if invalid or NotificationShade
if [[ -z "$packageName" || -z "$activityName" || "$packageName" == "NotificationShade" ]]; then
line=$(adb shell dumpsys window | grep -m 1 'mFocusedApp')
if [[ "$line" == *"/"* ]]; then
packageName=$(echo "$line" | cut -d'/' -f1 | awk '{print $NF}')
activityName=$(echo "$line" | cut -d'/' -f2 | cut -d'}' -f1 | cut -d' ' -f1)
fi
fi
# Filter out system overlays
if [[ "$packageName" =~ ^(com\.android\.systemui|NotificationShade)$ ]]; then
echo "⚠️ No foreground app (SystemUI or screen off)" >&2
return 1
fi
# Final validation and output
if [[ -n "$packageName" && -n "$activityName" ]]; then
echo "$packageName/$activityName"
return 0
else
echo "❌ Could not determine foreground activity" >&2
return 1
fi
}
# adb_foreground_package
#
# Prints the package name of the currently focused activity.
# Relies on `adb_foreground_activity`, and extracts the package component.
#
# Usage:
# adb_foreground_package
#
# Example:
# adb_foreground_package
# > com.example.myapp
adb_foreground_package() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
local activity output
output=$(adb_foreground_activity)
local exit_code=$?
if [[ $exit_code -eq 0 && "$output" == *"/"* ]]; then
activity="$output"
echo "$activity" | cut -d'/' -f1
return 0
else
echo "❌ Could not determine foreground package name" >&2
return 1
fi
}
# adb_get_apk_path
#
# Retrieves the APK file path on the device for a given package name.
# Supports the keyword "foreground" to detect the currently focused app.
#
# Usage:
# adb_get_apk_path [PACKAGE_NAME|foreground]
#
# Example:
# adb_get_apk_path com.example.myapp
# adb_get_apk_path foreground
adb_get_apk_path() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
if [[ $# -ne 1 ]]; then
echo "Usage: adb_get_apk_path [PACKAGE_NAME|foreground]" >&2
return 1
fi
local packageName=$1
if [[ "$packageName" == "foreground" ]]; then
packageName=$(adb_foreground_package) || return 1
fi
local packageLine apkFile
packageLine=$(adb shell pm list packages -f | grep -m 1 -F "$packageName")
if [[ -z "$packageLine" ]]; then
echo "❌ Package not found: $packageName" >&2
return 1
fi
apkFile=$(echo "$packageLine" | cut -d: -f2 | sed 's|.apk=.*|.apk|')
if [[ -z "$apkFile" ]]; then
echo "❌ Could not extract APK file path for $packageName" >&2
return 1
fi
echo "$apkFile"
}
# adb_pull_apk
#
# Pulls the APK file of a given package from the device to the current directory.
# Supports "foreground" to pull the currently focused app.
#
# Usage:
# adb_pull_apk [PACKAGE_NAME|foreground]
#
# Example:
# adb_pull_apk com.example.app
# adb_pull_apk foreground
adb_pull_apk() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
if [[ $# -ne 1 ]]; then
echo "Usage: adb_pull_apk [PACKAGE_NAME|foreground]" >&2
return 1
fi
local packageName=$1
if [[ "$packageName" == "foreground" ]]; then
packageName=$(adb_foreground_package) || return 1
fi
local apkPath
apkPath=$(adb_get_apk_path "$packageName") || return 1
echo "APK: $apkPath"
local outputName="./${packageName}.apk"
echo "πŸ“¦ Pulling APK for $packageName β†’ $outputName"
adb pull "$apkPath" "$outputName" 2>/dev/null
if [[ $? -ne 0 ]]; then
echo "⚠️ Direct pull failed. Trying fallback via /sdcard/"
local tmpApk="/sdcard/${packageName}.apk"
adb shell cp "$apkPath" "$tmpApk"
adb pull "$tmpApk" "$outputName"
adb shell rm "$tmpApk"
fi
if [[ -f "$outputName" ]]; then
echo "βœ… APK saved to: $outputName"
return 0
else
echo "❌ Failed to pull APK for $packageName" >&2
return 1
fi
}
# adb_clear_app_data
#
# Clears all application data (similar to uninstall/reinstall without APK removal).
# Supports "foreground" to clear the currently focused app.
#
# Usage:
# adb_clear_app_data [PACKAGE_NAME|foreground]
#
# Example:
# adb_clear_app_data com.example.app
# adb_clear_app_data foreground
adb_clear_app_data() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
if [[ $# -ne 1 ]]; then
echo "Usage: adb_clear_app_data [PACKAGE_NAME|foreground]" >&2
return 1
fi
local pkg="$1"
if [[ "$pkg" == "foreground" ]]; then
pkg=$(adb_foreground_package) || return 1
fi
echo "🧹 Clearing app data for: $pkg"
local output
output=$(adb shell pm clear "$pkg" 2>&1)
if [[ "$output" == *"Success"* ]]; then
echo "βœ… App data cleared for $pkg"
return 0
else
echo "❌ Failed to clear app data for $pkg"
echo "$output" >&2
return 1
fi
}
# adb_restart_app
#
# Force-stops and restarts an Android application by launching its main activity.
# Accepts either a package name or "foreground" to restart the currently focused app.
#
# Usage:
# adb_restart_app [PACKAGE_NAME|foreground]
#
# Example:
# adb_restart_app com.example.app
# adb_restart_app foreground
adb_restart_app() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
if [[ $# -ne 1 ]]; then
echo "Usage: adb_restart_app [PACKAGE_NAME|foreground]" >&2
return 1
fi
local pkg="$1"
if [[ "$pkg" == "foreground" ]]; then
pkg=$(adb_foreground_package) || return 1
fi
echo "πŸ” Restarting app: $pkg"
adb shell am force-stop "$pkg"
# Start main launcher activity using monkey
adb shell monkey -p "$pkg" -c android.intent.category.LAUNCHER 1 \
>/dev/null 2>&1
if [[ $? -eq 0 ]]; then
echo "βœ… App restarted: $pkg"
return 0
else
echo "❌ Failed to restart app: $pkg" >&2
return 1
fi
}
# adb_logcat
#
# Filters logcat output by the PID of a given package.
# Optionally specify a log level: V, D, I, W, E, or *
#
# Usage:
# adb_logcat [PACKAGE_NAME|foreground] [LEVEL]
#
# Example:
# adb_logcat com.example.myapp D
# adb_logcat foreground I
adb_logcat() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
if [[ $# -lt 1 || $# -gt 2 ]]; then
echo "Usage: adb_logcat [PACKAGE_NAME|foreground] [LEVEL]" >&2
return 1
fi
local pkg="$1"
local level="${2:-*}"
if [[ "$pkg" == "foreground" ]]; then
pkg=$(adb_foreground_package) || return 1
fi
local pid
pid=$(adb shell pidof "$pkg" 2>/dev/null | tr -d '\r')
if [[ -z "$pid" ]]; then
echo "❌ Could not find PID for: $pkg" >&2
return 1
fi
echo "πŸ“„ Streaming logcat for $pkg (PID: $pid) with level: $level"
echo "πŸ”Ž Press Ctrl+C to stop."
adb logcat --pid="$pid" "*:$level"
}
# adb_screencap
#
# Takes a screenshot of the device screen and saves it locally.
# If no filename is provided, it defaults to a timestamped PNG.
#
# Usage:
# adb_screencap [FILENAME]
#
# Example:
# adb_screencap
# adb_screencap screenshot.png
# adb_screencap my_capture # will be saved as my_capture.png
adb_screencap() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
local filename="$1"
[[ -z "$filename" ]] && filename=$(date "+%Y.%m.%d-%H.%M.%S")
if [[ ! "$filename" =~ \.(png|jpg|jpeg)$ ]]; then
filename="${filename}.png"
fi
local remote="/sdcard/screencap-temp.png"
echo "πŸ“Έ Capturing screenshot..."
adb shell screencap -p "$remote"
echo "⬇️ Pulling screenshot to ./$filename"
adb pull "$remote" "./$filename" >/dev/null
echo "🧹 Cleaning up device copy"
adb shell rm "$remote" >/dev/null
if [[ -f "$filename" ]]; then
echo "βœ… Screenshot saved: $filename"
return 0
else
echo "❌ Failed to save screenshot" >&2
return 1
fi
}
# adb_enable_talkback
#
# Enables the TalkBack accessibility service on the connected Android device.
# This uses the official TalkBack service component.
#
# Usage:
# adb_enable_talkback
adb_enable_talkback() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
echo "πŸ”Š Enabling TalkBack..."
adb shell settings put secure enabled_accessibility_services \
"com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService"
adb shell settings put secure accessibility_enabled 1
echo "βœ… TalkBack enabled."
}
# adb_disable_talkback
#
# Disables all enabled accessibility services, including TalkBack.
#
# Usage:
# adb_disable_talkback
adb_disable_talkback() {
if ! adb get-state 1>/dev/null 2>&1; then
echo "❌ Device not connected or adb unauthorized" >&2
return 1
fi
echo "πŸ”‡ Disabling all accessibility services..."
adb shell settings put secure enabled_accessibility_services null
adb shell settings put secure accessibility_enabled 0
echo "πŸ›‘ TalkBack disabled."
}
##
# adb_decompile_apk
#
# Pulls an APK from a connected Android device and opens it in `jadx-gui` for decompilation.
#
# @param $1 The package name or "foreground" to use the currently focused app. Required.
# @param $2 The output directory for saving the APK. Optional. Defaults to a temp directory.
#
# @return 0 on success, 1 on failure
#
# Requires:
# - adb
# - jadx-gui in $PATH
# - `get_foreground_activity` function available in the shell
#
# Example usage:
# adb_decompile_apk foreground
# adb_decompile_apk com.example.myapp ./output
##
adb_decompile_apk() {
# Check for required tools
if ! command -v adb &>/dev/null; then
echo "❌ Error: adb is not installed or not in your PATH."
echo "πŸ‘‰ Install Android platform tools: https://developer.android.com/tools"
return 1
fi
if ! command -v jadx-gui &>/dev/null; then
echo "❌ Error: jadx-gui is not installed or not in your PATH."
echo "πŸ‘‰ Install it via:"
echo " brew install jadx # macOS (Homebrew)"
echo " sudo apt install jadx # Ubuntu/Debian"
echo " Or download from: https://github.com/skylot/jadx"
return 1
fi
local packageName="$1"
local outputDir="$2"
if [[ -z "$packageName" ]]; then
echo "❌ Error: Package name (or 'foreground') is required."
echo "Usage: adb_decompile_apk <package.name|foreground> [output_dir]"
return 1
fi
if [[ "$packageName" == "foreground" ]]; then
packageName=$(adb_foreground_package) || return 1
fi
# Find APK path on device
local apkPath
apkPath=$(adb_get_apk_path $packageName)
if [[ -z "$apkPath" ]]; then
echo "❌ Could not find APK path for package: $packageName"
return 1
fi
# Set output directory
local outputBase="${outputDir:-$(mktemp -d)}"
mkdir -p "$outputBase"
local apkFile="$outputBase/${packageName}.apk"
echo "πŸ“¦ Pulling APK from $apkPath"
adb pull "$apkPath" "$apkFile" >/dev/null
if [[ ! -f "$apkFile" ]]; then
echo "❌ Failed to pull APK to $apkFile"
return 1
fi
echo "βœ… APK saved to: $apkFile"
echo "🧩 Launching jadx-gui..."
jadx-gui "$apkFile" &
}
###################################################################################################
adb() {
local cmd="$1"
shift || true
case "$cmd" in
pull)
case "$1" in
apk)
if [[ -z "$2" ]]; then
echo "Usage: adb pull apk [PACKAGE_NAME|foreground]" >&2
return 1
fi
adb_pull_apk "$2"
return $?
;;
*)
# Fall back to standard adb pull
command adb pull "$@"
return $?
;;
esac
;;
clear)
adb_clear_app_data "$1"
return $?
;;
foreground)
case "$1" in
activity|app)
adb_foreground_activity
return $?
;;
package|packagename)
adb_foreground_package
return $?
;;
*)
echo "❌ Unknown foreground subcommand: $1" >&2
return 1
;;
esac
;;
screencap|screenshot)
adb_screencap "$1"
return $?
;;
disable)
case "$1" in
talkback)
adb_disable_talkback
return $?
;;
*)
echo "❌ Unknown disable target: $1" >&2
return 1
;;
esac
;;
enable)
case "$1" in
talkback)
adb_enable_talkback
return $?
;;
*)
echo "❌ Unknown enable target: $1" >&2
return 1
;;
esac
;;
logcat)
adb_logcat "$@"
return $?
;;
restart)
adb_restart_app "$1"
return $?
;;
decompile)
adb_decompile_apk "$@"
return $?
;;
get-apk-path)
adb_get_apk_path "$1"
return $?
;;
*)
# Fall back to original adb command
command adb "$cmd" "$@"
;;
esac
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment