Created
May 16, 2025 21:56
-
-
Save jaredrummler/6f426f1e2b0cc54d48f26c78d57d1d5d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
# β 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