#!/bin/sh

# adb_backup.sh
# 2025-03-12
# by Gernot Walzl

# Backup and restore data of Android apps.
# ADB root debugging is required. Tested on Android 15.

set -e

CWD=$(pwd)

print_usage () {
  echo "Usage: $0 {list|backup PACKAGE|restore FILENAME}"
  echo "Example: $0 backup org.videolan.vlc"
  echo "Example: $0 restore org.videolan.vlc_backup.tar"
}

list () {
  adb shell pm list package -3 | sed 's/^package://' | sort
}

backup () {
  PKG="$1"
  adb root

  adb shell test -d "data/data/$PKG" || return 1

  mkdir -p "/tmp/$PKG/data"
  cd "/tmp/$PKG/data" || return 1
  adb shell tar -cvzf "/tmp/${PKG}_data.tar.gz" "data/data/$PKG"
  adb pull "/tmp/${PKG}_data.tar.gz" .
  adb shell rm -f "/tmp/${PKG}_data.tar.gz"

  adb shell tar -cvzf "/storage/emulated/0/${PKG}_Android.tar.gz" \
    "storage/emulated/0/Android/data/$PKG" \
    "storage/emulated/0/Android/media/$PKG" \
    "storage/emulated/0/Android/obb/$PKG" \
    || true
  adb pull "/storage/emulated/0/${PKG}_Android.tar.gz" .
  adb shell rm -f "/storage/emulated/0/${PKG}_Android.tar.gz"

  mkdir -p "/tmp/$PKG/app"
  cd "/tmp/$PKG/app" || return 1
  PKGPATHS=$(adb shell pm path "$PKG" | sed 's/^package://')
  for PKGPATH in $PKGPATHS; do
    adb pull "$PKGPATH"
    BASENAME=$(basename "$PKGPATH")
    mv "$BASENAME" "${PKG}_${BASENAME}"
  done

  cd "/tmp/$PKG"
  tar -cf "${CWD}/${PKG}_backup.tar" app data

  cd "$CWD"
  rm -rf "/tmp/$PKG"
}

restore () {
  FILENAME=$(realpath "$1")
  adb root

  PKG=$(tar -tf "$FILENAME" data \
    | grep '_data.tar.gz' \
    | sed 's#^data/\(.*\)_data.tar.gz$#\1#')
  if [ -z "$PKG" ]; then
    return 1
  fi

  mkdir -p "/tmp/$PKG"
  cd "/tmp/$PKG" || return 1
  tar -xf "$FILENAME"

  adb install app/*.apk
  USER_APP=$(adb shell stat -c '%U' "/data/data/$PKG")

  adb push "data/${PKG}_data.tar.gz" /tmp
  adb shell tar -xvf "/tmp/${PKG}_data.tar.gz"
  adb shell rm -f "/tmp/${PKG}_data.tar.gz"
  adb shell chown -R "${USER_APP}:${USER_APP}" "/data/data/$PKG"
  adb shell chown -R "${USER_APP}:${USER_APP}_cache" "/data/data/$PKG/cache"
  adb shell chown -R "${USER_APP}:${USER_APP}_cache" "/data/data/$PKG/code_cache"
  adb shell restorecon -DR "/data/data/$PKG"

  adb push "data/${PKG}_Android.tar.gz" /storage/emulated/0
  adb shell tar -xvf "/storage/emulated/0/${PKG}_Android.tar.gz"
  adb shell rm -f "/storage/emulated/0/${PKG}_Android.tar.gz"
  adb shell chown -R "$USER_APP" "/storage/emulated/0/Android/data/$PKG" \
    || true
  adb shell chown -R "$USER_APP" "/storage/emulated/0/Android/media/$PKG" \
    || true
  adb shell chown -R "$USER_APP" "/storage/emulated/0/Android/obb/$PKG" \
    || true

  cd "$CWD"
  rm -rf "/tmp/$PKG"
}

case "$1" in
  'list')
    list
    ;;
  'backup')
    backup "$2"
    ;;
  'restore')
    restore "$2"
    ;;
  *)
    print_usage
esac