deployidroid/deployidroid

5160 lines
163 KiB
Bash
Executable File

#!/usr/bin/env bash
set -e
###* Global variables
app_version="0.9.5"
profile_version="0.9.5"
app_file="${0}"
app_name="$(basename $0)"
app_config="${HOME}/.config/${app_name}/config.sh"
###* Generated Include Code
#### BEGIN_INCLUDE
###** Include utils.sh
declare -A utils_depends=(
[realpath]=''
[wc]=''
[awk]=''
[mktemp]=''
)
function msg {
local mesg="$1"; shift
printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "${@}" >&2
}
function msg2 {
local mesg="$1"; shift
printf "${BLUE} ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "${@}" >&2
}
function warning {
if [ -z "${IGNORE_WARN}" ]; then
local mesg="$1"; shift
printf "${YELLOW}==> WARNING:${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "${@}" >&2
fi
}
function error {
local mesg="$1"; shift
printf "${RED}==> ERROR:${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "${@}" >&2
}
function abort {
error "${@}"
error "Aborting..."
exit 1
}
function enable_colors {
unset ALL_OFF BOLD BLUE GREEN RED YELLOW
if [[ -t 2 && $USE_COLOR != "n" ]]; then
# prefer terminal safe colored and bold text when tput is supported
if tput setaf 0 &>/dev/null; then
ALL_OFF="$(tput sgr0)"
BOLD="$(tput bold)"
BLUE="${BOLD}$(tput setaf 4)"
GREEN="${BOLD}$(tput setaf 2)"
RED="${BOLD}$(tput setaf 1)"
YELLOW="${BOLD}$(tput setaf 3)"
else
ALL_OFF="\e[0m"
BOLD="\e[1m"
BLUE="${BOLD}\e[34m"
GREEN="${BOLD}\e[32m"
RED="${BOLD}\e[31m"
YELLOW="${BOLD}\e[33m"
fi
fi
# readonly ALL_OFF BOLD BLUE GREEN RED YELLOW
}
function is_cmd_exists {
if type -a "${1}" &>/dev/null; then
return 0
else
return 1
fi
}
function is_func_exists {
local t=$(type -t "${1}")
if [ x"${t}" = x'function' ]; then
return 0
else
return 1
fi
}
function is_var_exists {
local vars=($(eval "echo \${!$1@}"))
local v=
for v in "${vars[@]}"; do
if [ x"${v}" = x"${1}" ]; then
return 0
fi
done
return 1
}
function is_abs_path {
if [[ "${1}" =~ ^~?/ ]]; then
return 0
else
return 1
fi
}
# arg1: relavive-path, arg2(optional): base-dir
function get_realpath {
local rel_path="${1}"
if is_abs_path "${rel_path}"; then
echo "${rel_path}"
return
fi
local base_dir="${2:-.}"
local base_abs_dir=$(realpath -s "${base_dir}")
local abs_path=
if [ -e "${base_abs_dir}/${rel_path}" ]; then
abs_path=$(realpath -s "${base_abs_dir}/${rel_path}")
else
abs_path="${base_abs_dir}/${rel_path}"
fi
echo "${abs_path}"
}
# arg1: file-or-dir
function is_empty_dir {
local dir="${1}"
if [ -f "${dir}" ]; then
return 1
fi
local count=$(find "${dir}" -type f | wc | awk '{print $1}')
if [ "${count}" -eq 0 ]; then
return 0
else
return 1
fi
}
function read_password {
stty -echo
read password
stty echo
echo "${password}"
}
# test array contains item
# arg1: item to match, argN: array
function contains {
local e match="${1}"
shift
for e; do [[ "${e}" == "$match" ]] && return 0; done
return 1
}
# compare two string versions(like 0.9.5), if two version equals, return 0, if version 1 greater, return 1, if version 2 greater, return 2
# arg1: version string 1
# arg2: version string 2
function compver {
local IFS=.
local i ver1=(${1}) ver2=(${2})
for ((i=0; i<"${#ver1[@]}"; i++)); do
if [ -z "${ver2[i]}" ]; then
return 1
fi
if [ "${ver1[i]}" -gt "${ver2[i]}" ]; then
return 1
fi
if [ "${ver1[i]}" -lt "${ver2[i]}" ]; then
return 2
fi
done
if [ -n "${ver2[i]}" ]; then
return 2
fi
return 0
}
function compver_eq {
case $(compver "${1}" "${2}"; echo "${?}") in
0) return 0;;
esac
return 1
}
function compver_gt {
case $(compver "${1}" "${2}"; echo "${?}") in
1) return 0;;
esac
return 1
}
function compver_ge {
case $(compver "${1}" "${2}"; echo "${?}") in
0|1) return 0;;
esac
return 1
}
function compver_lt {
case $(compver "${1}" "${2}"; echo "${?}") in
2) return 0;;
esac
return 1
}
function compver_le {
case $(compver "${1}" "${2}"; echo "${?}") in
0|2) return 0;;
esac
return 1
}
# dump_section_headless <file> <begin search string> <end search string>
function dump_section_headless {
local file="${1}"
local begin="${2}"
local end="${3}"
awk -v begin="${begin}" -v end="${end}" 'BEGIN{in_section=0} $0 ~ end{if (in_section==1) exit} {if (in_section==1) print} $0 ~ begin{in_section=1}' "${file}"
}
# insert_section <file> <begin search string> <end search string> <content string>
function insert_section {
if is_section_exists "$1" "$2" "$3"; then
replace_section "$1" "$2" "$3" "$2\n$4\n$3"
else
echo -e "$2\n$4\n$3" >> "$1"
fi
}
# is_section_exists <file> <begin search string> <end search string>
function is_section_exists {
local begin endstr end;
begin=$(grep -E -n "$2" $1 | head -n1 | cut -d: -f1);
if [ "$begin" ]; then
if [ "$3" == " " -o ! "$3" ]; then
endstr='^[[:space:]]*$';
else
endstr="$3";
fi;
for end in $(grep -E -n "$endstr" $1 | cut -d: -f1) $last; do
if [ "$end" ] && [ "$begin" -lt "$end" ]; then
return;
fi;
done;
fi;
return 1;
}
# replace_section <file> <begin search string> <end search string> <replacement string>
# copy and fix last line issue from anykernel3/ak3_core.sh
function replace_section {
local begin endstr last end;
begin=$(grep -E -n "$2" $1 | head -n1 | cut -d: -f1);
if [ "$begin" ]; then
if [ "$3" == " " -o ! "$3" ]; then
endstr='^[[:space:]]*$';
else
endstr="$3";
fi;
last=$(wc -l $1 | cut -d\ -f1);
for end in $(grep -E -n "$endstr" $1 | cut -d: -f1) $last; do
if [ "$end" ] && [ "$begin" -lt "$end" ]; then
sed -i "${begin},${end}d" $1;
if [ "$end" == "$last" ]; then
echo >> $1;
sed -i "${begin}s|^|${4}\n|" $1;
sed -i '$d' $1;
else
sed -i "${begin}s|^|${4}\n|" $1;
fi
break;
fi;
done;
fi;
}
###** Include adb_utils.sh
# Depends: utils.sh
declare -A adb_depends=(
[tr]=''
[basename]=''
[dirname]=''
[grep]=''
[sed]=''
[awk]=''
[sha1sum]=''
[adb]='please install android-sdk or android-platform-tools firstly'
)
adb_cmd="adb"
# Constant variables
ANDROID_VERSION_CODES_P=28
ANDROID_VERSION_CODES_O=26
ANDROID_VERSION_CODES_N=24
ANDROID_VERSION_CODES_M=23
ANDROID_VERSION_CODES_LOLLIPOP=21
ANDROID_VERSION_CODES_JELLY_BEAN_MR1=17
ANDROID_VERSION_CODES_ICE_CREAM_SANDWICH=14
ANDROID_FIRST_APPLICATION_UID=10000
ADB_DEFAULT_DEVICE_TMPDIR='/data/local/tmp'
ADB_DEFAULT_ROOT_TYPE='auto'
# Cache variables
declare -A ADB_CACHE_USER_PACKAGES
ADB_CACHE_ALL_PACKAGES=
ADB_CACHE_ALL_SYS_PACKAGES=
ADB_CACHE_ALL_3RD_PACKAGES=
ADB_CACHE_ANDROID_VERSION=
ADB_CACHE_ANDROID_API_LEVEL=
ADB_CACHE_USERS=
ADB_CACHE_ANDROID_ABI_LIST=
# Optional variables, could override in host script or config file
adb_opt_verbose=
adb_opt_root_type="${ADB_DEFAULT_ROOT_TYPE}"
adb_opt_device_tmpdir="${ADB_DEFAULT_DEVICE_TMPDIR}"
adb_opt_allow_downgrade=
adb_opt_keep_data=
adb_opt_busybox=
adb_opt_serial=
###*** Adb functions
function _adb_strip_output {
tr -d '\r'
}
# arg1: user-id
function _adb_build_user_arg {
local user="${1}"
if [ -z "${user}" ] || ! adb_is-pm-support-multiuser; then
echo ''
else
echo "--user '${user}'"
fi
}
function _adb_ensure_tmpdir_exists {
adb_autoshell "${adb_opt_busybox} mkdir -p '${adb_opt_device_tmpdir}'"
adb_autoshell "${adb_opt_busybox} chmod 777 '${adb_opt_device_tmpdir}'"
}
# run adb shell in root permission if possible, or will fallback to normal shell
# opts: adb_opt_root_type
function _adb_usage_autoshell {
echo "autoshell <command>"
}
function adb_autoshell {
local cmdline="${*}"
if adb_has-root; then
adb_rootshell "${cmdline}"
else
adb_shell "${cmdline}"
fi
}
# run adb shell and trip the result
# opts: adb_opt_verbose
function _adb_usage_shell {
echo "shell <command>"
}
function adb_shell {
local cmdline="${*}"
if [ -z "${adb_opt_verbose}" -o 0"${adb_opt_verbose}" -lt 2 ]; then
if ! [[ "${cmdline}" =~ ">/dev/null"$ ]]; then
cmdline="${cmdline} 2>/dev/null"
fi
fi
if [ -n "${adb_opt_verbose}" ]; then
msg2 "adb shell %s" "${cmdline}"
fi
${adb_cmd} shell "${cmdline}" | _adb_strip_output
}
function _adb_usage_rootshell {
echo "rootshell <command>"
}
function adb_rootshell {
local cmdline="${*}"
if [ -z "${adb_opt_verbose}" -o 0"${adb_opt_verbose}" -lt 2 ]; then
if ! [[ "${cmdline}" =~ ">/dev/null"$ ]]; then
cmdline="${cmdline} 2>/dev/null"
fi
fi
if adb_has-root; then
if [ x"${adb_opt_root_type}" = x"su" ]; then
if [ -n "${adb_opt_verbose}" ]; then
msg2 "adb shell su -c %s" "${cmdline}"
fi
${adb_cmd} shell su -c "'${cmdline}'" | _adb_strip_output
elif [ x"${adb_opt_root_type}" = x"adb-root" ]; then
if [ -n "${adb_opt_verbose}" ]; then
msg2 "adb rootshell %s" "${cmdline}"
fi
${adb_cmd} shell "${cmdline}" | _adb_strip_output
fi
else
warning "Missing root permission: adb rootshell %s" "${cmdline}"
fi
}
# arg1: command-format(use %s to replace with user arg), arg2: [user-id] (user-id default: all users)
function _adb_shell_with_user_arg {
local cmdfmt="${1}"
local user="${2}"
if [ -z "${user}" ]; then
for u in $(_adb_list-users-plain); do
adb_shell "$(printf "${cmdfmt}" "$(_adb_build_user_arg ${u})")"
done
else
adb_shell "$(printf "${cmdfmt}" "$(_adb_build_user_arg ${user})")"
fi
}
# arg1: command-format(use %s to replace with user arg), arg2: [user-id] (user-id default: all users)
function _adb_rootshell_with_user_arg {
local cmdfmt="${1}"
local user="${2}"
if [ -z "${user}" ]; then
for u in $(_adb_list-users-plain); do
adb_rootshell "$(printf "${cmdfmt}" "$(_adb_build_user_arg ${u})")"
done
else
adb_rootshell "$(printf "${cmdfmt}" "$(_adb_build_user_arg ${user})")"
fi
}
function _adb_usage_check-online {
echo "check-online"
}
function adb_check-online {
${adb_cmd} shell "echo ''" &>/dev/null || abort "Looks adb device(${adb_opt_serial:-default}) is offline"
}
function _adb_usage_is-online {
echo "is-online"
}
function adb_is-online {
if ! is_cmd_exists "${adb_cmd}"; then
return 1
fi
if ${adb_cmd} shell "echo ''" &>/dev/null; then
return 0
else
return 1
fi
}
function _adb_usage_check-root {
echo "check-root"
}
function adb_check-root {
_fix_root_type
if [ -z "${adb_opt_root_type}" ] || [[ ! $(adb_rootshell '${adb_opt_busybox} id') =~ "root" ]]; then
abort "Looks there is no root permission in adb shell, please ensure 'adb root' works or 'su' command exists"
fi
}
function _adb_usage_has-root {
echo "has-root"
}
function adb_has-root {
_fix_root_type
if [ -z "${adb_opt_root_type}" ]; then
return 1
fi
return 0
}
function _fix_root_type {
if [ x"${adb_opt_root_type}" = x"auto" ]; then
if [[ $(${adb_cmd} shell 'id') =~ "root" ]]; then
adb_opt_root_type='adb-root'
elif [[ $(${adb_cmd} shell su -c 'id' 2>/dev/null) =~ "root" ]]; then
adb_opt_root_type='su'
else
adb_opt_root_type=
fi
elif [ x"${adb_opt_root_type}" = x"none" ]; then
adb_opt_root_type=
fi
}
function _adb_usage_is-cmd-exists {
echo "is-cmd-exists <cmd>"
}
function adb_is-cmd-exists {
local result=$(adb_shell "if type '${1}' &>/dev/null; then ${adb_opt_busybox} echo 1; fi")
if [ -z "${result}" ]; then
return 1
else
return 0
fi
}
function _adb_usage_get-kernel-version {
echo "get-kernel-version"
}
function adb_get-kernel-version {
adb_shell uname -r
}
# fill CACHE_xxx variables to speed up
function _adb_fetch_cache {
if [ -z "${ADB_CACHE_ANDROID_VERSION}" ]; then
msg "Cache adb output"
fi
adb_get-android-version 1>/dev/null
adb_get-android-api-level 1>/dev/null
adb_get-android-abi-list 1>/dev/null
_adb_fetch_users
_adb_fetch_pkgs_cache
}
function _adb_fetch_users {
ADB_CACHE_USERS=
adb_list-users 1>/dev/null
}
function _adb_fetch_pkgs_cache {
ADB_CACHE_USER_PACKAGES=()
ADB_CACHE_ALL_PACKAGES=
ADB_CACHE_ALL_SYS_PACKAGES=
ADB_CACHE_ALL_3RD_PACKAGES=
local user=
for user in $(_adb_list-users-plain); do
adb_list-3rd-packages "${user}" 1>/dev/null
done
adb_list-sys-packages 1>/dev/null
}
function _adb_usage_get-android-version {
echo "get-android-version"
}
function adb_get-android-version {
if [ -z "${ADB_CACHE_ANDROID_VERSION}" ]; then
ADB_CACHE_ANDROID_VERSION=$(adb_shell "getprop ro.build.version.release")
fi
echo "${ADB_CACHE_ANDROID_VERSION}"
}
function _adb_usage_get-android-api-level {
echo "get-android-api-level"
}
function adb_get-android-api-level {
if [ -z "${ADB_CACHE_ANDROID_API_LEVEL}" ]; then
ADB_CACHE_ANDROID_API_LEVEL=$(adb_shell "getprop ro.build.version.sdk")
if [ -z "${ADB_CACHE_ANDROID_API_LEVEL}" ]; then
# looks adb device is offline
ADB_CACHE_ANDROID_API_LEVEL=0
fi
fi
echo "${ADB_CACHE_ANDROID_API_LEVEL}"
}
function _adb_usage_get-android-abi-list {
echo "get-android-abi-list"
}
function adb_get-android-abi-list {
if [ -z "${ADB_CACHE_ANDROID_ABI_LIST}" ]; then
local cpu_abilist=$(adb_shell "getprop ro.product.cpu.abilist")
local cpu_abi=$(adb_shell "getprop ro.product.cpu.abi")
if [ -n "${cpu_abilist}" ]; then
ADB_CACHE_ANDROID_ABI_LIST="${cpu_abilist}"
else
ADB_CACHE_ANDROID_ABI_LIST="${cpu_abi}"
fi
fi
echo "${ADB_CACHE_ANDROID_ABI_LIST}" | tr ',' '\n'
}
function _adb_usage_is-support-multiuser {
echo "is-support-multiuser [apilevel]"
}
function adb_is-support-multiuser {
local apilevel="${1:-$(adb_get-android-api-level)}"
if [ "${apilevel}" -ge "${ANDROID_VERSION_CODES_ICE_CREAM_SANDWICH}" ]; then
return 0
else
return 1
fi
}
function _adb_usage_is-pm-support-multiuser {
echo "is-pm-support-multiuser [apilevel]"
}
function adb_is-pm-support-multiuser {
local apilevel="${1:-$(adb_get-android-api-level)}"
# Android support multiple user since 4.0, but the am/pm command support "--user" option after 4.2
if [ "${apilevel}" -ge "${ANDROID_VERSION_CODES_JELLY_BEAN_MR1}" ]; then
return 0
else
return 1
fi
}
function _adb_usage_list-users {
echo "list-users"
}
function adb_list-users {
if adb_is-support-multiuser; then
if [ -z "${ADB_CACHE_USERS}" ]; then
ADB_CACHE_USERS=$(adb_shell "pm list users 2>/dev/null")
fi
if [ -z "${ADB_CACHE_USERS}" ]; then
# some device need root permission to list users, just fallback here
ADB_CACHE_USERS='UserInfo{0:Primary:3}'
fi
echo "${ADB_CACHE_USERS}" | grep -o '{.*}' | tr -d '{}' | awk -F: '{print $1,$2}'
else
echo '0 <none>'
fi
}
function _adb_list-users-plain {
adb_list-users | awk '{print $1}'
}
function _adb_usage_is-only-one-user {
echo "is-only-one-user"
}
function adb_is-only-one-user {
local user_num=$(adb_list-users | wc | awk '{print $1}')
if [ "${user_num}" -eq 0 ]; then
return 0
else
return 1
fi
}
function _adb_usage_is-user-exists {
echo "is-user-exists <user-id>"
}
function adb_is-user-exists {
local user=
for user in $(_adb_list-users-plain); do
if [ x"${1}" = x"${user}" ]; then
return 0
fi
done
return 1
}
function _adb_usage_create-user {
echo "create-user <user-id> <user-name>"
}
function adb_create-user {
adb_rootshell "pm create-user --profileOf '${1}' '${2}'"
}
function _adb_usage_ensure-user-exists {
echo "ensure-user-exists <user-id> <user-name>"
}
function adb_ensure-user-exists {
local user="${1}"
local user_name="${2:-test}"
if ! adb_is-user-exists "${user}"; then
adb_create-user "${user}" "${user_name}"
fi
# update cached users
_adb_fetch_users
if ! adb_is-user-exists "${user}"; then
warning "Create user ${user} failed"
read -n1 -p "Please create user manually and press any key to continue..."
adb_ensure-user-exists "${user}" "${user_name}"
fi
}
function _adb_usage_get-current-user {
echo "get-current-user"
}
function adb_get-current-user {
local user=
if adb_is-pm-support-multiuser; then
user=$(adb_shell "dumpsys activity" | grep "mUserLru" | grep '\[.*\]' | grep -o '[0-9]\+' | tail -n 1)
fi
if [ -z "${user}" ]; then
user='0'
fi
echo "${user}"
}
function _adb_usage_get-user-name {
echo "get-user-name [user-id] (user-id default: current user)"
}
function adb_get-user-name {
local user="${1:-$(adb_get-current-user)}"
if adb_is-support-multiuser; then
adb_list-users | grep "^${user} " | awk '{print $2}'
else
echo '<none>'
fi
}
function _adb_usage_list-sys-packages {
echo "list-sys-packages"
}
function adb_list-sys-packages {
if [ -z "${ADB_CACHE_ALL_SYS_PACKAGES}" ]; then
ADB_CACHE_ALL_SYS_PACKAGES=$(adb_shell "pm list packages -f -s" | sort | uniq)
ADB_CACHE_ALL_PACKAGES=$(echo -e "${ADB_CACHE_ALL_PACKAGES}\n${ADB_CACHE_ALL_SYS_PACKAGES}" | sort | uniq)
fi
local pkg=
for pkg in $(echo "${ADB_CACHE_ALL_SYS_PACKAGES}" | awk -F: '{print $2}'); do
echo "${pkg##*=}"
done
}
function _adb_usage_list-3rd-packages {
echo "list-3rd-packages [user-id] (user-id default: current user)"
}
function adb_list-3rd-packages {
local user="${1:-$(adb_get-current-user)}"
if [ -z "${ADB_CACHE_USER_PACKAGES[${user}_3rd]}" ]; then
ADB_CACHE_USER_PACKAGES+=([${user}_3rd]=$(adb_shell "pm list packages -f -3 $(_adb_build_user_arg ${user})"))
ADB_CACHE_ALL_3RD_PACKAGES=$(echo -e "${ADB_CACHE_ALL_3RD_PACKAGES}\n${ADB_CACHE_USER_PACKAGES[${user}_3rd]}" | sort | uniq)
ADB_CACHE_ALL_PACKAGES=$(echo -e "${ADB_CACHE_ALL_PACKAGES}\n${ADB_CACHE_USER_PACKAGES[${user}_3rd]}" | sort | uniq)
fi
# for android 8.0+, the format is: package:/data/app/org.fdroid.fdroid-nzG6JC-iGBiyPjMYNpFhfw==/base.apk=org.fdroid.fdroid
# for older version, the format is: package:/data/app/org.fdroid.fdroid-1/base.apk=org.fdroid.fdroid
local pkg=
for pkg in $(echo "${ADB_CACHE_USER_PACKAGES[${user}_3rd]}" | awk -F: '{print $2}'); do
echo "${pkg##*=}"
done
}
function _adb_usage_list-packages {
echo "list-packages [user-id] (user-id default: current user)"
}
function adb_list-packages {
adb_list-3rd-packages "${1}"
adb_list-sys-packages
}
function _adb_usage_is-sys-package {
echo "is-sys-package <PKG>"
}
function adb_is-sys-package {
local pkg="${1}"
if [ -z "${ADB_CACHE_ALL_SYS_PACKAGES}" ]; then
_adb_fetch_pkgs_cache
fi
adb_list-sys-packages | grep "^${pkg}$" &>/dev/null
}
function _adb_usage_is-3rd-package {
echo "is-3rd-package <PKG> [user-id] (user-id default: all users)"
}
function adb_is-3rd-package {
local pkg="${1}"
local user="${2}"
if [ -z "${ADB_CACHE_ALL_3RD_PACKAGES}" ]; then
_adb_fetch_pkgs_cache
fi
if [ -z "${user}" ]; then
if echo "${ADB_CACHE_ALL_3RD_PACKAGES}" | grep "=${pkg}$" &>/dev/null; then
return 0
else
return 1
fi
else
adb_list-3rd-packages "${user}" | grep "^${pkg}$" &>/dev/null
fi
}
function _adb_usage_is-package-installed {
echo "is-package-installed <PKG> [user-id] (user-id default: all users)"
}
function adb_is-package-installed {
local pkg="${1}"
local user="${2}"
if [ -z "${ADB_CACHE_ALL_PACKAGES}" ]; then
_adb_fetch_pkgs_cache
fi
if [ -z "${user}" ]; then
if echo "${ADB_CACHE_ALL_PACKAGES}" | grep "=${pkg}$" &>/dev/null; then
return 0
else
return 1
fi
else
adb_is-sys-package "${pkg}" || adb_is-3rd-package "${pkg}" "${user}"
fi
}
function _adb_usage_get-package-version-code {
echo "get-package-version-code <PKG>"
}
function adb_get-package-version-code {
local pkg="${1}"
adb_shell "dumpsys package '${pkg}'" | grep versionCode | awk '{print $1}' | awk -F= '{print $2}' | head -n 1
}
function _adb_usage_get-package-version-name {
echo "get-package-version-name <PKG>"
}
function adb_get-package-version-name {
local pkg="${1}"
adb_shell "dumpsys package '${pkg}'" | grep versionName | awk -F= '{print $2}' | head -n 1
}
function _adb_usage_get-package-path {
echo "get-package-path <PKG>"
}
function adb_get-package-path {
local pkg="${1}"
local path=
if [ -n "${ADB_CACHE_ALL_PACKAGES}" ]; then
path=$(echo "${ADB_CACHE_ALL_PACKAGES}" | grep "=${pkg}$" | awk -F: '{print $2}')
path="${path%=*}"
fi
if [ -z "${path}" ]; then
path=$(adb_shell "pm path '${pkg}'" | awk -F: '{print $2}')
fi
echo "${path}"
}
function _adb_usage_get-package-display-name {
echo "get-package-display-name <PKG>"
}
function adb_get-package-display-name {
if ! adb_is-cmd-exists "aapt"; then
return
fi
local pkg="${1}"
local path=$(adb_get-package-path "${pkg}")
local display_name=
display_name=$(adb_shell "aapt dump badging '${path}'" | grep "application-label:" | sed -e "s/application-label:'//" -e "s/'\$//")
echo "${display_name}"
}
function _adb_usage_get-package-datadir {
echo "get-package-datadir <PKG> [user-id] [apilevel] (user-id default: all users)"
}
function adb_get-package-datadir {
local pkg="${1}"
local user="${2:-$(adb_get-current-user)}"
local apilevel="${3:-$(adb_get-android-api-level)}"
if adb_is-support-multiuser "${apilevel}"; then
echo "/data/user/${user}/${pkg}"
else
echo "/data/data/${pkg}"
fi
}
function _adb_usage_fix-package-datadir-permission {
echo "fix-package-datadir-permission <PKG> [user-id] (user-id default: current user)"
}
function adb_fix-package-datadir-permission {
local pkg="${1}"
local user="${2:-$(adb_get-current-user)}"
local pkguid=$(adb_get-package-uid "${pkg}" "${user}")
local device_datadir=$(adb_get-package-datadir "${pkg}" "${user}")
adb_rootshell "${adb_opt_busybox} mkdir -p '${device_datadir}'"
adb_chown-recursive "${pkguid}:${pkguid}" "${device_datadir}"
adb_rootshell "${adb_opt_busybox} chmod -R 0751 '${device_datadir}'"
adb_rootshell "${adb_opt_busybox} chmod -R 0771 '${device_datadir}'/*"
adb_fix-selinux-permission "${device_datadir}"
}
function _adb_usage_chown-recursive {
echo "chown-recursive <ownership> <path>"
}
function adb_chown-recursive {
# user 'find' instead of 'chown -R' because chown not support '-R' argument in android
adb_rootshell "${adb_opt_busybox} find '${2}' -exec chown '${1}' '{}' \;"
}
function _adb_usage_fix-selinux-permission {
echo "fix-selinux-permission <path>"
}
function adb_fix-selinux-permission {
if [ $(adb_get-android-api-level) -ge "${ANDROID_VERSION_CODES_M}" ]; then
adb_rootshell "restorecon -R '${1}'"
fi
}
function _adb_usage_get-package-uid {
echo "get-package-uid <PKG> <user-id>"
}
function adb_get-package-uid {
local pkg="${1}"
local user="${2}"
local uid=$(adb_shell "dumpsys package '${pkg}'" | grep -o 'userId=\w\+' | head -n 1 | awk -F= '{print $2}')
if [ "${user}" -eq 0 ]; then
echo "${uid}"
else
echo "${user}${uid}"
fi
}
function _adb_usage_start-package {
echo "start-package <PKG>"
}
function adb_start-package {
local pkg="${1}"
adb_shell "monkey -p '${pkg}' -c android.intent.category.LAUNCHER 1"
}
function _adb_usage_kill-package {
echo "kill-package <PKG> [user-id] (only kill background package, user-id default: all users)"
}
function adb_kill-package {
local pkg="${1}"
local user="${2}"
adb_shell "am kill $(_adb_build_user_arg ${user}) '${pkg}'"
}
function _adb_usage_stop-package {
echo "stop-package <PKG> [user-id] (user-id default: all users)"
}
function adb_stop-package {
local pkg="${1}"
local user="${2}"
adb_shell "am force-stop $(_adb_build_user_arg ${user}) '${pkg}'"
}
function _adb_usage_clear-package {
echo "clear-package <PKG> [user-id] (user-id default: all users)"
}
function adb_clear-package {
local pkg="${1}"
local user="${2}"
_adb_shell_with_user_arg "pm clear %s '${pkg}'" "${user}"
}
# arg1: device-path, arg2: host-path, return 0 if is same file
function _adb_is-same-file {
device_hash=$(adb_shell "${adb_opt_busybox} sha1sum '${1}'" | awk '{print $1}')
host_hash=$(sha1sum "${2}" | awk '{print $1}')
if [ x"${device_hash}" = x"${host_hash}" ]; then
return 0
else
return 1
fi
}
function _adb_usage_is-file-exists {
echo "is-file-exists <device-path>"
}
function adb_is-file-exists {
local result=$(adb_autoshell "[ -e '${1}' ] && ${adb_opt_busybox} echo 1")
if [ -z "${result}" ]; then
return 1
else
return 0
fi
}
function _adb_usage_is-dir-exists {
echo "is-dir-exists <device-path>"
}
function adb_is-dir-exists {
local result=$(adb_autoshell "[ -d '${1}' ] && ${adb_opt_busybox} echo 1")
if [ -z "${result}" ]; then
return 1
else
return 0
fi
}
function _adb_usage_get-file-timestamp {
echo "get-file-timestamp <device-path>"
}
function adb_get-file-timestamp {
local device_timestamp
if adb_is-dir-exists "${1}"; then
device_timestamp=$(adb_autoshell "${adb_opt_busybox} find '${1}' -print0 -maxdepth 2 | ${adb_opt_busybox} xargs -0 ${adb_opt_busybox} stat -c '%Y' | ${adb_opt_busybox} sort -n | ${adb_opt_busybox} tail -n 1")
elif adb_is-file-exists "${1}"; then
device_timestamp=$(adb_autoshell "${adb_opt_busybox} stat -c '%Y' '${1}'")
fi
if [ -z "${device_timestamp}" ]; then
device_timestamp=0
fi
echo "${device_timestamp}"
}
function _adb_usage_set-file-timestamp {
echo "set-file-timestamp <device-path> <seconds>"
}
function adb_set-file-timestamp {
if adb_is-file-exists "${1}" && [ x"${adb_opt_busybox}" != x"toybox" ]; then
# android default toybox touch command not support modify file time, please special '--busybox' arg
local datafmt=$(date --utc "+%FT%T.%NZ" -d @${2})
adb_autoshell "${adb_opt_busybox} touch -c -m -d '${datafmt}' '${fixed_device_path}'"
fi
}
function _adb_usage_pull {
echo "pull <device-path> <host-path> [use-tmpdir]"
}
function adb_pull {
local device_path="${1}"
local host_path="${2:-.}"
local use_tmpdir="${3}"
if ! adb_is-file-exists "${device_path}"; then
msg2 "adb_pull: ${device_path} not found, ignore"
return 1
fi
local device_is_dir= host_is_dir= host_dir_exists=
if [[ "${device_path}" =~ /$ ]]; then
device_is_dir=1
fi
if [[ "${host_path}" =~ /$ ]]; then
host_is_dir=1
fi
if [ -d "${host_path}" ]; then
host_dir_exists=1
fi
# ensure host parent dir exists
host_path=$(get_realpath "${host_path}")
if [ -n "$(dirname ${host_path})" ]; then
mkdir -p "$(dirname ${host_path})"
fi
# if pull device file to host dir, and host dir not exists, ensure
# it exists at first, such as "adb_pull device/file host/dir/"
if [ -z "${device_is_dir}" -a -n "${host_is_dir}" -a -z "${host_dir_exists}" ]; then
mkdir -p "${host_path}"
fi
_fix_root_type
if [ -z "${use_tmpdir}" ] || [ x"${adb_opt_root_type}" = x"adb-root" ] || [ -z "${adb_opt_root_type}" ]; then
${adb_cmd} pull "${device_path}" "${host_path}"
else
_adb_ensure_tmpdir_exists
local tmpdir="${adb_opt_device_tmpdir}/$(basename "${device_path}")"
adb_autoshell "${adb_opt_busybox} rm -r '${tmpdir}'"
local fix_device_path="${device_path%/}" # remove tailing slash or 'toybox cp -R' not works correct
adb_autoshell "${adb_opt_busybox} cp -R -L '${fix_device_path}' '${adb_opt_device_tmpdir}'"
adb_chown-recursive "shell:shell" "${tmpdir}"
${adb_cmd} pull "${tmpdir}" "${host_path}"
adb_autoshell "${adb_opt_busybox} rm -r '${tmpdir}'"
fi
# if pull device dir to host dir, and host dir already exists, just
# align the host dir , such as "adb_pull device/dir/ host/dir/"
if [ -n "${host_dir_exists}" -a -n "${device_is_dir}" -a -n "${host_is_dir}" ]; then
local device_dirname=$(basename "${device_path}")
mv "${host_path}/${device_dirname}"/* "${host_path}"
rmdir "${host_path}/${device_dirname}"
fi
}
function _adb_usage_push {
echo "push <host-path> <device-path> [use-tmpdir]"
}
function adb_push {
local host_path="${1}"
local device_path="${2}"
local use_tmpdir="${3}"
local device_is_dir= host_is_dir= device_dir_exists=
if [[ "${device_path}" =~ /$ ]]; then
device_is_dir=1
fi
if [[ "${host_path}" =~ /$ ]]; then
host_is_dir=1
fi
if adb_is-dir-exists "${device_path}"; then
device_dir_exists=1
fi
host_path=$(get_realpath "${host_path}")
if ! [ -e "${host_path}" ]; then
msg2 "adb_push: ${host_path} not found, ignore"
return 1
fi
_fix_root_type
# ensure device parent dir exists
if [ -n $(dirname "${device_path}") ]; then
adb_autoshell "${adb_opt_busybox} mkdir -p '$(dirname ${device_path})'"
fi
# if push host file to device dir, and device dir not exists, ensure
# it exists at first, such as "adb_push host/file device/dir/"
if [ -z "${host_is_dir}" -a -n "${device_is_dir}" -a -z "${device_dir_exists}" ]; then
adb_autoshell "${adb_opt_busybox} mkdir -p '${device_path}'"
fi
if [ -z "${use_tmpdir}" ] || [ x"${adb_opt_root_type}" = x"adb-root" ] || [ -z "${adb_opt_root_type}" ]; then
${adb_cmd} push "${host_path}" "${device_path}"
else
_adb_ensure_tmpdir_exists
local tmpdir="${adb_opt_device_tmpdir}/$(basename "${host_path}")"
adb_autoshell "${adb_opt_busybox} rm -r '${tmpdir}'"
${adb_cmd} push "${host_path}" "${adb_opt_device_tmpdir}"
adb_autoshell "${adb_opt_busybox} cp -R -L '${tmpdir}' '${device_path}'"
adb_autoshell "${adb_opt_busybox} rm -r '${tmpdir}'"
fi
# if push host dir to device dir, and device dir already exists, just
# align the device dir , such as "adb_push host/dir/ device/dir/"
if [ -n "${device_dir_exists}" -a -n "${device_is_dir}" -a -n "${host_is_dir}" ]; then
local host_dirname=$(basename "${host_path}")
# TODO: not work if contains non-empty sub-dir
adb_autoshell "${adb_opt_busybox} mv '${device_path}${host_dirname}'/* '${device_path}'"
adb_autoshell "${adb_opt_busybox} rmdir '${device_path}${host_dirname}'"
fi
}
# opts: adb_opt_allow_downgrade
function _adb_usage_install {
echo "install <host-appfile> [user-id] (user-id default: all users)"
}
function adb_install {
local host_appfile=$(get_realpath "${1}")
local user="${2}"
local extra_arg=
local result=
if [ -n "${adb_opt_allow_downgrade}" ]; then
extra_arg='-d'
fi
if [ -z "${user}" ] || ! adb_is-pm-support-multiuser; then
local cmd="${adb_cmd} install -r ${extra_arg} '${host_appfile}'"
if [ -n "${adb_opt_verbose}" ]; then
msg2 "adb_install ${host_appfile}"
msg2 "adb_install cmd: ${cmd}"
fi
result=$(eval "${cmd}")
else
if [ -n "${adb_opt_verbose}" ]; then
msg2 "adb_install ${host_appfile} for user ${user}"
fi
# install for special user
_adb_ensure_tmpdir_exists
local tmpdir="${adb_opt_device_tmpdir}/$(basename "${host_appfile}")"
adb_shell "${adb_opt_busybox} mkdir -p '${adb_opt_device_tmpdir}'"
adb_shell "${adb_opt_busybox} rm -r '${tmpdir}'"
${adb_cmd} push "${host_appfile}" "${adb_opt_device_tmpdir}"
result=$(adb_shell "pm install -r ${extra_arg} --user ${user} '${tmpdir}'")
adb_shell "${adb_opt_busybox} rm -r '${tmpdir}'"
fi
if echo "${result}"| grep -q 'Success'; then
return 0
else
local err=$(echo "${result}" | grep 'Failure')
msg2 "adb_install failed: ${err}"
return 1
fi
}
# opts: adb_opt_keep_data
function _adb_usage_uninstall {
echo "uninstall <PKG> [user-id] (user-id default: all users)"
}
function adb_uninstall {
local pkg="${1}"
local user="${2}"
local extra_arg=
if [ -n "${adb_opt_keep_data}" ]; then
extra_arg='-k'
fi
if [ -z "${user}" ] || ! adb_is-pm-support-multiuser; then
if [ -n "${adb_opt_verbose}" ]; then
msg2 "adb_uninstall ${pkg}"
fi
adb_shell "pm uninstall ${extra_arg} '${pkg}'"
else
if [ -n "${adb_opt_verbose}" ]; then
msg2 "adb_uninstall ${pkg} for user ${user}"
fi
adb_shell "pm uninstall ${extra_arg} --user '${user}' '${pkg}'"
fi
}
function _adb_usage_disable {
echo "disable <PKG-OR-COMPONENT> [user-id] (user-id default: all users)"
}
function adb_disable {
local pkg_or_comp="${1}"
local user="${2}"
if [ -n "${adb_opt_verbose}" ]; then
msg2 "adb_disable ${pkg_or_comp}"
fi
_adb_rootshell_with_user_arg "pm disable %s '${pkg_or_comp}'" "${user}"
}
function _adb_usage_enable {
echo "enable <PKG-OR-COMPONENT> [user-id] (user-id default: all users)"
}
function adb_enable {
local pkg_or_comp="${1}"
local user="${2}"
if [ -n "${adb_opt_verbose}" ]; then
msg2 "adb_enable $(_adb_build_user_arg ${user}) ${pkg_or_comp}"
fi
_adb_rootshell_with_user_arg "pm enable %s '${pkg_or_comp}'" "${user}"
}
function _adb_usage_remount-rw {
echo "remount-rw <path> remount /system or /vendor as read and write"
}
function adb_remount-rw {
if [[ "${1}" == /vendor/* ]]; then
adb_rootshell "${adb_opt_busybox} mount -o remount,rw /vendor"
else
adb_remount-system-rw
fi
}
function _adb_usage_remount-ro {
echo "remount-ro <path> remount /system or /vendor as read and write"
}
function adb_remount-ro {
if [[ "${1}" == /vendor/* ]]; then
adb_rootshell "${adb_opt_busybox} mount -o remount,ro /vendor"
else
adb_remount-system-ro
fi
}
function _adb_usage_remount-system-rw {
echo "remount-system-rw remount /system as read and write"
}
function adb_remount-system-rw {
if adb_is-mountpoint "/system"; then
if ! adb_is-mountpoint-rw "/system"; then
adb_rootshell "${adb_opt_busybox} mount -o remount,rw /system"
if ! adb_is-mountpoint-rw "/system"; then
warning "Could not remount /system, please try \`adb root; adb disable-verity; adb remount\` manually and try again"
fi
fi
elif adb_is-mountpoint "/"; then
adb_rootshell "mount -o remount,rw /"
fi
# it seems unnecessary to remount /system, it's not a mount point
}
function _adb_usage_remount-system-ro {
echo "remount-system-ro remount /system as read only"
}
function adb_remount-system-ro {
if adb_is-mountpoint "/system"; then
if ! adb_is-mountpoint-ro "/system"; then
adb_rootshell "${adb_opt_busybox} mount -o remount,ro /system"
fi
elif adb_is-mountpoint "/"; then
adb_rootshell "mount -o remount,ro /"
fi
}
function _adb_usage_is-mountpoint {
echo "is-mountpoint <path>"
}
function adb_is-mountpoint {
local path="${1}"
local result=$(adb_shell "${adb_opt_busybox} mountpoint -q ${1} && ${adb_opt_busybox} echo 1")
if [ -z "${result}" ]; then
return 1
else
return 0
fi
}
function _adb_usage_is-mountpoint-rw {
echo "is-mountpoint-rw <path>"
}
function adb_is-mountpoint-rw {
local path="${1}"
local mounts=$(adb_shell "cat /proc/mounts")
echo "${mounts}" | grep -q "\s${path}\s.*\srw[[:space:],]"
}
function _adb_usage_is-mountpoint-ro {
echo "is-mountpoint-ro <path>"
}
function adb_is-mountpoint-ro {
local path="${1}"
local mounts=$(adb_shell "cat /proc/mounts")
echo "${mounts}" | grep -q "\s${path}\s.*\sro[[:space:],]"
}
function _adb_usage_install-sys {
echo "install-sys <host-appfile> [device-install-dir]"
}
function adb_install-sys {
# copy from oandbackup ShellCommands.restoreSystemApk()
local host_appfile=$(get_realpath "${1}")
local device_appdir="${2:-/system/app}"
local filename=$(basename "${host_appfile}")
local appname="${filename%.apk}"
appname="${appname%-*}"
adb_remount-rw "${device_appdir}"
# locations of apks have been changed in android 5
local apilevel=$(adb_get-android-api-level)
if [ "${apilevel}" -ge "${ANDROID_VERSION_CODES_LOLLIPOP}" ]; then
device_appdir="${device_appdir}/${appname}"
fi
adb_rootshell "${adb_opt_busybox} mkdir -p '${device_appdir}'"
adb_rootshell "${adb_opt_busybox} chmod -R 755 '${device_appdir}'"
adb_push "${host_appfile}" "${device_appdir}/${filename}" 1
adb_rootshell "${adb_opt_busybox} chmod 644 '${device_appdir}/${filename}'"
adb_remount-ro "${device_appdir}"
msg2 "maybe a reboot is necessary for system app"
}
# opts: adb_opt_keep_data
function _adb_usage_uninstall-sys {
echo "uninstall-sys <PKG>"
}
function adb_uninstall-sys {
local pkg="${1}"
local device_appfile=$(adb_get-package-path "${pkg}")
if [ -z "${device_appfile}" ]; then
warning "Could not detect package path: ${pkg}"
return
fi
if [ -z "${adb_opt_keep_data}" ]; then
adb_clear-package "${pkg}"
fi
adb_remount-rw "${device_appfile}"
adb_rootshell "${adb_opt_busybox} rm '${device_appfile}'"
# locations of apks have been changed in android 5
local apilevel=$(adb_get-android-api-level)
if [ "${apilevel}" -ge "${ANDROID_VERSION_CODES_LOLLIPOP}" ]; then
device_appfile=$(dirname "${device_appfile}")
fi
local device_app_parent=$(dirname "${device_appfile}")
if [ x"${device_app_parent}" = x"/system/app" -o x"${device_app_parent}" = x"/vendor/app" \
-o x"${device_app_parent}" = x"/system/priv-app" -o x"${device_app_parent}" = x"/vendor/priv-app" ]; then
adb_rootshell "${adb_opt_busybox} rm -r '${device_appfile}'"
fi
adb_remount-ro "${device_appfile}"
msg2 "maybe a reboot is necessary for system app"
}
function _adb_usage_make-pkg-installed-for-user {
echo "make-pkg-installed-for-user <PKG> <user-id>"
}
function adb_make-pkg-installed-for-user {
adb_shell "pm install -r -d --user '${2}' '$(adb_get-package-path "${1}")'"
}
function _adb_usage_get-default-launcher {
echo "get-default-launcher [user-id] (user-id default: all users)"
}
function adb_get-default-launcher {
_adb_shell_with_user_arg "cmd shortcut get-default-launcher %s" "${1}"
}
function _adb_usage_clear-default-launcher {
echo "clear-default-launcher [user-id] (user-id default: all users)"
}
function adb_clear-default-launcher {
_adb_shell_with_user_arg "cmd shortcut clear-default-launcher %s" "${1}"
}
function _adb_usage_set-default-launcher {
echo "set-default-launcher <activity> [user-id] (user-id default: all users)"
}
function adb_set-default-launcher {
_adb_shell_with_user_arg "pm set-home-activity %s ${1}" "${2}"
}
function _adb_usage_disable-run-background {
echo "disable-run-background <PKG> [user-id] (user-id default: all users)"
}
function adb_disable-run-background {
local apilevel=$(adb_get-android-api-level)
if [ "${apilevel}" -lt "${ANDROID_VERSION_CODES_N}" ]; then
warning "adb_diable-run-background only works since Android 7.0 (API level 24)"
return
fi
local pkg="${1}"
local user="${2}"
_adb_shell_with_user_arg "cmd appops set %s '${pkg}' RUN_IN_BACKGROUND ignore" "${user}"
if [ "${apilevel}" -ge "${ANDROID_VERSION_CODES_P}" ]; then
_adb_shell_with_user_arg "cmd appops set %s '${pkg}' RUN_ANY_IN_BACKGROUND ignore" "${user}"
fi
}
function _adb_usage_enable-run-background {
echo "enable-run-background <PKG> [user-id] (user-id default: all users)"
}
function adb_enable-run-background {
local apilevel=$(adb_get-android-api-level)
if [ "${apilevel}" -lt "${ANDROID_VERSION_CODES_N}" ]; then
warning "adb_enable-run-background only works since Android 7.0 (API level 24)"
return
fi
local pkg="${1}"
local user="${2}"
_adb_shell_with_user_arg "cmd appops set %s '${pkg}' RUN_IN_BACKGROUND allow" "${user}"
if [ "${apilevel}" -ge "${ANDROID_VERSION_CODES_P}" ]; then
_adb_shell_with_user_arg "cmd appops set %s '${pkg}' RUN_ANY_IN_BACKGROUND allow" "${user}"
fi
}
function _adb_usage_list-autostart-components {
echo "list-autostart-components [PKG]"
}
function adb_list-autostart-components {
local pkg="${1}"
if [ -z "${pkg}" ]; then
adb_shell "dumpsys package resolvers receiver" | awk '/android.intent.action.BOOT_COMPLETED/{p=1;next}/:/{p=0}p {print $2}'
else
adb_shell "dumpsys package resolvers receiver" | awk '/android.intent.action.BOOT_COMPLETED/{p=1;next}/:/{p=0}p {print $2}' | grep "^${pkg}/"
fi
}
function _adb_usage_disable-autostart {
echo "disable-autostart <PKG> [user-id] (user-id default: all users)"
}
function adb_disable-autostart {
local pkg="${1}"
local user="${2}"
local comp=
for comp in $(adb_list-autostart-components "${pkg}"); do
adb_disable "${comp}" "${user}"
done
}
function _adb_usage_enable-autostart {
echo "enable-autostart <PKG> [user-id] (user-id default: all users)"
}
function adb_enable-autostart {
local pkg="${1}"
local user="${2}"
local comp=
for comp in $(adb_list-autostart-components "${pkg}"); do
adb_enable "${comp}" "${user}"
done
}
function _adb_usage_grant-permission {
echo "grant-permission <PKG> <permission> [user-id] (user-id default: all users)"
}
function adb_grant-permission {
local pkg="${1}"
local perm="${2}"
local user="${3}"
# add permission prefix
if ! [[ "${perm}" =~ \. ]]; then
perm="android.permission.${perm}"
fi
_adb_shell_with_user_arg "pm grant %s '${pkg}' ${perm}" "${user}"
}
function _adb_usage_revoke-permission {
echo "revoke-permission <PKG> <permission> [user-id] (user-id default: all users)"
}
function adb_revoke-permission {
local pkg="${1}"
local perm="${2}"
local user="${3}"
# add permission prefix
if ! [[ "${perm}" =~ \. ]]; then
perm="android.permission.${perm}"
fi
_adb_shell_with_user_arg "pm revoke %s '${pkg}' ${perm}" "${user}"
}
function _adb_usage_grant-storage-permission {
echo "grant-storage-permission <PKG> [user-id] (user-id default: all users)"
}
function adb_grant-storage-permission {
adb_grant-permission "${1}" "android.permission.READ_EXTERNAL_STORAGE" "${2}"
adb_grant-permission "${1}" "android.permission.WRITE_EXTERNAL_STORAGE" "${2}"
}
function _adb_usage_revoke-storage-permission {
echo "revoke-storage-permission <PKG> [user-id] (user-id default: all users)"
}
function adb_revoke-storage-permission {
adb_revoke-permission "${1}" "android.permission.READ_EXTERNAL_STORAGE" "${2}"
adb_revoke-permission "${1}" "android.permission.WRITE_EXTERNAL_STORAGE" "${2}"
}
function _adb_usage_grant-location-permission {
echo "grant-location-permission <PKG> [user-id] (user-id default: all users)"
}
function adb_grant-location-permission {
adb_grant-permission "${1}" "android.permission.ACCESS_FINE_LOCATION" "${2}"
adb_grant-permission "${1}" "android.permission.ACCESS_COARSE_LOCATION" "${2}"
}
function _adb_usage_revoke-location-permission {
echo "revoke-location-permission <PKG> [user-id] (user-id default: all users)"
}
function adb_revoke-location-permission {
adb_revoke-permission "${1}" "android.permission.ACCESS_FINE_LOCATION" "${2}"
adb_revoke-permission "${1}" "android.permission.ACCESS_COARSE_LOCATION" "${2}"
}
function _adb_usage_disable-battery-optimization {
echo "disable-battery-optimization <PKG>"
}
function adb_disable-battery-optimization {
local apilevel=$(adb_get-android-api-level)
if [ "${apilevel}" -lt "${ANDROID_VERSION_CODES_M}" ]; then
warning "adb_disable-battery-optimization only works since Android 6.0 (API level 23)"
return
fi
adb_shell "dumpsys deviceidle whitelist +${1}"
}
function _adb_usage_enable-battery-optimization {
echo "enable-battery-optimization <PKG>"
}
function adb_enable-battery-optimization {
local apilevel=$(adb_get-android-api-level)
if [ "${apilevel}" -lt "${ANDROID_VERSION_CODES_M}" ]; then
warning "adb_enable-battery-optimization only works since Android 6.0 (API level 23)"
return
fi
adb_shell "dumpsys deviceidle whitelist -${1}"
}
###** Include backup_cmds.sh
# Depends: utils.sh, adb_utils.sh
declare -A backup_depends=(
[basename]=''
[dirname]=''
[awk]=''
[tar]=''
[gzip]=''
[find]=''
)
declare -A backup_depends_optional=(
[openssl]=''
)
# Constant variables
BACKUP_DEFAULT_APPDIR='./app'
BACKUP_DEFAULT_TYPE='app+data'
BACKUP_DEFAULT_ARCHIVE_SIDE='auto'
# Optional variables, could override in host script or config file
backup_opt_verbose=
backup_opt_appdir="${BACKUP_DEFAULT_APPDIR}"
backup_opt_archive_side="${BACKUP_DEFAULT_ARCHIVE_SIDE}"
declare -a backup_freq_apps=(
"com.android.providers.contacts"
"com.android.providers.settings"
"com.android.providers.telephony"
"com.android.messaging"
"com.android.settings"
"com.cyanogenmod.trebuchet"
"org.cyanogenmod.cmsettings"
"org.lineageos.lineagesettings"
"org.lineageos.trebuchet"
)
declare -a backup_spec_apps=(
"@bluetooth"
"@datausage"
"@user.accounts"
"@user.appwidgets"
"@user.icon"
"@user.wallpaper"
"@wifi"
)
backup_appinfo_name="appinfo.sh"
backup_appinfo_name_fallback="pkginfo.sh"
###*** Backup help functions
# arg1: appline, arg2: default user
# for example: 'user_0:com.android.providers.contacts:type=app+data;version_code=123' -> appuser=0, appid=com.android.providers.contacts, appline_options[type]=app+data;appline_options[version_code]=123
function backup_parse_appline {
local appline="${1}"
local default_user="${2}"
local options_str=
appuser= appid= appline_options=()
appid=$(echo "${appline}" | awk -F: '{print $1}')
if [[ "${appid}" =~ ^user_[^.]*$ ]]; then
# user prefix exists
appuser="${appid#user_}"
appid=$(echo "${appline}" | awk -F: '{print $2}')
options_str=$(echo "${appline}" | awk -F: '{print $3}')
else
appuser="${default_user}"
options_str=$(echo "${appline}" | awk -F: '{print $2}')
fi
if [ -n "${options_str}" -o -n "${backup_arg_options}" ]; then
local type= repo= version_code= install_path= allow_unstable= appdir= datedir= appfile= datafile=
if [ -n "${options_str}" ]; then
eval "${options_str}"
fi
if [ -n "${backup_arg_options}" ]; then
eval "${backup_arg_options}"
fi
[ -n "${type}" ] && appline_options[type]="${type}"
[ -n "${repo}" ] && appline_options[repo]="${repo}"
[ -n "${version_code}" ] && appline_options[version_code]="${version_code}"
[ -n "${install_path}" ] && appline_options[install_path]="${install_path}"
[ -n "${allow_unstable}" ] && appline_options[allow_unstable]="${allow_unstable}"
[ -n "${appdir}" ] && appline_options[appdir]="${appdir}"
[ -n "${datedir}" ] && appline_options[datedir]="${datedir}"
[ -n "${appfile}" ] && appline_options[appfile]="${appfile}"
[ -n "${datafile}" ] && appline_options[datafile]="${datafile}"
fi
if [ -z "${appline_options[type]}" ]; then
if backup_is_spec_app "${appid}"; then
appline_options[type]="data"
elif [ x"${appuser}" = x'system' ]; then
appline_options[type]="app"
elif backup_is_app_in_system_user "${appid}"; then
appline_options[type]="data"
else
appline_options[type]="${BACKUP_DEFAULT_TYPE}"
fi
fi
}
# arg1: app line, arg2: user
# for example: 'com.android.providers.contacts:app+data:version_code=123' -> appid='com.android.providers.contacts', type='app+data', appline_options='version_code=123'
function _backup_parse_appline_old {
local pkgline="${1}"
local user="${2}"
appid= type= appline_options=
appid=$(echo "${pkgline}" | awk -F: '{print $1}')
type=$(echo "${pkgline}" | awk -F: '{print $2}')
appline_options=$(echo "${pkgline}" | awk -F: '{print $3}')
if [ -z "${type}" ]; then
if backup_is_spec_app "${appid}"; then
type="data"
elif [ x"${user}" = x'system' ]; then
type="app"
elif backup_is_app_in_system_user "${appid}"; then
type="data"
else
type="${BACKUP_DEFAULT_TYPE}"
fi
fi
if [ -n "${backup_arg_type}" ]; then
type="${backup_arg_type}"
fi
}
# arg1: app id
function backup_is_app_in_system_user {
local check_appid="${1}"
local appid= appline=
for appline in "${system_apps[@]}"; do
if [[ "${appline}" =~ ^user_[^.]*: ]]; then
appid="${appline#*:}"
appid="${appid%:*}"
else
appid="${appline%:*}"
fi
if [ x"${check_appid}" = x"${appid}" ]; then
return
fi
done
return 1
}
# arg1: app id
function backup_get_comment {
local display_name=
if adb_is-online; then
display_name=$(adb_get-package-display-name "${appid}")
fi
local comment=
if [ -n "${display_name}" ]; then
comment=" # ${display_name}"
fi
echo "${comment}"
}
# arg1: backup_app, arg2: backup_data
function backup_get_backup_type {
local backup_app="${1}"
local backup_data="${2}"
if [ -n "${backup_app}" -a -n "${backup_data}" ]; then
echo "app+data"
elif [ -n "${backup_app}" ]; then
echo "app"
elif [ -n "${backup_data}" ]; then
echo "data"
fi
}
# arg1: app id
function backup_is_spec_app {
if [[ "${1}" == @* ]]; then
return 0
else
return 1
fi
}
# arg1: app id
function backup_is_spec_app_support_multiuser {
if [[ "${1}" == @user.* ]]; then
return 0
else
return 1
fi
}
function backup_fix_archive_side {
local old="${backup_opt_archive_side}"
if [ x"${backup_opt_archive_side}" = x"auto" ]; then
if backup_is_device_archive_cmd_exists; then
backup_opt_archive_side="device"
else
backup_opt_archive_side="host"
fi
fi
if [ x"${old}" != x"${backup_opt_archive_side}" ]; then
msg2 "fix archive side: ${old} -> ${backup_opt_archive_side}"
fi
if [ x"${backup_opt_archive_side}" = x"device" ] && ! backup_is_device_archive_cmd_exists; then
abort "User special --archive-side to device, but there is no tar and gzip commands in it, please install busybox and retry"
fi
}
function backup_is_device_archive_cmd_exists {
if adb_is-cmd-exists "tar" && adb_is-cmd-exists "gzip"; then
return 0
else
return 1
fi
}
function backup_prepare_args {
if [ -n "${backup_arg_profile}" ]; then
if [ ! -f "${backup_arg_profile}" ]; then
abort "Profile file not found: ${backup_arg_profile}"
fi
local profile_path=$(get_realpath "${backup_arg_profile}")
profile_dir=$(dirname "${backup_arg_profile}")
source "${backup_arg_profile}"
# check profile version
if [ x"${arg_cmd}" != x"check" ] && compver_lt "${version}" "${profile_version}"; then
abort "Profile format not support: please use 'check --fix' to upgrade version to ${profile_version}"
fi
# fix relative path
if [ x"${PWD}" != x"${profile_dir}" ]; then
appdir=$(get_realpath "${appdir}" "${profile_dir}")
datadir=$(get_realpath "${datadir}" "${profile_dir}")
fi
else
backup_build_default_profile "${@}"
fi
# add user prefix for system_apps
local i= appline=
for ((i=0; i<"${#system_apps[@]}"; i++)); do
appline="${system_apps[i]}"
if [[ "${appline}" =~ ^user_[^.]*: ]]; then
system_apps[i]="user_system:${appline#user_*:}"
else
system_apps[i]="user_system:${appline}"
fi
if ! contains "system" "${users[@]}"; then
users=("system" "${users[@]}")
fi
done
# expand user aliases: cur, current, sys, all
for ((i=0; i<"${#user_apps[@]}"; i++)); do
appline="${user_apps[i]}"
if [[ "${appline}" =~ ^user_[^.]*: ]]; then
local appuser="${appline%%:*}"
appuser="${appuser#user_}"
if [ x"${appuser}" = x'cur' -o x"${appuser}" = x'current' ]; then
local cur=$(adb_get-current-user)
user_apps[i]="user_${cur}:${appline#user_*:}"
if ! contains "${cur}" "${users[@]}"; then
users+=("${cur}")
fi
elif [ x"${appuser}" = x'all' ]; then
user_apps[i]="user_0:${appline#user_*:}"
if ! contains "0" "${users[@]}"; then
users+=("0")
fi
local u=
for u in $(adb_list-users | awk '{print $1}'); do
if [ x"${u}" != x"0" ]; then
user_apps+=("user_${u}:${appline#user_*:}")
if ! contains "${u}" "${users[@]}"; then
users+=("${u}")
fi
fi
done
elif ! contains "${appuser}" "${users[@]}"; then
users+=("${appuser}")
fi
else
# no user special, expand to user 0
user_apps[i]="user_0:${appline}"
if ! contains "0" "${users[@]}"; then
users+=("0")
fi
fi
done
# filter applines
for ((i="${#system_apps[@]}"-1; i>=0; i--)); do
appline="${system_apps[i]}"
if ! [[ "${appline}" =~ "${backup_arg_filter}" ]]; then
msg2 "ignore appline ${appline} by filter ${backup_arg_filter}"
unset system_apps[i]
fi
done
for ((i="${#user_apps[@]}"-1; i>=0; i--)); do
appline="${user_apps[i]}"
if ! [[ "${appline}" =~ "${backup_arg_filter}" ]]; then
msg2 "ignore appline ${appline} by filter ${backup_arg_filter}"
unset user_apps[i]
fi
done
msg2 "profile dir: ${profile_dir}"
msg2 "profile name: ${name}"
msg2 "app dir: ${appdir}"
msg2 "data dir: ${datadir}"
msg2 "user ids: ${users[*]}"
}
function backup_build_default_profile {
if [ ${#} -eq 0 ]; then
abort "Must special --profile or BACKUPAPPLINE|${backup_appinfo_name}.."
fi
profile_dir=$(get_realpath ".")
name="${backup_arg_name:-default}"
appdir=$(get_realpath "${backup_opt_appdir}")
local user_datadir=
local appline=
for appline in "${@}"; do
if [[ "${appline}" =~ /"${backup_appinfo_name}"$ ]]; then
# appline is a appinfo.sh file
if [ ! -f "${appline}" ]; then
abort "${backup_appinfo_name} file not found: ${appline}"
fi
# fix appline name, profile dir and profile name
local appinfo_file=$(get_realpath "${appline}")
local appinfo_dir=$(dirname "${appinfo_file}")
appline=$(basename "${appinfo_dir}")
user_datadir=$(dirname "${appinfo_dir}")
appline="$(basename "${user_datadir}"):${appline}"
if [ -z "${datadir}" ]; then
datadir=$(dirname "${user_datadir}")
elif [ x"${datadir}" != x$(dirname "${user_datadir}") ]; then
abort "Could not detect the correct datadir, there are multiple ${backup_appinfo_name} but with different datadir"
fi
profile_dir=$(dirname "${datadir}")
name=$(basename "${datadir}")
elif [ -z "${datadir}" ]; then
datadir=$(get_realpath "./data/${name}")
fi
# parse appline
local appuser= appid=; unset appline_options; declare -A appline_options
backup_parse_appline "${appline}"
if backup_is_spec_app "${appid}"; then
if backup_is_spec_app_support_multiuser "${appid}"; then
eval "user_apps+=('${appline}')"
else
eval "system_apps+=('${appline}')"
fi
else
if [ x"${appuser}" = x'sys' -o x"${appuser}" = x'system' ]; then
eval "system_apps+=('${appline}')"
else
eval "user_apps+=('${appline}')"
fi
fi
done
if [ -z "${profile_dir}" -o -z "${name}" ]; then
abort "Could not detect profile dir(${profile_dir}) or profile name(${name}), a correct dir tree is: profildir/profilename/user_x/com.app.name/${backup_appinfo_name}"
fi
}
# arg1: <appid>, arg2: <user-id> arg3: [apilevel]
function backup_get_backup_files {
local appid="${1}"
local user="${2}"
if ! backup_is_spec_app "${appid}"; then
# data file only exists for normal users
if [ x"${user}" != x"system" ]; then
device_backup_files+=("$(adb_get-package-datadir "${appid}" "${user}")")
fi
return
fi
local apilevel="${3:-$(adb_get-android-api-level)}"
case "${appid}" in
"@user.icon")
if adb_is-support-multiuser "${apilevel}"; then
device_backup_files+=("/data/system/users/${user}/photo.png")
fi
;;
"@user.accounts")
if [ "${apilevel}" -ge "${ANDROID_VERSION_CODES_N}" ]; then
device_backup_files+=("/data/system_ce/${user}/accounts_ce.db")
device_backup_files+=("/data/system_de/${user}/accounts_de.db")
elif adb_is-support-multiuser "${apilevel}"; then
device_backup_files+=("/data/system/users/${user}/accounts.db")
else
device_backup_files+=("/data/system/accounts.db")
fi
;;
"@user.appwidgets")
if adb_is-support-multiuser "${apilevel}"; then
device_backup_files+=("/data/system/users/${user}/appwidgets.xml")
else
device_backup_files+=("/data/system/appwidgets.xml")
fi
;;
"@user.wallpaper")
if adb_is-support-multiuser "${apilevel}"; then
device_backup_files+=("/data/system/users/${user}/wallpaper")
device_backup_files+=("/data/system/users/${user}/wallpaper_info.xml")
else
device_backup_files+=("/data/system/wallpaper")
device_backup_files+=("/data/system/wallpaper_info.xml")
fi
;;
"@datausage")
if adb_is-support-multiuser "${apilevel}"; then
device_backup_files+=("/data/system/netpolicy.xml")
device_backup_files+=("/data/system/netstats")
fi
;;
"@bluetooth")
if adb_is-support-multiuser "${apilevel}"; then
device_backup_files+=("/data/misc/bluedroid")
else
device_backup_files+=("/data/misc/bluetooth")
device_backup_files+=("/data/misc/bluetoothd")
fi
;;
"@wifi")
if [ "${apilevel}" -ge "${ANDROID_VERSION_CODES_O}" ]; then
device_backup_files+=("/data/misc/wifi/WifiConfigStore.xml" "/data/misc/wifi/wpa_supplicant.conf")
else
device_backup_files+=("/data/misc/wifi/wpa_supplicant.conf")
fi
;;
esac
}
# arg1: <appid>, arg2: <backup-apilevel>, arg3: <current-apilevel>, arg4: <user>, arg5: <path>
function backup_fix_restore_path_for_compatibility {
local appid="${1}"
local backup_apilevel="${2}"
local current_apilevel="${3}"
local user="${4}"
local path="${5}"
local newpath="${path}"
case "${path}" in
"/data/system/accounts.db" |\
"/data/system/users/${user}/accounts.db" |\
"/data/system_ce/${user}/accounts_ce.db")
if [ "${current_apilevel}" -ge "${ANDROID_VERSION_CODES_N}" ]; then
newpath="/data/system_ce/${user}/accounts_ce.db"
elif adb_is-support-multiuser "${current_apilevel}"; then
newpath="/data/system/users/${user}/accounts.db"
else
newpath="/data/system/accounts.db"
fi
;;
esac
echo "${newpath}"
}
# arg1: <app-id>, arg2: <backup-apilevel>, arg3: <current-apilevel>
function backup_check_app_data_compatibility {
local appid="${1}"
local backup_apilevel="${2}"
local current_apilevel="${3}"
case "${appid}" in
"@user.accounts")
if [ "${backup_apilevel}" -lt "${ANDROID_VERSION_CODES_N}" ] &&
[ "${current_apilevel}" -ge "${ANDROID_VERSION_CODES_N}" ]; then
return 1
fi
if [ "${backup_apilevel}" -ge "${ANDROID_VERSION_CODES_N}" ] &&
[ "${current_apilevel}" -lt "${ANDROID_VERSION_CODES_N}" ]; then
return 1
fi
;;
"@bluetooth")
if adb_is-support-multiuser "${backup_apilevel}" && ! adb_is-support-multiuser "${current_apilevel}"; then
return 1
fi
if adb_is-support-multiuser "${current_apilevel}" && ! adb_is-support-multiuser "${backup_apilevel}"; then
return 1
fi
;;
com.android.*)
if [ "${current_apilevel}" -lt "${backup_apilevel}" ]; then
return 1
fi
;;
esac
return 0
}
# arg1: <appid>, arg2: [user-id], arg3: [apilevel]
function backup_adb_fix-spec-datafile-permission {
local appid="${1}"
local user="${2:-$(adb_get-current-user)}"
local apilevel="${3:-$(adb_get-android-api-level)}"
local device_backup_files=()
backup_get_backup_files "${appid}" "${user}" "${apilevel}"
local f
for f in "${device_backup_files[@]}"; do
if ! adb_is-file-exists "${f}"; then
msg2 "ignore fix permission: ${f} not found"
continue
fi
case "${f}" in
"/data/misc/wifi/WifiConfigStore.xml" |\
"/data/system/users/${user}/photo.png" |\
"/data/system_ce/${user}/accounts_ce.db" |\
"/data/system/users/${user}/accounts.db" |\
"/data/system/accounts.db" |\
"/data/system/users/${user}/appwidgets.xml" |\
"/data/system/appwidgets.xml" |\
"/data/system/users/${user}/wallpaper" |\
"/data/system/users/${user}/wallpaper_info.xml" |\
"/data/system/wallpaper" |\
"/data/system/wallpaper_info.xml" |\
"/data/system/netpolicy.xml" |\
"/data/system/netstats")
adb_chown-recursive 'system:system' "${f}"
adb_rootshell "${adb_opt_busybox} chmod -R 0600 '${f}'"
;;
"/data/system_de/${user}/accounts_de.db")
adb_chown-recursive 'system:system' "${f}"
adb_rootshell "${adb_opt_busybox} chmod -R 0660 '${f}'"
;;
"/data/misc/bluedroid" | \
"/data/misc/bluetooth" | \
"/data/misc/bluetoothd")
adb_chown-recursive 'bluetooth:bluetooth' "${f}"
adb_rootshell "${adb_opt_busybox} chmod -R 0771 '${f}'"
;;
"/data/misc/wifi/wpa_supplicant.conf")
adb_rootshell "${adb_opt_busybox} chown wifi:wifi '${f}'"
adb_rootshell "${adb_opt_busybox} chmod 0660 '${f}'"
;;
esac
adb_fix-selinux-permission "${f}"
done
}
function _backup_convert_appinfo_vars {
appinfo_options[id]="${id}"
appinfo_options[version_name]="${version_name}"
appinfo_options[version_code]="${version_code}"
appinfo_options[android_api_level]="${android_api_level}"
appinfo_options[is_system]="${is_system}"
appinfo_options[is_encrypted]="${is_encrypted}"
appinfo_options[install_path]="${install_path}"
appinfo_options[data_timestamp]="${data_timestamp}"
}
# arg1: appinfo-file
function backup_generate_appinfo_file {
echo "id='${appinfo_options[id]}'
version_name='${appinfo_options[version_name]}'
version_code='${appinfo_options[version_code]}'
android_api_level='${appinfo_options[android_api_level]}'
is_system='${appinfo_options[is_system]}'
is_encrypted='${appinfo_options[is_encrypted]}'
install_path='${appinfo_options[install_path]}'
data_timestamp='${appinfo_options[data_timestamp]}'" > "${1}"
}
# arg1: appinfo-file
function backup_parse_appinfo_file {
local appinfo_file="${1}"
local id= version_name= version_code= android_api_level= is_system= is_encrypted= install_path= data_timestamp=
appinfo_options=()
source "${appinfo_file}"
[ -n "${id}" ] && appinfo_options[id]="${id}"
[ -n "${version_name}" ] && appinfo_options[version_name]="${version_name}"
[ -n "${version_code}" ] && appinfo_options[version_code]="${version_code}"
[ -n "${android_api_level}" ] && appinfo_options[android_api_level]="${android_api_level}"
[ -n "${is_system}" ] && appinfo_options[is_system]="${is_system}"
[ -n "${is_encrypted}" ] && appinfo_options[is_encrypted]="${is_encrypted}"
[ -n "${install_path}" ] && appinfo_options[install_path]="${install_path}"
[ -n "${data_timestamp}" ] && appinfo_options[data_timestamp]="${data_timestamp}"
}
# arg1: appinfo-file, arg2: new-varname, arg3: old-varname
function backup_need_fallback_var {
local file="${1}"
local new_varname="${2}"
local old_varname="${3}"
local old_var= new_var=
declare -n old_var="${old_varname}"
declare -n new_var="${new_varname}"
if [ -z "${new_var}" -a -n "${old_var}" ]; then
msg2 "old style: variable \$${old_varname} not found, fallback to \$${new_varname} in ${file}"
return
fi
return 1
}
# arg1: new-file-varname, arg2: old-file-varname
function backup_need_fallback_file {
local new_varname="${1}"
local old_varname="${2}"
local old_var= new_var=
declare -n old_var="${old_varname}"
declare -n new_var="${new_varname}"
if [ ! -f "${new_var}" -a -f "${old_var}" ]; then
msg2 "old style: file ${new_var} not found, fallback to ${old_var}"
return
fi
return 1
}
###*** Backup commands
function _cmd_usage_new {
echo "new [--profile-type type] [--3rd-app] [--3rd-data] [--sys-app] [--sys-data] [--freq-app] [--freq-data] [--spec-data] [--name name] [-u|--user id] [--appdir dir] [--device-tmpdir dir] <--profile file.sh>
new template profile for different commands
--profile-type, append different template code for different types, could set multiple times
the value could be 'backup', 'restore', 'deploy' and 'cleanup'
--3rd-app, backup 3rd application
--3rd-data, backup 3rd application data
--sys-app, backup system application
--sys-data, backup system application data
--freq-app, backup frequently used application which defined in \$backup_freq_apps
such as contacts, call logs
(conflict with --sys-app and --sys-data)
--freq-data, backup frequently used application data
--spec-data, backup special data which defined in \$backup_spec_apps
such as user accounts, saved wifi access points"
}
function cmd_new {
msg "New backup profile ${backup_arg_profile}"
do_cmd_new > "${backup_arg_profile}"
}
function do_cmd_new {
if [ -z "${backup_arg_profile}" ]; then
abort "Need special --profile file.sh"
fi
# fix args
if [ -n "${backup_arg_sys_app}" -a -n "${backup_arg_freq_app}" ]; then
backup_arg_freq_app=
msg2 "ignore --freq-app for --sys-app exists"
fi
if [ -n "${backup_arg_sys_data}" -a -n "${backup_arg_freq_data}" ]; then
backup_arg_freq_data=
msg2 "ignore --freq-data for --sys-data exists"
fi
local content=
local header_block= user_block= system_apps_block= user_apps_block=
local backup_block= restore_block= deploy_block= cleanup_block=
# header
local name="${backup_arg_name}"
if [ -z "${name}" ]; then
name=$(basename "${backup_arg_profile}")
name=${name%.*}
fi
header_block="version='${app_version}'
name='${name}'"
header_block="${header_block}
appdir='${backup_opt_appdir}'
datadir='./data/${name}'"
if adb_is-online; then
header_block="${header_block}
android_api_level=$(adb_get-android-api-level) # android $(adb_get-android-version)"
fi
# user block
local user_block= system_apps_block= user_apps_block=
local appid= options_str=
local users=() user= users_str=
users_str=
if [ -n "${backup_arg_spec_data}" -o -n "${backup_arg_sys_app}" -o -n "${backup_arg_freq_app}" ]; then
users_str="system"
fi
if adb_is-online; then
users_str="${users_str} $(adb_list-users | awk '{print $1}')"
else
users_str="${users_str} 0"
fi
for user in ${users_str}; do
if [ ${#backup_arg_users[@]} -gt 0 ]; then
if contains "${user}" "${backup_arg_users[@]}"; then
users+=("${user}")
fi
else
users+=("${user}")
fi
done
user_block="users=(${users[@]})"
if adb_is-online; then
for user in "${users[@]}"; do
if [ x"${user}" != x"system" ] && ! adb_is-only-one-user; then
user_block="${user_block}\nuser_${user}_name='$(adb_get-user-name ${user})'"
fi
done
fi
# for system user
if contains "system" "${users[@]}"; then
system_apps_block="system_apps=(\n"
# sys apps
if [ -n "${backup_arg_sys_app}" ]; then
if adb_is-online; then
system_apps_block="${system_apps_block} # sys apps\n"
for appid in $(adb_list-sys-packages ${user} | sort); do
options_str="type=app"
if contains "deploy" "${backup_arg_profile_types[@]}" || contains "cleanup" "${backup_arg_profile_types[@]}"; then
options_str="${options_str};version_code=$(adb_get-package-version-code ${appid})"
fi
system_apps_block="${system_apps_block} '${appid}:${options_str}'$(backup_get_comment ${appid})\n"
done
else
msg2 "ignore --sys-app for adb offline"
fi
fi
# freq apps
if [ -n "${backup_arg_freq_app}" ]; then
system_apps_block="${system_apps_block} # freq apps\n"
for appid in "${backup_freq_apps[@]}"; do
if ! adb_is-online || adb_is-sys-package "${appid}"; then
options_str="type=app"
if adb_is-online; then
if contains "deploy" "${backup_arg_profile_types[@]}" || contains "cleanup" "${backup_arg_profile_types[@]}"; then
options_str="${options_str};version_code=$(adb_get-package-version-code ${appid})"
fi
fi
system_apps_block="${system_apps_block} '${appid}:${options_str}'$(backup_get_comment ${appid})\n"
fi
done
fi
# spec system data
if [ -n "${backup_arg_spec_data}" ]; then
system_apps_block="${system_apps_block} # spec system data\n"
local appid=
for appid in "${backup_spec_apps[@]}"; do
if ! backup_is_spec_app_support_multiuser "${appid}"; then
system_apps_block="${system_apps_block} '${appid}'\n"
fi
done
fi
system_apps_block="${system_apps_block})"
fi
# for normal user
user_apps_block="user_apps=(\n"
for user in "${users[@]}"; do
if [ x"${user}" = x"system" ] ; then
continue
fi
local user_prefix= user_comment=
if [ x"${user}" != x"0" ]; then
user_prefix="user_${user}:"
fi
if adb_is-online && ! adb_is-only-one-user; then
user_comment=" for user ${user}"
fi
# sys apps data
if [ -n "${backup_arg_sys_data}" ]; then
if adb_is-online; then
user_apps_block="${user_apps_block} # sys apps data${user_comment}\n"
for appid in $(adb_list-sys-packages ${user} | sort); do
# for com.android.providers applications, the data files only
# exists in the default user
if [[ "${appid}" =~ ^com.android.providers ]]; then
if [ x"${user}" = x"0" ]; then
user_apps_block="${user_apps_block} '${user_prefix}${appid}:type=data'$(backup_get_comment ${appid})\n"
fi
else
user_apps_block="${user_apps_block} '${user_prefix}${appid}:type=data'$(backup_get_comment ${appid})\n"
fi
done
else
msg2 "ignore --sys-data for adb offline"
fi
fi
# freq apps and data
if [ -n "${backup_arg_freq_app}" -o -n "${backup_arg_freq_data}" ]; then
user_apps_block="${user_apps_block} # freq apps${user_comment}\n"
for appid in "${backup_freq_apps[@]}"; do
if adb_is-online && adb_is-3rd-package "${appid}"; then
local backup_type=$(backup_get_backup_type "${backup_arg_freq_app}" "${backup_arg_freq_data}")
options_str="type=${backup_type}"
if [[ "${backup_type}" =~ "app" ]]; then
if contains "deploy" "${backup_arg_profile_types[@]}" || contains "cleanup" "${backup_arg_profile_types[@]}"; then
options_str="${options_str};version_code=$(adb_get-package-version-code ${appid})"
fi
fi
user_apps_block="${user_apps_block} '${user_prefix}${appid}:${options_str}'$(backup_get_comment ${appid})\n"
elif ! adb_is-online || adb_is-sys-package "${appid}" && [ -n "${backup_arg_freq_data}" ];then
# for com.android.providers applications, the data files only
# exists in the default user
if [[ "${appid}" =~ ^com.android.providers ]]; then
if [ x"${user}" = x"0" ]; then
user_apps_block="${user_apps_block} '${user_prefix}${appid}:type=data'$(backup_get_comment ${appid})\n"
fi
else
user_apps_block="${user_apps_block} '${user_prefix}${appid}:type=data'$(backup_get_comment ${appid})\n"
fi
fi
done
fi
# 3rd apps and data
if [ -n "${backup_arg_3rd_app}" -o -n "${backup_arg_3rd_data}" ]; then
if adb_is-online; then
user_apps_block="${user_apps_block} # 3rd apps${user_comment}\n"
for appid in $(adb_list-3rd-packages ${user} | sort); do
local backup_type=$(backup_get_backup_type "${backup_arg_3rd_app}" "${backup_arg_3rd_data}")
options_str="type=${backup_type}"
if [[ "${backup_type}" =~ "app" ]]; then
if contains "deploy" "${backup_arg_profile_types[@]}" || contains "cleanup" "${backup_arg_profile_types[@]}"; then
options_str="${options_str};version_code=$(adb_get-package-version-code ${appid})"
fi
fi
user_apps_block="${user_apps_block} '${user_prefix}${appid}:${options_str}'$(backup_get_comment ${appid})\n"
done
else
msg2 "ignore --3rd-app and --3rd-data for adb offline"
fi
fi
if [ -n "${backup_arg_spec_data}" ]; then
user_apps_block="${user_apps_block} # spec user data${user_comment}\n"
for appid in "${backup_spec_apps[@]}"; do
if backup_is_spec_app_support_multiuser "${appid}"; then
user_apps_block="${user_apps_block} '${user_prefix}${appid}'\n"
fi
done
fi
done
user_apps_block="${user_apps_block})"
backup_block="## backup pre/post hooks
# function pre_backup {}
# function pre_backup_adb {} # run in adb shell
# function post_backup_adb {} # run in adb shell
# function post_backup {}
# function my_backup_pre_app_hook {}
# function my_backup_post_app_hook {}
# function my_backup_pre_data_hook {}
# function my_backup_post_data_hook {}
# backup_pre_app_hooks['user_0']=my_backup_pre_app_hook
# backup_post_app_hooks['user_0:appid']=my_backup_post_app_hook
# backup_pre_data_hooks['user_0']=my_backup_pre_data_hook
# backup_post_data_hooks['user_0:appid']=my_backup_post_data_hook"
restore_block=$(echo "${backup_block}" | sed 's/backup/restore/g')
deploy_block="## deploy pre/post hooks
# function pre_deploy {}
# function pre_deploy_adb {} # run in adb shell
# function post_deploy_adb {} # run in adb shell
# function post_deploy {}
# function my_deploy_pre_app_hook {}
# function my_deploy_post_app_hook {}
# deploy_pre_app_hooks['user_0']=my_deploy_pre_app_hook
# deploy_post_app_hooks['user_0:appid']=my_deploy_post_app_hook
# function my_deploy_file {}
# function my_deploy_dir {}
# deploy_file_funcs['file-path']=my_deploy_file
# deploy_file_funcs['dir-path']=my_deploy_dir"
cleanup_block=$(echo "${deploy_block}" | sed 's/deploy/cleanup/g')
content="${header_block}\n\n${user_block}\n\n${system_apps_block}\n\n${user_apps_block}"
if contains "backup" "${backup_arg_profile_types[@]}"; then
content="${content}\n\n${backup_block}"
fi
if contains "restore" "${backup_arg_profile_types[@]}"; then
content="${content}\n\n${restore_block}"
fi
if contains "deploy" "${backup_arg_profile_types[@]}"; then
content="${content}\n\n${deploy_block}"
fi
if contains "cleanup" "${backup_arg_profile_types[@]}"; then
content="${content}\n\n${cleanup_block}"
fi
echo -e "${content}"
}
function _cmd_usage_backup {
echo "backup [--name name] [--password pwd] [--options options] [--filter filter] [--appdir dir] [--device-tmpdir dir] [--archive-side value] <--profile file.sh|BACKUPAPPLINE..|${backup_appinfo_name}..>
backup android application and user data"
}
function cmd_backup {
declare -A backup_pre_app_hooks
declare -A backup_post_app_hooks
declare -A backup_pre_data_hooks
declare -A backup_post_data_hooks
adb_check-root
backup_fix_archive_side
local profile_dir= name= version= appdir= datadir= users=() system_apps=() user_apps=()
backup_prepare_args "${@}"
run_cmd_hook "backup" "pre"
msg "Backup applications"
msg2 "datadir: ${datadir}"
local appline=
for appline in "${system_apps[@]}" "${user_apps[@]}"; do
local appuser= appid=; unset appline_options; declare -A appline_options
backup_parse_appline "${appline}"
if contains "${appuser}" "${users[@]}"; then
_cmd_do_backup
fi
done
run_cmd_hook "backup" "post"
}
function _cmd_do_backup {
# source appinfo.sh
local user_datadir="${datadir}/user_${appuser}"
if [ -n "${appline_options[datadir]}" ]; then
user_datadir="${appline_options[datadir]}/user_${appuser}"
fi
local host_datadir="${user_datadir}/${appid}"
local appinfo_file="${host_datadir}/${backup_appinfo_name}"
unset appinfo_options; declare -A appinfo_options
if [ -f "${appinfo_file}" ]; then
backup_parse_appinfo_file "${appinfo_file}"
else
appinfo_options[data_timestamp]=0
fi
# backup app
if [[ "${appline_options[type]}" =~ "app" ]]; then
run_appline_hook "backup" "pre" "app" "user_${appuser}:${appid}"
if ! adb_is-package-installed "${appid}" "${appuser}"; then
msg2 "ignore backup app user_${appuser}:${appid}: not installed in user ${appuser}"
if ! backup_is_spec_app "${appid}"; then
return
fi
else
appinfo_options[version_name]=$(adb_get-package-version-name "${appid}")
appinfo_options[version_code]=$(adb_get-package-version-code "${appid}")
local app_filename="${appid}_${appinfo_options[version_code]}.apk"
local host_appfile="${appdir}/${app_filename}"
if [ -n "${appline_options[appdir]}" ]; then
host_appfile="${appline_options[appdir]}/${app_filename}"
fi
if [ -n "${appline_options[appfile]}" ]; then
host_appfile="${appline_options[appfile]}"
fi
mkdir -p "$(dirname "${host_appfile}")"
local device_appfile=$(adb_get-package-path "${appid}")
local ignore_app=
if [ -f "${host_appfile}" ]; then
if _adb_is-same-file "${device_appfile}" "${host_appfile}"; then
ignore_app=1
else
msg2 "backup app user_${appuser}:${appid}: ${host_appfile} exists but checksum failed, will overwrite later"
fi
fi
if [ -n "${ignore_app}" ]; then
msg2 "ignore backup app user_${appuser}:${appid}: ${host_appfile} is up to date"
else
msg2 "backup app user_${appuser}:${appid}: ${device_appfile} -> ${host_appfile}"
adb_pull "${device_appfile}" "${host_appfile}" 1 || true
fi
if adb_is-sys-package "${appid}"; then
appinfo_options[is_system]='1'
fi
# save system app directory if is not /system/app
if [ -n "${appinfo_options[is_system]}" ]; then
local device_appdir=$(dirname "${device_appfile}")
local apilevel=$(adb_get-android-api-level)
if [ "${apilevel}" -ge "${ANDROID_VERSION_CODES_LOLLIPOP}" ]; then
device_appdir=$(dirname "${device_appdir}")
fi
if [ x"${device_appdir}" != x"/system/app" ]; then
appinfo_options[install_path]="${device_appdir}"
fi
fi
fi
run_appline_hook "backup" "post" "app" "user_${appuser}:${appid}"
fi
# backup data
if [[ "${appline_options[type]}" =~ "data" ]]; then
run_appline_hook "backup" "pre" "data" "user_${appuser}:${appid}"
local device_backup_files=()
backup_get_backup_files "${appid}" "${appuser}"
local device_timestamp=0
local ignore_data=
local f=
for f in "${device_backup_files[@]}"; do
local t=$(adb_get-file-timestamp "${f}")
if [ "${t}" -gt "${device_timestamp}" ]; then
device_timestamp="${t}"
fi
done
if [ -f "${appinfo_file}" ]; then
if [ "${device_timestamp}" -le "${appinfo_options[data_timestamp]}" ]; then
ignore_data=1
else
appinfo_options[data_timestamp]="${device_timestamp}"
fi
else
appinfo_options[data_timestamp]="${device_timestamp}"
fi
if [ "${device_timestamp}" -eq '0' ]; then
# all backup files not found in device
ignore_data=2
fi
if [ -n "${ignore_data}" ]; then
case "${ignore_data}" in
1) msg2 "ignore backup data user_${appuser}:${appid}: ${host_datadir} is up to date";;
2) msg2 "ignore backup data user_${appuser}:${appid}: data files(${device_backup_files[@]}) all not found in device";;
esac
else
local archive_filename="${appid}.tar.gz"
# pull data files from device
mkdir -p "${host_datadir}"
if [ x"${backup_opt_archive_side}" = x"device" ]; then
# make archive in device and pull
local device_data_archive="${adb_opt_device_tmpdir}/${archive_filename}"
local backup_filenames=()
for f in "${device_backup_files[@]}"; do
backup_filenames+=( $(basename "${f}") )
done
# cleanup
adb_rootshell "${adb_opt_busybox} rm '${device_data_archive}'"
if [ ${#device_backup_files[@]} -eq 1 ]; then
local f="${device_backup_files[0]}"
adb_rootshell "${adb_opt_busybox} tar -czhf ${device_data_archive} -C '$(dirname "${f}")' '$(basename "${f}")' 1>/dev/null"
else
adb_rootshell "cd '${adb_opt_device_tmpdir}'; for f in ${device_backup_files[@]}; do ${adb_opt_busybox} rm -r \$(basename \"\${f}\") 2>/dev/null; ${adb_opt_busybox} cp -R -L \"\${f}\" .; done; ${adb_opt_busybox} tar -czhf ${device_data_archive} ${backup_filenames[@]} 1>/dev/null; ${adb_opt_busybox} rm -r ${backup_filenames[@]}"
fi
msg2 "backup data user_${appuser}:${appid}: ${device_data_archive} -> ${host_datadir}"
adb_chown-recursive "shell:shell" "${device_data_archive}"
adb_pull "${device_data_archive}" "${host_datadir}"
# cleanup again
adb_rootshell "${adb_opt_busybox} rm '${device_data_archive}'"
else
# pull files and make archive in host
local backup_filenames=()
for f in "${device_backup_files[@]}"; do
msg2 "backup data user_${appuser}:${appid}: ${f} -> ${host_datadir}"
if adb_pull "${f}" "${host_datadir}" 1; then
backup_filenames+=( $(basename "${f}") )
fi
done
(
cd "${host_datadir}"
rm -f "${archive_filename}"
tar -czhf "${archive_filename}" "${backup_filenames[@]}" 1>/dev/null
rm -rf "${backup_filenames[@]}"
)
fi
if [ -n "${backup_arg_password}" ]; then
msg2 "encrypt archive: ${archive_filename} -> ${archive_filename}.enc"
appinfo_options[is_encrypted]='1'
openssl enc -aes-256-cbc -k "${backup_arg_password}" -in "${host_datadir}/${archive_filename}" -out "${host_datadir}/${archive_filename}".enc
rm -f "${host_datadir}/${archive_filename}"
fi
fi
run_appline_hook "backup" "post" "data" "user_${appuser}:${appid}"
fi
# generate appinfo.sh
mkdir -p "${host_datadir}"
appinfo_options[id]="${appid}" appinfo_options[android_api_level]=$(adb_get-android-api-level)
backup_generate_appinfo_file "${appinfo_file}"
}
function _cmd_usage_restore {
echo "restore [--name name] [-u|--user id] [--options options] [--filter filter] [--wait-sys-app seconds] [--default-install-path] [--appdir dir] [--device-tmpdir dir] [--archive-side value] <--profile file.sh|RESTOREAPPLINE..|${backup_appinfo_name}..>
restore android application and user data, for multiple users, will create user if need
--wait-sys-app, wait for new installed system app ready [default: 3]
--default-install-path, install app to /data/app or /system/app instead of the defined install_path in ${backup_appinfo_name}"
}
function cmd_restore {
declare -A restore_pre_app_hooks
declare -A restore_post_app_hooks
declare -A restore_pre_data_hooks
declare -A restore_post_data_hooks
adb_check-root
backup_fix_archive_side
local error_apps=()
local profile_dir= name= version= appdir= datadir= users=() system_apps=() user_apps=()
backup_prepare_args "${@}"
run_cmd_hook "restore" "pre"
# ensure users exists
local user= user_name=
for user in "${users[@]}"; do
declare -n user_name="user_${user}_name"
if [ x"${user}" != x"system" ]; then
adb_ensure-user-exists "${user}" "${user_name}"
fi
done
local redirect_user=
if [ ${#backup_arg_users[@]} -gt 0 -a -z "${backup_arg_profile}" ]; then
users=("${backup_arg_users[@]}")
redirect_user=1
fi
local new_app_installed_in_prev_user=
for user in "${users[@]}"; do
msg "Restore applications for user ${user}"
# if new application install in previous user, just update package cache
if [ -n "${new_app_installed_in_prev_user}" ]; then
_adb_fetch_pkgs_cache
new_app_installed_in_prev_user=
fi
local appline=
for appline in "${system_apps[@]}" "${user_apps[@]}"; do
local appuser= appid=; unset appline_options; declare -A appline_options
backup_parse_appline "${appline}"
if [ -n "${redirect_user}" ]; then
_cmd_do_restore
elif [ x"${appuser}" = x"${user}" ]; then
_cmd_do_restore
fi
done
done
run_cmd_hook "restore" "post"
if [ ${#error_apps[@]} -ne 0 ]; then
warning "Restore applications failed: ${error_apps[*]}"
return 1
else
return 0
fi
}
function _cmd_do_restore {
# source appinfo.sh
local user_datadir="${datadir}/user_${appuser}"
if [ -n "${appline_options[datadir]}" ]; then
user_datadir="${appline_options[datadir]}/user_${appuser}"
fi
local host_datadir="${user_datadir}/${appid}"
local appinfo_file="${host_datadir}/${backup_appinfo_name}"
unset appinfo_options; declare -A appinfo_options
if [ -f "${appinfo_file}" ]; then
backup_parse_appinfo_file "${appinfo_file}"
elif [ x"${appline_options[type]}" = x"data" ]; then
appinfo_options[data_timestamp]=0
else
msg2 "ignore restore app user_${appuser}:${appid}: ${appinfo_file} not found"
return
fi
if ! backup_is_spec_app "${appid}"; then
adb_stop-package "${appid}"
fi
# restore app
local fresh_installed=
if [[ "${appline_options[type]}" =~ "app" ]]; then
run_appline_hook "restore" "pre" "app" "user_${appuser}:${appid}"
local app_filename="${appid}_${appinfo_options[version_code]}.apk"
local host_appfile="${appdir}/${app_filename}"
if [ -n "${appline_options[appdir]}" ]; then
host_appfile="${appline_options[appdir]}/${app_filename}"
fi
if [ -n "${appline_options[appfile]}" ]; then
host_appfile="${appline_options[appfile]}"
fi
local ignore_app=
if adb_is-package-installed "${appid}" "${user}"; then
local installed_version_code=$(adb_get-package-version-code "${appid}")
# only reinstall for newer version
if [ 10"${installed_version_code}" -ge 10"${appinfo_options[version_code]}" ]; then
ignore_app=1
else
msg2 "restore app user_${appuser}:${appid}: already installed but version is older, will reinstall later"
fi
elif adb_is-package-installed "${appid}"; then
# installed in other user
local installed_version_code=$(adb_get-package-version-code "${appid}")
if [ 10"${installed_version_code}" -ge 10"${appinfo_options[version_code]}" ]; then
ignore_app=2
fi
fi
if [ -n "${ignore_app}" ]; then
case "${ignore_app}" in
1) msg2 "ignore restore app user_${appuser}:${appid}: already installed";;
2)
msg2 "restore app user_${appuser}:${appid}: already installed in other user"
adb_make-pkg-installed-for-user "${appid}" "${user}"
;;
esac
else
msg2 "restore app user_${appuser}:${appid}: ${host_appfile}"
if [ -n "${appinfo_options[is_system]}" ]; then
if [ -n "${backup_arg_default_install_path}" ]; then
appinfo_options[install_path]=
fi
if [[ "${appinfo_options[install_path]}" =~ ^/data/app ]]; then
# shadowed system app
if [ -n "${redirect_user}" ]; then
adb_install "${host_appfile}" "${user}"
else
adb_install "${host_appfile}"
fi
else
adb_install-sys "${host_appfile}" "${appinfo_options[install_path]}"
fi
fresh_installed=1
new_app_installed_in_prev_user=1
else
if adb_install "${host_appfile}" "${user}"; then
fresh_installed=1
new_app_installed_in_prev_user=1
else
error_apps+=("user_${appuser}:${appid}:type=app")
fi
fi
fi
run_appline_hook "restore" "post" "app" "user_${appuser}:${appid}"
fi
# restore app data
if [[ "${appline_options[type]}" =~ "data" ]]; then
run_appline_hook "restore" "pre" "data" "user_${appuser}:${appid}"
local device_backup_files=()
backup_get_backup_files "${appid}" "${user}" "${appinfo_options[android_api_level]}"
local backup_filenames=()
local device_timestamp=0
local f=
for f in "${device_backup_files[@]}"; do
local t=$(adb_get-file-timestamp "${f}")
if [ "${t}" -gt "${device_timestamp}" ]; then
device_timestamp="${t}"
fi
backup_filenames+=( $(basename "${f}") )
done
local ignore_data=
local archive_filename="${appid}.tar.gz"
if [ -n "${appinfo_options[is_encrypted]}" ]; then
archive_filename="${appid}.tar.gz.enc"
fi
local host_data_archive="${host_datadir}/${archive_filename}"
if [ -n "${appline_options[datafile]}" ]; then
host_data_archive="${appline_options[datafile]}"
host_datadir=$(dirname "${host_data_archive}")
fi
if [ ! -f "${host_data_archive}" ]; then
ignore_data=1
else
if ! backup_is_spec_app "${appid}"; then
local not_installed=
if ! adb_is-package-installed "${appid}"; then
not_installed=1
fi
# update application check and recheck if fresh installed just now
if [ -n "${not_installed}" -a -n "${fresh_installed}" ]; then
if [ -n "${appinfo_options[is_system]}" ]; then
# wait for seconds for system app
msg2 "wait for seconds for system application to be ready.."
sleep "${backup_arg_wait_sys_app_timeout}"
_adb_fetch_pkgs_cache
if adb_is-package-installed "${appid}"; then
not_installed=
else
not_installed=1
fi
else
# fresh install means just installed for normal app
not_installed=
fi
fi
if [ -n "${not_installed}" ]; then
ignore_data=2
fi
fi
# just ignore if timestamp not changed
if [ "${device_timestamp}" -eq "${appinfo_options[data_timestamp]}" ]; then
ignore_data=3
fi
if ! backup_check_app_data_compatibility "${appid}" "${appinfo_options[android_api_level]}" "$(adb_get-android-api-level)"; then
ignore_data=4
fi
fi
if [ -n "${ignore_data}" ]; then
case "${ignore_data}" in
1) msg2 "ignore restore data user_${appuser}:${appid}: ${host_data_archive} not found";;
2) warning "Ignore restore data ${appid}: application not installed"
error_apps+=("user_${appuser}:${appid}:type=data")
;;
3) msg2 "ignore restore data user_${appuser}:${appid}: ${device_backup_files[*]} is up to date";;
4) warning "Ignore restore data ${appid}: not compatibility with current device"
error_apps+=("user_${appuser}:${appid}:type=data")
;;
esac
else
(
# push data files to device
cd "${host_datadir}"
# decrypt archive
if [ -n "${appinfo_options[is_encrypted]}" ]; then
msg2 "decrypt archive: ${archive_filename} -> ${appid}.tar.gz"
if [ -z "${backup_arg_password}" ]; then
abort "${host_data_archive} is encrypted, need --passowrd argument"
fi
openssl enc -aes-256-cbc -d -k "${backup_arg_password}" -in "${archive_filename}" -out "${appid}.tar.gz"
# fix variables
archive_filename="${appid}.tar.gz"
host_data_archive="${host_datadir}/${archive_filename}"
fi
if [ x"${backup_opt_archive_side}" = x"device" ]; then
# push and extract archive file in device
local device_data_archive="${adb_opt_device_tmpdir}/${archive_filename}"
# clean up old data files
adb_rootshell "${adb_opt_busybox} rm '${device_data_archive}'"
adb_rootshell "cd '${adb_opt_device_tmpdir}' && ${adb_opt_busybox} rm -r ${backup_filenames[@]}"
msg2 "restore data user_${appuser}:${appid}: ${host_data_archive} -> ${device_data_archive}"
adb_push "${archive_filename}" "${device_data_archive}"
# extract archive in device
adb_rootshell "cd '${adb_opt_device_tmpdir}' && ${adb_opt_busybox} tar -xzf '${archive_filename}' 1>/dev/null"
for f in "${device_backup_files[@]}"; do
local filename=$(basename "${f}")
msg2 "restore data user_${appuser}:${appid}: ${adb_opt_device_tmpdir}/${filename} -> ${f}"
local fixed_device_path=$(backup_fix_restore_path_for_compatibility "${appid}" "${appinfo_options[android_api_level]}" "$(adb_get-android-api-level)" "${user}" "${f}")
if [ x"${f}" != x"${fixed_device_path}" ]; then
msg2 "fix path for compatibility: ${f} -> ${fixed_device_path}"
fi
adb_rootshell "cd '${adb_opt_device_tmpdir}' && if [ -e '${filename}' ];then
rm -r '${fixed_device_path}' 2>/dev/null
mv '${filename}' '${fixed_device_path}'
fi"
# touch to change timestamp
adb_set-file-timestamp "${fixed_device_path}" "${appinfo_options[data_timestamp]}"
done
# clean up old data files again
adb_rootshell "${adb_opt_busybox} rm '${device_data_archive}'"
adb_rootshell "cd '${adb_opt_device_tmpdir}' && ${adb_opt_busybox} rm -r ${backup_filenames[@]}"
else
# extract archive file in host and push
# clean up old data files
rm -rf "${backup_filenames[@]}"
# extract archive in host
tar -xzf "${archive_filename}" 1>/dev/null
for f in "${device_backup_files[@]}"; do
local filename=$(basename "${f}")
msg2 "restore data user_${appuser}:${appid}: ${host_datadir}/${filename} -> ${f}"
local fixed_device_path=$(backup_fix_restore_path_for_compatibility "${appid}" "${appinfo_options[android_api_level]}" "$(adb_get-android-api-level)" "${user}" "${f}")
if [ x"${f}" != x"${fixed_device_path}" ]; then
msg2 "fix path for compatibility: ${f} -> ${fixed_device_path}"
fi
if [ -e "${filename}" ]; then
if [ -f "${filename}" ] || ! is_empty_dir "${filename}"; then
adb_rootshell "${adb_opt_busybox} rm -r '${fixed_device_path}'"
adb_push "${filename}" "${fixed_device_path}" 1
elif is_empty_dir "${filename}"; then
# TODO: Check for empty dir for adb push will not
# work it. But in this case, should we keep the
# device data dir empty, too?
msg2 "ignore restore file: ${filename} is empty direcotory"
fi
else
msg2 "ignore restore file: ${filename} not found"
fi
# touch to change timestamp
adb_set-file-timestamp "${fixed_device_path}" "${appinfo_options[data_timestamp]}"
done
# clean up data files again
rm -rf "${backup_filenames[@]}"
fi
if [ -n "${appinfo_options[is_encrypted]}" ]; then
rm -f "${host_data_archive}"
fi
# fix application data file permission
msg2 "fix application data file permission: user_${appuser}:${appid}"
if backup_is_spec_app "${appid}"; then
backup_adb_fix-spec-datafile-permission "${appid}" "${user}"
msg2 "maybe a reboot is necessary for special app user_${appuser}:${appid}"
else
adb_fix-package-datadir-permission "${appid}" "${user}"
fi
)
fi
run_appline_hook "restore" "post" "data" "user_${appuser}:${appid}"
fi
}
function _cmd_usage_check {
echo "check [--fix] [--clean] [--copy-to dir] [--move-to dir] [--appdir dir] [dir|--profile file.sh]
check backup app and data in special directory or profile
--fix, fix incompatible issues if possible
--clean, remove apk files that lost reference, only works without --profile
--copy-to, copy the related app and data to another directory, only works with --profile
--move-to, move the related app and data to another directory, only works with --profile"
}
function cmd_check {
local dir="${1:-${PWD}}"
if [ -n "${backup_arg_profile}" ]; then
msg "Check backup app and data for profile: ${backup_arg_profile}"
if [ ! -f "${backup_arg_profile}" ]; then
abort "Profile file not found: ${backup_arg_profile}"
fi
local profile_dir= name= version= appdir= datadir= users=() system_apps=() user_apps=()
backup_prepare_args "${@}"
# fix old profile format
if compver_lt "${version}" "0.9.5"; then
if [ -n "${backup_arg_check_fix}" ]; then
msg2 "fix old style: profile format before 0.9.5"
# convert user_xxx_packages to user_xxx_apps
sed -i "s/user_\(.*\)_packages=/user_\1_apps=/" "${backup_arg_profile}"
local user=
for user in "${users[@]}"; do
local applines=()
local section=$(dump_section_headless "${backup_arg_profile}" "^user_${user}_apps=\\\\(" "^\\\\)$")
eval "applines=(
${section}
)"
if [ "${#applines[@]}" -eq 0 ]; then
continue
fi
local fixed_applines=$(
local IFS=$'\n'
local appline=
for appline in "${applines[@]}"; do
local appid= type= appline_options=
_backup_parse_appline_old "${appline}" "${user}"
if [ -n "${appline_options}" ]; then
appline_options=";${appline_options}"
fi
if [ x"${user}" = x"system" -o x"${user}" = x"0" ]; then
printf " '${appid}:type=${type}${appline_options}'\\\\n"
else
printf " 'user_${user}:${appid}:type=${type}${appline_options}'\\\\n"
fi
done
)
fixed_applines="${fixed_applines%\\n}"
replace_section "${backup_arg_profile}" "user_${user}_apps=\(" "^\)$" "user_${user}_apps=(\n${fixed_applines}\n)"
if [ x"${user}" = x"system" ]; then
sed -i "s/user_system_apps=/system_apps=/" "${backup_arg_profile}"
else
sed -i "s/user_${user}_apps=/user_apps+=/" "${backup_arg_profile}"
fi
done
sed -i "1i version='0.9.5'" "${backup_arg_profile}"
# reload profile
backup_prepare_args "${@}"
else
warning "Found old style: profile format before 0.9.5, you should use --fix argument or will report error later"
fi
fi
local appline=
for appline in "${system_apps[@]}" "${user_apps[@]}"; do
local appuser= appid=; unset appline_options; declare -A appline_options
backup_parse_appline "${appline}"
# source appinfo.sh
local user_datadir="${datadir}/user_${appuser}"
local host_datadir="${user_datadir}/${appid}"
local appinfo_file="${host_datadir}/${backup_appinfo_name}"
local appinfo_file_fallback="${host_datadir}/${backup_appinfo_name_fallback}"
unset appinfo_options; declare -A appinfo_options
local id= version_name= version_code= android_version= android_api_level= is_system= is_encrypted= install_path= data_timestamp=
local name= install_dir= # fallback vars
if backup_need_fallback_file appinfo_file appinfo_file_fallback; then
if [ -n "${backup_arg_check_fix}" ]; then
msg2 "fix old style: rename ${appinfo_file_fallback} to ${appinfo_file}"
mv -f "${appinfo_file_fallback}" "${appinfo_file}"
else
appinfo_file="${appinfo_file_fallback}"
fi
fi
if [ -f "${appinfo_file}" ]; then
source "${appinfo_file}"
else
msg2 "missing ${backup_appinfo_name}: ${appinfo_file}"
continue
fi
# fallback to old variables
if backup_need_fallback_var "${appinfo_file}" install_path install_dir; then
install_path="${install_dir}"
if [ -n "${backup_arg_check_fix}" ]; then
_backup_convert_appinfo_vars
backup_generate_appinfo_file "${appinfo_file}"
fi
fi
if backup_need_fallback_var "${appinfo_file}" id name; then
id="${name}"
if [ -n "${backup_arg_check_fix}" ]; then
_backup_convert_appinfo_vars
backup_generate_appinfo_file "${appinfo_file}"
fi
fi
# check app
if [[ "${appline_options[type]}" =~ "app" ]]; then
local host_appfile="${appdir}/${appid}_${version_code}.apk"
local host_appfile_fallback="${appdir}/${appid}-${version_name}-${version_code}.apk"
if backup_need_fallback_file host_appfile host_appfile_fallback; then
if [ -n "${backup_arg_check_fix}" ]; then
msg2 "fix old style: rename ${host_appfile_fallback} to ${host_appfile}"
mv -f "${host_appfile_fallback}" "${host_appfile}"
else
host_appfile="${host_appfile_fallback}"
fi
fi
if [ ! -f "${host_appfile}" ]; then
msg2 "missing app: ${host_appfile}"
else
_cmd_check_do_copy_or_move_file "$(dirname "${appdir}")" "${host_appfile}"
fi
fi
# check app data
if [[ "${appline_options[type]}" =~ "data" ]]; then
local archive_filename="${appid}.tar.gz"
if [ -n "${is_encrypted}" ]; then
archive_filename="${appid}.tar.gz.enc"
fi
local host_data_archive="${host_datadir}/${archive_filename}"
if [ ! -f "${host_data_archive}" ]; then
msg2 "missing data: ${host_data_archive}"
fi
fi
_cmd_check_do_copy_or_move_file "$(dirname $(dirname "${datadir}"))" "${host_datadir}"
done
_cmd_check_do_copy_or_move_file "$(dirname "${backup_arg_profile}")" "${backup_arg_profile}"
else
msg "Check backup app and data for directory: ${dir}"
local appdir=$(get_realpath "${backup_opt_appdir}" "${dir}")
IFS=$'\n'
# collect all apk files
local all_apps= appfile=
declare -A all_apps
for appfile in $(find "${dir}" -iname '*.apk'); do
all_apps["$(get_realpath ${appfile})"]=0
done
local appinfo_file=
for appinfo_file in $(find "${dir}" -iname "${backup_appinfo_name}" -o -iname "${backup_appinfo_name_fallback}"); do
if [[ "${appinfo_file}" =~ "${backup_appinfo_name_fallback}"$ ]]; then
local appinfo_new_file=$(dirname "${appinfo_file}")/"${backup_appinfo_name}"
if backup_need_fallback_file appinfo_new_file appinfo_file && [ -n "${backup_arg_check_fix}" ]; then
msg2 "fix old style: rename ${appinfo_file} to ${appinfo_new_file}"
mv -f "${appinfo_file}" "${appinfo_new_file}"
appinfo_file="${appinfo_new_file}"
fi
fi
unset appinfo_options; declare -A appinfo_options
local id= version_name= version_code= android_version= android_api_level= is_system= is_encrypted= install_path= data_timestamp=
local name= install_dir= # fallback vars
if [ -f "${appinfo_file}" ]; then
source "${appinfo_file}"
else
msg2 "missing ${backup_appinfo_name}: ${appinfo_file}"
continue
fi
# fallback to old variables
if backup_need_fallback_var "${appinfo_file}" install_path install_dir; then
install_path="${install_dir}"
if [ -n "${backup_arg_check_fix}" ]; then
_backup_convert_appinfo_vars
backup_generate_appinfo_file "${appinfo_file}"
fi
fi
if backup_need_fallback_var "${appinfo_file}" id name; then
id="${name}"
if [ -n "${backup_arg_check_fix}" ]; then
_backup_convert_appinfo_vars
backup_generate_appinfo_file "${appinfo_file}"
fi
fi
if backup_is_spec_app "${id}"; then
continue
fi
# check app
local host_appfile=$(get_realpath "${appdir}/${id}_${version_code}.apk")
local host_appfile_fallback=$(get_realpath "${appdir}/${id}-${version_name}-${version_code}.apk")
if backup_need_fallback_file host_appfile host_appfile_fallback; then
if [ -n "${backup_arg_check_fix}" ]; then
msg2 "fix old style: rename ${host_appfile_fallback} to ${host_appfile}"
mv -f "${host_appfile_fallback}" "${host_appfile}"
if [ -z "${all_apps[${host_appfile}]}" ]; then
all_apps["${host_appfile}"]=0
fi
unset all_apps["${host_appfile_fallback}"]
else
host_appfile="${host_appfile_fallback}"
fi
fi
if [ -z "${all_apps[${host_appfile}]}" ]; then
msg2 "missing app: ${host_appfile}"
else
all_apps["${host_appfile}"]=$((all_apps["${host_appfile}"] + 1))
fi
done
# show all apk files that lost reference
for f in "${!all_apps[@]}"; do
if [ "${all_apps[${f}]}" -eq 0 ]; then
msg2 "lost reference: ${f}"
if [ -n "${backup_arg_check_clean}" ]; then
msg2 "clean file: ${f}"
rm -f "${f}"
fi
fi
done
fi
}
# arg1: src_base_dir, arg2: src_path
function _cmd_check_do_copy_or_move_file {
local opt_type=
if [ -n "${backup_arg_check_copy_to}" ]; then
opt_type='copy'
elif [ -n "${backup_arg_check_move_to}" ]; then
opt_type='move'
fi
if [ -z "${opt_type}" ]; then
return
fi
if [ -n "${backup_opt_verbose}" ]; then
msg2 "_cmd_check_do_copy_or_move_file: ${1} ${2}"
fi
local src_base_dir="${1}"
local src_path="${2}"
local src_sub_path="${2##${1}}"
src_sub_path="${src_sub_path#/}" # remove prefix '/' if exists
src_sub_path=$(dirname "${src_sub_path}")
if [ x"${src_sub_path}" = x'.' ]; then
src_sub_path=
fi
local dest_base_dir=
local dest_dir=
if [ x"${opt_type}" = x'copy' ]; then
dest_base_dir="${backup_arg_check_copy_to}"
dest_dir="${dest_base_dir}/${src_sub_path}"
msg2 "check: ${opt_type} ${src_path} -> ${dest_dir}"
mkdir -p "${dest_dir}"
cp -rf "${src_path}" "${dest_dir}"
else
dest_base_dir="${backup_arg_check_move_to}"
dest_dir="${dest_base_dir}/${src_sub_path}"
msg2 "check: ${opt_type} ${src_path} -> ${dest_dir}"
mkdir -p "${dest_dir}"
mv -f "${src_path}" "${dest_dir}"
rmdir --ignore-fail-on-non-empty -p "${src_base_dir}/${src_sub_path}"
fi
}
###*** Dispatch backup arguments
backup_arg_profile=
backup_arg_name=
backup_arg_options=
backup_arg_filter=
backup_arg_password=
backup_arg_3rd_app=
backup_arg_3rd_data=
backup_arg_sys_app=
backup_arg_sys_data=
backup_arg_freq_app=
backup_arg_freq_data=
backup_arg_spec_data=
backup_arg_wait_sys_app_timeout=3
backup_arg_default_install_path=
backup_arg_check_fix=
backup_arg_check_clean=
backup_arg_check_copy_to=
backup_arg_check_move_to=
declare -a backup_arg_profile_types
declare -a backup_arg_users
declare -a backup_left_args=()
function backup_dispatch_args {
while [ ${#} -gt 0 ]; do
case "${1}" in
--profile-type) backup_arg_profile_types+=("${2}"); shift;shift;;
--3rd-app) backup_arg_3rd_app=1; shift;;
--3rd-data) backup_arg_3rd_data=1; shift;;
--sys-app) backup_arg_sys_app=1; shift;;
--sys-data) backup_arg_sys_data=1; shift;;
--freq-app) backup_arg_freq_app=1; shift;;
--freq-data) backup_arg_freq_data=1; shift;;
--spec-data) backup_arg_spec_data=1; shift;;
-p|--profile) backup_arg_profile="${2}"; shift;shift;;
--name) backup_arg_name="${2}"; shift;shift;;
-u|--user)
if [ x"${2}" = x'cur' -o x"${2}" = x'current' ]; then
local cur=$(adb_get-current-user)
if ! contains "${cur}" "${backup_arg_users[@]}"; then
backup_arg_users+=("${cur}")
fi
elif [ x"${2}" = x'sys' ]; then
if ! contains "system" "${backup_arg_users[@]}"; then
backup_arg_users+=("system")
fi
elif [ x"${2}" = x'all' ]; then
local u=
for u in $(adb_list-users | awk '{print $1}'); do
if ! contains "${u}" "${backup_arg_users[@]}"; then
backup_arg_users+=("${u}")
fi
done
else
if ! contains "${2}" "${backup_arg_users[@]}"; then
backup_arg_users+=("${2}")
fi
fi
shift;shift;;
--appdir) backup_opt_appdir="${2}"; shift;shift;;
--archive-side) backup_opt_archive_side="${2}"; shift;shift;;
--password) backup_arg_password="${2}"; shift;shift;;
--options) backup_arg_options="${2}"; shift;shift;;
--filter) backup_arg_filter="${2}"; shift;shift;;
--wait-sys-app) backup_arg_wait_sys_app_timeout="${2}"; shift;shift;;
--default-install-path) backup_arg_default_install_path=1; shift;;
--fix) backup_arg_check_fix=1; shift;;
--clean) backup_arg_check_clean=1; shift;;
--copy-to) backup_arg_check_copy_to="${2}"; shift;shift;;
--move-to) backup_arg_check_move_to="${2}"; shift;shift;;
*) backup_left_args+=("${1}"); shift;;
esac
done
# fix password
if [ x"${backup_arg_password}" = x"-" ]; then
check_depends_optional
printf "password: "
backup_arg_password=$(read_password)
echo
fi
}
###** Include deploy_cmds.sh
# Depends: utils.sh, adb_utils.sh, backup_cmds.sh, fdroid_cmds.sh
###*** Deploy commands
function _cmd_usage_deploy {
echo "deploy [--options options] [--filter filter] <--profile file.sh|APPLINE..>
deploy applications from profile"
}
function cmd_deploy {
declare -A deploy_pre_app_hooks
declare -A deploy_post_app_hooks
declare -A deploy_file_funcs
local error_apps=()
local profile_dir= name= version= appdir= datadir= users=() system_apps=() user_apps=()
backup_prepare_args "${@}"
run_cmd_hook "deploy" "pre"
# ensure users exists
local user= user_name=
for user in "${users[@]}"; do
declare -n user_name="user_${user}_name"
if [ x"${user}" != x"system" ]; then
adb_ensure-user-exists "${user}" "${user_name}"
fi
done
local new_app_installed_in_prev_user=
for user in "${users[@]}"; do
local user_datadir="${datadir}/user_${user}"
msg "Install applications for user ${user}"
# if new application install in previous user, just update package cache
if [ -n "${new_app_installed_in_prev_user}" ]; then
_adb_fetch_pkgs_cache
new_app_installed_in_prev_user=
fi
local appline=
for appline in "${system_apps[@]}" "${user_apps[@]}"; do
local appuser= appid=; unset appline_options; declare -A appline_options
backup_parse_appline "${appline}"
if [ x"${appuser}" = x"${user}" ]; then
if [[ "${appline_options[type]}" =~ "app" ]]; then
# deploy app
_cmd_do_deploy
fi
if [[ "${appline_options[type]}" =~ "data" ]] && [ -n "${datadir}" -o -n "${appline_options[datadir]}" -o -n "${appline_options[datafile]}" ]; then
# restore app data
if [[ "${appline_options[type]}" =~ "app" ]]; then
_adb_fetch_pkgs_cache
fi
appline_options[type]="data"
_cmd_do_restore
fi
fi
done
done
run_file_funcs "deploy"
run_cmd_hook "deploy" "post"
if [ ${#error_apps[@]} -ne 0 ]; then
warning "Install applications failed: ${error_apps[*]}"
return 1
else
return 0
fi
}
function _cmd_do_deploy {
if backup_is_spec_app "${appid}"; then
return
fi
if [ x"${appuser}" != x'system' ] && backup_is_app_in_system_user "${appid}"; then
# this is a system application, don't install here
msg2 "ignore app ${appid}: should also exists in \$system_apps"
return
fi
# install app
run_appline_hook "deploy" "pre" "app" "user_${appuser}:${appid}"
if ! (
local fdroid_arg_install_sys= fdroid_arg_install_path=
# overwrite variables
if [ -n "${appline_options[repo]}" ]; then
local repo_url="${fdroid_repos[${appline_options[repo]}]}"
fdroid_repos=(["${appline_options[repo]}"]="${repo_url}")
fi
if [ x"${appuser}" = x"system" ]; then
fdroid_arg_install_sys=1
fdroid_arg_users=()
else
fdroid_arg_users=("${appuser}")
fi
if [ -n "${appline_options[install_path]}" ]; then
fdroid_arg_install_path="${appline_options[install_path]}"
fi
if [ -n "${appline_options[allow_unstable]}" ]; then
fdroid_opt_allow_unstable="${appline_options[allow_unstable]}"
fi
local install_target="${appid}=${appline_options[version_code]}"
if [ -n "${appline_options[appfile]}" ]; then
install_target="${appline_options[appfile]}"
fi
if [ -n "${fdroid_opt_verbose}" ]; then
local repo_names="${!fdroid_repos[@]}"
msg2 "cmd_install ${install_target} repos=(${repo_names}) install_sys=${fdroid_arg_install_sys} install_path=${fdroid_arg_install_path}"
fi
local backup_arg_profile=
cmd_install "${install_target}"
); then
error_apps+=("user_${appuser}:${appid}")
else
new_app_installed_in_prev_user=1
fi
run_appline_hook "deploy" "post" "app" "user_${appuser}:${appid}"
}
function _cmd_usage_cleanup {
echo "cleanup [--options options] [--filter filter] [--disable] [--enable] <--profile file.sh|APPLINE..>
cleanup applications from profile
--disable, disable applications instead of uninstalling them
--enable, enable applications instead of uninstalling them"
}
function cmd_cleanup {
declare -A cleanup_pre_app_hooks
declare -A cleanup_post_app_hooks
declare -A cleanup_file_funcs
local error_apps=()
local profile_dir= name= version= appdir= datadir= users=() system_apps=() user_apps=()
backup_prepare_args "${@}"
run_cmd_hook "cleanup" "pre"
msg "Uninstall applications"
local appline=
for appline in "${system_apps[@]}" "${user_apps[@]}"; do
local appuser= appid=; unset appline_options; declare -A appline_options
backup_parse_appline "${appline}"
if contains "${appuser}" "${users[@]}"; then
_cmd_do_cleanup
fi
done
run_file_funcs "cleanup"
run_cmd_hook "cleanup" "post"
if [ ${#error_apps[@]} -ne 0 ]; then
warning "Uninstall applications failed: ${error_apps[*]}"
return 1
else
return 0
fi
}
function _cmd_do_cleanup {
if [ x"${appuser}" != x'system' ] && ! adb_is-user-exists "${appuser}"; then
msg2 "ignore app user_${appuser}:${appid}: user ${appuser} not exists"
return
fi
if backup_is_spec_app "${appid}"; then
return
fi
if [ x"${appuser}" != x'system' ] && backup_is_app_in_system_user "${appid}"; then
# this is a system application, don't uninstall here
msg2 "ignore app ${appid}: should also exists in \$system_apps"
return
fi
# uninstall app
run_appline_hook "cleanup" "pre" "app" "user_${appuser}:${appid}"
if ! (
local user= fdroid_arg_users=()
if [ x"${appuser}" = x"system" ]; then
user=
fdroid_arg_users=()
else
user="${appuser}"
fdroid_arg_users=("${appuser}")
fi
local backup_arg_profile=
if [ x"${appline_options[type]}" = x"data" ]; then
# cleanup app data only
adb_clear-package "${appid}" "${user}"
else
# cleanup app
if [ -n "${deploy_arg_disable}" ]; then
adb_disable "${appid}" "${user}"
elif [ -n "${deploy_arg_enable}" ]; then
adb_enable "${appid}" "${user}"
else
cmd_uninstall "${appid}"
fi
fi
); then
error_apps+=("user_${appuser}:${appid}")
fi
run_appline_hook "cleanup" "post" "app" "user_${appuser}:${appid}"
}
###*** Dispatch deploy arguments
deploy_arg_disable=
deploy_arg_enable=
declare -a deloy_left_args=()
function deploy_dispatch_args {
while [ ${#} -gt 0 ]; do
case "${1}" in
--disable) deploy_arg_disable=1; shift;;
--enable) deploy_arg_enable=1; shift;;
*) deploy_left_args+=("${1}"); shift;;
esac
done
}
###** Include fdroid_cmds.sh
# Depends: utils.sh, adb_utils.sh(optional), backup_cmds.sh
declare -A fdroid_depends=(
[basename]=''
[grep]=''
[tr]=''
[awk]=''
[sha256sum]=''
[curl]=''
[xmlstarlet]=''
)
# Optional variables, could override in host script or config file
fdroid_opt_cache_dir="${HOME}/.cache/${app_name}"
fdroid_opt_allow_unstable=
fdroid_opt_download_cmd="curl -fLC - --retry 3 --retry-delay 5 -O '%s'"
# or set fdroid_opt_download_cmd in config file to use wget, aria2c
# fdroid_opt_download_cmd="wget -c '%s'"
# fdroid_opt_download_cmd="aria2c -c '%s'"
declare -A fdroid_repos=(
["fdroid"]="https://f-droid.org/repo"
)
###*** F-Droid help functions
# arg1: index-file
function fdroid_check_index_file {
local index_xml="${1}"
if [ ! -f "${index_xml}" ]; then
warning "${index_xml} not exists, please run 'update' first"
return 1
fi
if ! xmlstarlet validate -q "${index_xml}"; then
warning "${index_xml} is invalide, please run 'update' again to fix"
return 1
fi
return 0
}
# Get suggest application version code
# opts: fdroid_opt_allow_unstable
# arg1: appid, arg2: apilevel, arg3: abilist
function fdroid_get_app_suggest_version_code {
local appid="${1}"
local apilevel="${2}"
if [ -z "${apilevel}" ] && adb_is-online; then
apilevel=$(adb_get-android-api-level)
fi
local abilist="${3}"
if [ -z "${abilist}" ] && adb_is-online; then
abilist=$(adb_get-android-abi-list)
fi
local versions=
if [ -n "${fdroid_opt_allow_unstable}" ]; then
versions=$(fdroid_get_app_versions "${1}")
else
versions=$(fdroid_get_app_versions "${1}" | \
grep -i -v "version=.*beta.\?';" | \
grep -i -v "version=.*alpha.\?';" | \
grep -i -v "version=.*pre.\?';" | \
grep -i -v "version=.*rc.\?';" | \
grep -i -v "version=.*[-_]git';")
fi
local IFS=$'\n'
local suggest_version= suggest_version_code=
local version= version_code= sdkver= native_code=
for v in ${versions}; do
if [ -z "${v}" ]; then
continue
fi
eval "${v}"
# check sdk api level
if [ -n "${apilevel}" -a -n "${sdkver}" ]; then
if [ "${apilevel}" -lt "${sdkver}" ]; then
continue
fi
fi
# check cpu abi list
if [ -n "${abilist}" -a -n "${native_code}" ]; then
local arch
for arch in $(echo "${native_code}" | tr ',' '\n'); do
if contains "${arch}" ${abilist}; then
if [ -z "${suggest_version}" ] || [ "${suggest_version_code}" -lt "${version_code}" ]; then
suggest_version_code="${version_code}"
suggest_version="${version}"
fi
fi
done
else
if [ -z "${suggest_version}" ] || [ "${suggest_version_code}" -lt "${version_code}" ]; then
suggest_version_code="${version_code}"
suggest_version="${version}"
fi
fi
done
echo "${suggest_version_code}"
}
# output-format: version=xxx;version_code=xxx;sdkver=xxx;native_code=xxx
function fdroid_get_app_versions {
local appid="${1}"
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
xmlstarlet select -t -m "//fdroid/application[@id=\"${appid}\"]/package/version" --nl -o "version='" -v . -o "';version_code=" -m '../versioncode' -v . -o ';sdkver=' -m '../sdkver' -v . -o ';native_code=' -m '../nativecode' -v . "${index_xml}" || true
done
}
function fdroid_get_app_version {
local appid="${1}"
local version_code="${2}"
local version=
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local cmd="xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]/package/versioncode[.=\"${version_code}\"]/../version' -v . --nl ${index_xml}"
version=$(eval "${cmd}" | head -n 1)
if [ -n "${version}" ]; then
break
fi
done
echo "${version}"
}
function fdroid_get_app_native_code {
local appid="${1}"
local version="${2}"
local native_code=
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local cmd="xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]/package/version[.=\"${version}\"]/../nativecode' -v . --nl ${index_xml}"
native_code=$(eval "${cmd}" | head -n 1)
if [ -n "${native_code}" ]; then
break
fi
done
echo "${native_code}" | tr ',' '\n'
}
# arg1: appid, arg2: version_code
function fdroid_get_app_repo {
local appid="${1}"
local version_code="${2}"
local apkname
local repo
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local cmd="xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]/package/versioncode[.=\"${version_code}\"]/../apkname' -v . --nl ${index_xml}"
apkname=$(eval "${cmd}" | head -n 1)
if [ -n "${apkname}" ]; then
repo="${r}"
break
fi
done
echo "${repo}"
}
# arg1: appid, arg2: version_code
function fdroid_get_app_url {
local appid="${1}"
local version_code="${2}"
local apkname
local url
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local cmd="xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]/package/versioncode[.=\"${version_code}\"]/../apkname' -v . --nl ${index_xml}"
apkname=$(eval "${cmd}" | head -n 1)
if [ -n "${apkname}" ]; then
url="${fdroid_repos[${r}]}/${apkname}"
break
fi
done
echo "${url}"
}
# Return format: sha256:37203e6764fc0525cdf3b2555884dfbece45585ddd072022ea563563fe2149be
function fdroid_get_app_hash {
local appid="${1}"
local version_code="${2}"
local hash
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local cmd="xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]/package/versioncode[.=\"${version_code}\"]/../hash' -v '@type' -o ':' -v '.' --nl ${index_xml}"
hash=$(eval "${cmd}" | head -n 1)
if [ -n "${hash}" ]; then
break
fi
done
echo "${hash}"
}
function fdroid_get_app_category {
local appid="${1}"
local category
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local cmd="xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]/category' -v '.' --nl ${index_xml}"
category=$(eval "${cmd}" | head -n 1)
if [ -n "${category}" ]; then
break
fi
done
echo "${category}"
}
function fdroid_get_app_display_name {
local appid="${1}"
local display_name=
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local cmd="xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]/name' -v '.' --nl ${index_xml}"
display_name=$(eval "${cmd}" | head -n 1)
if [ -n "${display_name}" ]; then
break
fi
done
echo "${display_name}"
}
# arg1: appid, arg2: [version_code]
function fdroid_is_app_exists {
local appid="${1}"
local version_code="${2}"
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local cmd=
if [ -n "${version_code}" ]; then
cmd="xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]/package/versioncode[.=\"${version_code}\"]' -v . ${index_xml}"
else
cmd="xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]/package/versioncode' -v . ${index_xml}"
fi
if eval "${cmd} &>/dev/null"; then
return 0
fi
done
return 1
}
###*** F-Droid commands
function _cmd_usage_update {
echo "update [-r|--repo name]
download fdroid repo index.xml file to cache direcotry"
}
function cmd_update {
msg "Update F-Droid repository index.xml files.."
for r in "${!fdroid_repos[@]}"; do
local url="${fdroid_repos[${r}]}/index.xml"
local repodir="${fdroid_opt_cache_dir}/repo/${r}"
local cmd=$(printf "${fdroid_opt_download_cmd}" "${url}")
msg2 "${r}: download ${url} to ${repodir}"
mkdir -p "${repodir}"
(
cd "${repodir}"
if [ -f "index.xml" ]; then
mv -f index.xml index.xml.old
fi
if eval "${cmd}"; then
if [ -f "index.xml.old" ]; then
rm -f index.xml.old
fi
else
warning "${r}: download ${url} failed"
if [ -f "index.xml.old" ]; then
mv -f index.xml.old index.xml
fi
fi
)
done
}
function _cmd_usage_list {
echo "list [-r|--repo name] [--cat] [--desc] [--ver] [--sug] [--only-ver] [--allow-unstable] [--appid] [keyword|APP..]
list matched applications
--appid, use application id instead keyword
the keyword support awk regexp syntax
--cat, show and match for category
--desc, show and match for application description
--ver, show all application version details
--sug, show suggest version
only works for keyword or APP specialized
--only-ver, show application versions only"
}
function cmd_list {
if [ ${#} -eq 0 ]; then
_fdroid_show_app_info
return
fi
local matched_apps=()
local appid=
if [ -n "${fdroid_arg_list_appid}" ]; then
matched_apps=("${@}")
else
for appid in $(_fdroid_list_matched_apps "${1}" | sort | uniq); do
matched_apps+=("${appid}")
done
fi
for appid in "${matched_apps[@]}"; do
_fdroid_show_app_info "${appid}" | xmlstarlet unesc
done
}
# arg1: keyword
function _fdroid_list_matched_apps {
local keyword="${1}"
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local cmd="xmlstarlet select -t -m '//fdroid/application' -o '====appid:' -v '@id' --nl $(_fdroid_build_show_app_xml_cmd) ${index_xml}"
eval "${cmd}" | awk -v keyword="${keyword}" 'BEGIN{IGNORECASE=1;RS="===="} $0 ~ keyword{print}' | grep 'appid:' | awk -F: '{print $2}'
done
}
# arg1: appid
function _fdroid_show_app_info {
local appid="${1}"
local found=
for r in "${!fdroid_repos[@]}"; do
local index_xml="${fdroid_opt_cache_dir}/repo/${r}/index.xml"
if ! fdroid_check_index_file "${index_xml}"; then
continue
fi
local url_prefix="${fdroid_repos[${r}]}/"
if [ -z "${appid}" ]; then
eval "xmlstarlet select -t -m '//fdroid/application' $(_fdroid_build_show_app_xml_cmd "${url_prefix}") ${index_xml}" || true
found=1
else
if eval "xmlstarlet select -t -m '//fdroid/application[@id=\"${appid}\"]' $(_fdroid_build_show_app_xml_cmd "${url_prefix}") ${index_xml}"; then
if [ -n "${fdroid_arg_list_sug}" ]; then
local suggest_version=
local suggest_version_code=$(fdroid_get_app_suggest_version_code "${appid}")
if [ -z "${suggest_version_code}" ]; then
suggest_version="<none>"
suggest_version_code="<none>"
else
suggest_version=$(fdroid_get_app_version "${appid}" "${suggest_version_code}")
fi
echo " ${BOLD}suggest version: ${suggest_version} (${suggest_version_code})${ALL_OFF}"
fi
found=1
fi
fi
done
if [ -z "${found}" ]; then
warning "Application ${appid} not found in repo"
fi
}
# arg1: [url_prefix]
function _fdroid_build_show_app_xml_cmd {
if [ -n "${fdroid_arg_list_only_ver}" ]; then
echo "-m './package/versioncode' -v '../../@id' -o '=' -v '.' -o ' ' -v '../../name' -o ' ' -v '../version' --nl"
return
fi
local url_prefix="${1}"
local cmd="-o '${YELLOW}' -v '@id' -o ' - ' -m './name' -v . -o '${ALL_OFF}' --nl"
if [ -n "${fdroid_arg_list_cat}" ]; then
cmd="${cmd} -o ' ${BOLD}category:${ALL_OFF} ' -m '../category' -v . --nl"
fi
if [ -n "${fdroid_arg_list_desc}" ]; then
cmd="${cmd} -o ' ${BOLD}description:${ALL_OFF} ' -m '../desc' -v . --nl"
fi
if [ -n "${fdroid_arg_list_ver}" ]; then
cmd="${cmd} -o '${BOLD} versions:${ALL_OFF}' --nl -m '../package/version' -o ' ${BOLD}' -v . -o '${ALL_OFF}: date=' -m '../added' -v . -o ' versioncode=' -m '../versioncode' -v . -o ' sdkver=' --if '../sdkver' -v '../sdkver' --break -o ' nativecode=' --if '../nativecode' -v '../nativecode' --break -o ' url=${url_prefix}' -m '../apkname' -v . --nl"
fi
echo "${cmd}"
}
function _cmd_usage_download {
echo "download [-r|--repo name] [--allow-unstable] [--force-download] <VERAPP..>
download applications
if already exists, check the hash
--force-download, always download application even through file exists"
}
function cmd_download {
local error_apps=()
for verapp in "${@}"; do
local appid="$(echo ${verapp} | awk -F= '{print $1}')"
local version="$(echo ${verapp} | awk -F= '{print $2}')"
if [ -z "${version}" ]; then
version=$(fdroid_get_app_suggest_version_code "${appid}")
if [ -z "${version}" ]; then
warning "Ignore ${appid}: suggest version not found"
error_apps+=("${appid}")
continue
fi
fi
if ! fdroid_is_app_exists "${appid}" "${version}"; then
warning "Ignore ${appid}=${version}: not found in repo"
error_apps+=("${appid}")
continue
fi
local url="$(fdroid_get_app_url "${appid}" "${version}")"
local repo="$(fdroid_get_app_repo "${appid}" "${version}")"
local appdir="${fdroid_opt_cache_dir}/app/${repo}"
local cmd="$(printf "${fdroid_opt_download_cmd}" "${url}")"
if [ x"${arg_cmd}" = x"download" ]; then
msg "Download app ${appid}=${version} ${url}"
else
msg2 "download app ${appid}=${version} ${url}"
fi
local appfile="${appdir}/$(basename ${url})"
local hash="$(fdroid_get_app_hash "${appid}" "${version}")"
local need_download=
if [ -f "${appfile}" ] && ! check_file_hash "${appfile}" "${hash}"; then
need_download=1
fi
if [ ! -f "${appfile}" -o -n "${fdroid_arg_force_download}" ]; then
need_download=1
fi
if [ -n "${need_download}" ]; then
mkdir -p "${appdir}"
(cd "${appdir}"; eval "${cmd}")
fi
if ! check_file_hash "${appfile}" "${hash}"; then
error_apps+=("${appid}")
fi
done
if [ ${#error_apps[@]} -ne 0 ]; then
warning "Download applications failed: ${error_apps[*]}"
return 1
else
return 0
fi
}
function check_file_hash {
local file="${1}"
local hash_type="$(echo ${2} | awk -F: '{print $1}')"
local hash_value="$(echo ${2} | awk -F: '{print $2}')"
if [ x"${hash_type}" = x"sha256" ]; then
local file_hash="$(sha256sum ${file} | awk '{print $1}')"
if [ x"${file_hash}" = x"${hash_value}" ]; then
return 0
else
warning "File hash incorrect: ${file} ${hash_type} is ${file_hash}, prefer is ${hash_value}"
return 1
fi
fi
warning "Unknown hash type: ${2}"
return 1
}
function _cmd_usage_install {
echo "install [-r|--repo name] [-u|--user id] [--allow-unstable] [--allow-downgrade] [--install-path dir] <VERAPP|APKFILE..>
download and install applications
--install-path, install system application to special directory [default: '/system/app']
--allow-downgrade, allow downgrade application"
}
function cmd_install {
local error_apps=()
for verapp in "${@}"; do
local appfile= installed_sys= installed_3rd= appid=
if [ -f "${verapp}" ]; then
appfile="${verapp}"
appid="${appfile}" # just set appid a value for error message
else
appid="$(echo ${verapp} | awk -F= '{print $1}')"
local version="$(echo ${verapp} | awk -F= '{print $2}')"
if [ -z "${version}" ]; then
version=$(fdroid_get_app_suggest_version_code "${appid}")
if [ -z "${version}" ]; then
warning "Ignore ${appid}: suggest version not found"
error_apps+=("${appid}")
continue
fi
fi
if [ x"${arg_cmd}" = x"install" ]; then
msg "Install app ${appid}=${version}"
else
msg2 "install app ${appid}=${version}"
fi
# check if already installed
if adb_is-sys-package "${appid}"; then
local installed_version=$(adb_get-package-version-code "${appid}")
if [ x"${installed_version}" = x"${version}" ]; then
installed_sys=1
fi
fi
if adb_is-3rd-package "${appid}"; then
local installed_version=$(adb_get-package-version-code "${appid}")
if [ x"${installed_version}" = x"${version}" ]; then
installed_3rd=1
fi
fi
# check if application exists
if [ -z "${installed_sys}" -a -z "${installed_3rd}" ]; then
if ! fdroid_is_app_exists "${appid}" "${version}"; then
warning "Ignore ${appid}=${version}: not found in repo"
error_apps+=("${appid}")
continue
fi
fi
# download application
if ! cmd_download "${verapp}"; then
error_apps+=("${appid}")
continue
fi
local repo="$(fdroid_get_app_repo "${appid}" "${version}")"
local url="$(fdroid_get_app_url "${appid}" "${version}")"
local appdir="${fdroid_opt_cache_dir}/app/${repo}"
appfile="${appdir}/$(basename ${url})"
fi
if contains "system" "${fdroid_arg_users[@]}"; then
msg2 "install ${appid} as system app"
if [ -n "${installed_sys}" ]; then
msg2 "ignore app ${appid}: already installed"
else
adb_check-root
if ! adb_install-sys "${appfile}" "${fdroid_arg_install_path}"; then
error_apps+=("${appid}")
fi
fi
fi
if [ ${#fdroid_arg_users[@]} -eq 0 ] || adb_is-only-one-user; then
if [ -n "${installed_3rd}" ]; then
msg2 "ignore app ${appid}: already installed"
continue
else
msg2 "install app ${appid}"
if ! adb_install "${appfile}"; then
error_apps+=("${appid}")
fi
fi
else
local user=
for user in "${fdroid_arg_users[@]}"; do
if [ x"${user}" = x"system" ]; then
continue
fi
if ! adb_is-user-exists "${user}"; then
msg2 "ignore user ${user}: user not exists"
fi
if [ -n "${installed_3rd}" ] && adb_is-3rd-package "${appid}" "${user}"; then
msg2 "ignore app ${appid}: already installed for user ${user}"
else
msg2 "install app ${appid} for user ${user}"
if ! adb_install "${appfile}" "${user}"; then
error_apps+=("${appid}")
fi
fi
done
fi
done
if [ ${#error_apps[@]} -ne 0 ]; then
if [ x"${arg_cmd}" = x"install" ]; then
warning "Install applications failed: ${error_apps[*]}"
else
warning "Install app failed: ${error_apps[*]}"
fi
return 1
else
return 0
fi
}
function _cmd_usage_uninstall {
echo "uninstall [-u|--user id] [--keep-data] <DEVICEAPP..>
uninstall applications
--keep-data, keep the data and cache directories after removal"
}
function cmd_uninstall {
local error_apps=()
local appid=
for appid in "${@}"; do
if [ x"${arg_cmd}" = x"uninstall" ]; then
msg "Uninstall app ${appid}"
else
msg2 "uninstall app ${appid}"
fi
if ! adb_is-package-installed "${appid}"; then
msg2 "ignore app ${appid}: not installed"
continue
fi
if adb_is-sys-package "${appid}"; then
msg2 "uninstall system app ${appid}"
local device_appfile=$(adb_get-package-path "${appid}")
if [[ "${device_appfile}" =~ ^/data/app ]]; then
# shadowed system app
if ! adb_uninstall "${appid}"; then
error_apps+=("${appid}")
fi
else
adb_check-root
if ! adb_uninstall-sys "${appid}"; then
error_apps+=("${appid}")
fi
fi
continue
fi
if [ ${#fdroid_arg_users[@]} -eq 0 ] || adb_is-only-one-user; then
msg2 "uninstall app ${appid}"
if ! adb_uninstall "${appid}"; then
error_apps+=("${appid}")
fi
else
local user=
for user in "${fdroid_arg_users[@]}"; do
if ! adb_is-user-exists "${user}"; then
msg2 "ignore user ${user}: user not exists"
fi
if ! adb_is-package-installed "${appid}" "${user}"; then
msg2 "ignore app ${appid}: not installed for user ${user}"
continue
fi
msg2 "uninstall app ${appid} for user ${user}"
if ! adb_uninstall "${appid}" "${user}"; then
error_apps+=("${appid}")
fi
done
fi
done
if [ ${#error_apps[@]} -ne 0 ]; then
if [ x"${arg_cmd}" = x"uninstall" ]; then
warning "Uninstall applications failed: ${error_apps[*]}"
else
warning "uninstall app failed: ${error_apps[*]}"
fi
return 1
else
return 0
fi
}
function _cmd_usage_upgrade {
echo "upgrade [-r|--repo name] [-u|--user id] [--allow-unstable] [-l|--list] [DEVICEAPP..]
upgrade applications
if no application id specialized, will upgrade all applications
-l, --list, only list all upgradeable applications"
}
function cmd_upgrade {
local users=
if [ ${#fdroid_arg_users[@]} -gt 0 ]; then
users="${fdroid_arg_users[@]}"
else
users="$(adb_list-users | awk '{print $1}')"
fi
local apps=()
local error_apps=()
for user in ${users}; do
if [ ${#} -eq 0 ]; then
if [ x"${user}" = x"system" ]; then
eval "apps=( $(adb_list-sys-packages) )"
else
eval "apps=( $(adb_list-3rd-packages ${user}) )"
fi
else
apps=("${@}")
fi
if [ x"${user}" = x"system" ]; then
msg "Upgrade system applications"
elif adb_is-only-one-user; then
msg "Upgrade applications"
else
msg "Upgrade applications for user ${user}"
fi
local appid=
for appid in "${apps[@]}"; do
if ([ x"${user}" = x"system" ] && adb_is-sys-package "${appid}") || ([ x"${user}" != x"system" ] && adb_is-3rd-package "${appid}" "${user}"); then
local suggest_version_code=$(fdroid_get_app_suggest_version_code "${appid}")
if [ -n "${suggest_version_code}" ]; then
local display_name=$(fdroid_get_app_display_name "${appid}")
local suggest_version=$(fdroid_get_app_version "${appid}" "${suggest_version_code}")
local installed_version=$(adb_get-package-version-name "${appid}")
local installed_version_code=$(adb_get-package-version-code "${appid}")
if [ "${suggest_version_code}" -gt "${installed_version_code}" ]; then
msg2 "${appid}(${display_name}): ${installed_version}(${installed_version_code}) -> ${YELLOW}${suggest_version}(${suggest_version_code})${ALL_OFF}"
if [ -z "${fdroid_arg_upgrade_list}" ]; then
local fdroid_arg_users=("${user}")
if ! cmd_install "${appid}=${suggest_version_code}"; then
error_apps+=("${appid}")
fi
fi
elif [ -n "${fdroid_opt_verbose}" ]; then
msg2 "ignore app ${appid}(${display_name}): up to date"
fi
elif [ -n "${fdroid_opt_verbose}" ]; then
if fdroid_is_app_exists "${appid}"; then
msg2 "ignore app ${appid}: suggest version not found"
else
msg2 "ignore app ${appid}: not found in repo"
fi
fi
else
if [ x"${user}" = x"system" ]; then
msg2 "ignore system app ${appid}: not installed"
elif adb_is-only-one-user; then
msg2 "ignore app ${appid}: not installed"
else
msg2 "ignore app ${appid}: not installed for user ${user}"
fi
fi
done
done
if [ ${#error_apps[@]} -ne 0 ]; then
warning "Upgrade applications failed: ${error_apps[*]}"
return 1
else
return 0
fi
}
###*** Dispatch F-Droid arguments
fdroid_arg_list_cat=
fdroid_arg_list_desc=
fdroid_arg_list_ver=
fdroid_arg_list_only_ver=
fdroid_arg_list_sug=
fdroid_arg_list_appid=
fdroid_arg_force_download=
fdroid_arg_install_path=
fdroid_arg_upgrade_list=
declare -a fdroid_arg_users
declare -a fdroid_arg_repos
declare -a fdroid_left_args=()
function fdroid_dispatch_args {
while [ ${#} -gt 0 ]; do
case "${1}" in
--cache-dir) fdroid_opt_cache_dir="${2}"; shift;shift;;
--allow-downgrade) adb_opt_allow_downgrade=1; shift;;
--keep-data) adb_opt_keep_data=1; shift;;
--allow-unstable) fdroid_opt_allow_unstable=1; shift;;
--cat) fdroid_arg_list_cat=1; shift;;
--desc) fdroid_arg_list_desc=1; shift;;
--ver) fdroid_arg_list_ver=1; shift;;
--only-ver) fdroid_arg_list_only_ver=1; shift;;
--sug) fdroid_arg_list_sug=1; shift;;
--appid) fdroid_arg_list_appid=1; shift;;
# ignore --user here, just accept backup_arg_users's value
# -u|--user) shift;shift;;
-r|--repo)
fdroid_arg_repos+=("${2}")
shift;shift;;
--force-download) fdroid_arg_force_download=1; shift;;
--install-path) fdroid_arg_install_path="${2}"; shift;shift;;
-l|--list) fdroid_arg_upgrade_list=1; shift;;
*) fdroid_left_args+=("${1}"); shift;;
esac
done
# limit operation only in specialized repos
if [ ${#fdroid_arg_repos[@]} -gt 0 ]; then
for r in "${!fdroid_repos[@]}"; do
if ! contains "${r}" "${fdroid_arg_repos[@]}"; then
unset "fdroid_repos[${r}]"
fi
done
fi
}
#### END_INCLUDE
###* Code
###** Variables
declare -A app_depends=(
[basename]=''
[grep]=''
[sed]=''
)
###** Help functions
# arg1: depends variable name
function check_depends {
local depends
declare -n depends="${1}"
for c in "${!depends[@]}"; do
local suggest="${depends[${c}]}"
if ! is_cmd_exists "${c}"; then
if [ -z "${suggest}" ]; then
abort "Missing command: ${c}"
else
abort "Missing command: ${c}, ${suggest}"
fi
fi
done
}
# arg1: cmd-name
function is_need_adb_cmd {
case "${1}" in
adb|install|uninstall|upgrade|backup|restore|new|deploy|cleanup)
return;;
esac
return 1
}
# arg1: cmd-name
function is_optional_need_adb_cmd {
case "${1}" in
new)
return;;
esac
return 1
}
# arg1: cmd-name
function is_need_cache_cmd {
case "${1}" in
install|upgrade|update|download|list)
return;;
esac
return 1
}
function indent_lv1 {
sed 's/^\(.\)/ \1/g'
}
function indent_lv2 {
sed 's/^/ /g'
}
function get_self_funcs {
grep -o "^function \+${1}.* {" "${app_file}" | sed -e "s/function \+//" -e "s/^\(.*\) {/\1/" | sort
}
function get_all_cmd_usage {
for c in $(get_self_funcs "cmd_" | sed 's/cmd_//g'); do
get_cmd_usage "${c}"
done
}
# arg1: cmd-name
function get_cmd_usage {
_cmd_usage_"${1}"
}
# arg1: cmd-name
function get_cmd_desc {
_cmd_usage_"${1}" | head -n 2 | tail -n 1 | sed 's/^ *//'
}
# arg1: cmd-name
function get_cmd_args {
_cmd_usage_"${1}" | head -n 1 | sed "s/^${1}//" | grep -o '\-\-\?[0-9a-zA-Z-]\+'
if [ x"${1}" = x'complete' ]; then
return
fi
echo "-c"
echo "--config"
if is_need_cache_cmd "${1}"; then
echo "--cache-dir"
fi
if is_need_adb_cmd "${1}"; then
echo "--busybox"
echo "--device-tmpdir"
echo '--root-type'
echo '-s'
echo '--serial'
fi
echo '--no-color'
echo '-v'
echo '--verbose'
echo '-vv'
echo '--verbose2'
echo '-h'
echo '--help'
}
# arg1: cmd-name, arg2: arg-name
function get_arg_desc {
local cmd="${1}"
local arg="${2}"
local desc=
desc=$((echo "${global_options}"; _cmd_usage_"${cmd}") | grep "^ *${arg},")
if [ -z "${desc}" ]; then
desc=$((echo "${global_options}"; _cmd_usage_"${cmd}") | grep ", ${arg},")
fi
echo "${desc}" | sed 's/^.*--[^,]*, //'
}
# arg1: cmd-name
function is_cmd_args_contains_APP {
if _cmd_usage_"${1}" | grep -q '[^A-Z]APP'; then
return 0
else
return 1
fi
}
# arg1: cmd-name
function is_cmd_args_contains_VERAPP {
if _cmd_usage_"${1}" | grep -q 'VERAPP'; then
return 0
else
return 1
fi
}
# arg1: cmd-name
function is_cmd_args_contains_DEVICEAPP {
if _cmd_usage_"${1}" | grep -q 'DEVICEAPP'; then
return 0
else
return 1
fi
}
# arg1: cmd-name
function is_cmd_args_contains_APPLINE {
if _cmd_usage_"${1}" | grep -q 'APPLINE'; then
return 0
else
return 1
fi
}
# arg1: cmd-name
function is_cmd_args_contains_BACKUPAPPLINE {
if _cmd_usage_"${1}" | grep -q 'BACKUPAPPLINE'; then
return 0
else
return 1
fi
}
# arg1: cmd-name
function is_cmd_args_contains_RESTOREAPPLINE {
if _cmd_usage_"${1}" | grep -q 'RESTOREAPPLINE'; then
return 0
else
return 1
fi
}
function get_cmd_funcs_with_arg_APP {
for c in $(get_self_funcs "cmd_" | sed 's/cmd_//g'); do
if is_cmd_args_contains_APP "${c}"; then
echo "${c}"
fi
done
}
function get_cmd_funcs_with_arg_VERAPP {
for c in $(get_self_funcs "cmd_" | sed 's/cmd_//g'); do
if is_cmd_args_contains_VERAPP "${c}"; then
echo "${c}"
fi
done
}
function get_cmd_funcs_with_arg_DEVICEAPP {
for c in $(get_self_funcs "cmd_" | sed 's/cmd_//g'); do
if is_cmd_args_contains_DEVICEAPP "${c}"; then
echo "${c}"
fi
done
}
function get_cmd_funcs_with_arg_APPLINE {
for c in $(get_self_funcs "cmd_" | sed 's/cmd_//g'); do
if is_cmd_args_contains_APPLINE "${c}"; then
echo "${c}"
fi
done
}
function get_cmd_funcs_with_arg_BACKUPAPPLINE {
for c in $(get_self_funcs "cmd_" | sed 's/cmd_//g'); do
if is_cmd_args_contains_BACKUPAPPLINE "${c}"; then
echo "${c}"
fi
done
}
function get_cmd_funcs_with_arg_RESTOREAPPLINE {
for c in $(get_self_funcs "cmd_" | sed 's/cmd_//g'); do
if is_cmd_args_contains_RESTOREAPPLINE "${c}"; then
echo "${c}"
fi
done
}
function get_all_adb_usage {
for c in $(get_self_funcs "adb_" | sed 's/adb_//g'); do
get_adb_usage "${c}"
done
}
# arg1: adb-func-name
function get_adb_usage {
_adb_usage_"${1}"
}
function get_adb_funcs_with_arg_PKG {
for c in $(get_self_funcs "adb_" | sed 's/adb_//g'); do
if _adb_usage_"${c}" | grep -q 'PKG'; then
echo "${c}"
fi
done
}
# arg1: cmd-name arg2: hook-type(pre or post)
function run_cmd_hook {
local cmd="${1}"
local type="${2}"
local hook_func="${type}_${cmd}"
local adb_hook_func="${type}_${cmd}_adb"
if [ x"${type}" = x"pre" ]; then
# run adb hook later
if is_func_exists "${hook_func}"; then
msg "Run ${hook_func}"
"${hook_func}"
fi
if is_func_exists "${adb_hook_func}"; then
msg "Run ${adb_hook_func}"
local adb_cmd_str="$(type ${adb_hook_func} | sed 1d);${adb_hook_func}"
adb_autoshell "${adb_cmd_str}"
fi
else
# run adb hook at first
if is_func_exists "${adb_hook_func}"; then
msg "Run ${adb_hook_func}"
local adb_cmd_str="$(type ${adb_hook_func} | sed 1d);${adb_hook_func}"
adb_autoshell "${adb_cmd_str}"
fi
if is_func_exists "${hook_func}"; then
msg "Run ${hook_func}"
"${hook_func}"
fi
fi
}
# arg1: cmd-name arg2: hook-type(pre or post) arg3: app-type(app or data) arg4: user_x:appid
function run_appline_hook {
local cmd="${1}"
local hook_type="${2}"
local app_type="${3}"
local appline="${4}"
local hook_varname="${cmd}_${hook_type}_${app_type}_hooks"
if ! is_var_exists "${hook_varname}"; then
return
fi
msg2 "run ${hook_varname} for ${appline}"
declare -n hooks="${hook_varname}"
for filter in "${!hooks[@]}"; do
if [[ "${appline}" =~ "${filter}" ]]; then
local hookstr="${hooks[${filter}]}"
eval "${hookstr}"
fi
done
}
# arg1: cmd-name, run commands on device file or dir
function run_file_funcs {
local cmd="${1}"
local funcs_varname="${cmd}_file_funcs"
if ! is_var_exists "${funcs_varname}"; then
return
fi
msg "Run ${funcs_varname}"
declare -n funcs="${funcs_varname}"
for file in "${!funcs[@]}"; do
local func="${funcs[${file}]}"
if ! is_func_exists "${func}"; then
msg2 "ignore ${func} for ${file}"
continue
fi
msg2 "run ${func} for ${file}"
local tmpdir=$(mktemp -d)
if [ -n "${arg_verbose}" ]; then
msg2 "create temporary dir ${tmpdir}"
fi
local filename=$(basename "${file}")
local host_path="${tmpdir}/${filename}"
local is_dir=
local exists=
if [[ "${file}" =~ /$ ]]; then
is_dir=1
fi
if adb_is-file-exists "${file}"; then
adb_pull "${file}" "${tmpdir}" 1
exists=1
elif [ -n "${is_dir}" ]; then
mkdir "${host_path}"
else
touch "${host_path}"
fi
"${func}" "${host_path}" "${exists}"
if [ -n "${is_dir}" ]; then
adb_push "${host_path}/" "${file}" 1
else
adb_push "${host_path}" "${file}" 1
fi
rm -rf "${tmpdir}"
done
}
###** Help commands
function _cmd_usage_adb {
echo "adb <adb-wrapped-command>
run adb wrapped commands, helps for debug work"
}
function cmd_adb {
local adb_subcmd="${1}"; shift
adb_"${adb_subcmd}" "${@}"
}
function _cmd_usage_complete {
echo "complete [--bash|--fish]
generate command completion code for shell
--bash, show completion code for bash
--fish, show completion code for fish"
}
function cmd_complete {
_cmd_complete_"${arg_complete_shell}"
}
function _cmd_complete_bash {
local funcname="_comp_${app_name//./_}"
echo "function ${funcname}_adb_users {
adb shell pm list users 2>/dev/null | grep -o '{.*}' | tr -d '{}' | awk -F: '{print \$1}'
}
function ${funcname}_backupapplines {"
for spec in "${backup_spec_apps[@]}"; do
echo " echo '${spec}'"
done
echo " adb shell pm list packages -s 2>/dev/null | tr -d '\r' | sed 's/package:/user_system:/' 2>/dev/null
local u=
for u in \$(${funcname}_adb_users); do
if [ \${u} -eq 0 ]; then
adb shell pm list packages -3 --user \${u} 2>/dev/null | tr -d '\r' | sed 's/package://' 2>/dev/null
else
adb shell pm list packages -3 --user \${u} 2>/dev/null | tr -d '\r' | sed \"s/package:/user_\${u}:/\" 2>/dev/null
fi
done
}"
echo "function ${funcname}_deviceapps {
adb shell pm list packages -3 2>/dev/null | tr -d '\r' | sed 's/package://' 2>/dev/null
}
function ${funcname}_fdroid_apps {
local app_file=\"\${COMP_WORDS[0]}\"
\${app_file} list --no-color | awk '{print \$1}' 2>/dev/null
}
function ${funcname}_fdroid_verapps {
local app_file=\"\${COMP_WORDS[0]}\"
\${app_file} list --only-ver --appid \${1} 2>/dev/null | awk -F\"\t\" '{print \$1}' | awk -F= '{print \$2}'
}
function ${funcname} {"
echo '
local cur prev_index prev
cur="${COMP_WORDS[${COMP_CWORD}]}"
cur_index="${COMP_CWORD}"
prev_index=$((COMP_CWORD - 1))
prev2_index=$((COMP_CWORD - 2))
prev="${COMP_WORDS[${prev_index}]}"
prev2="${COMP_WORDS[${prev2_index}]}"
local cmd= subcmd=
local i=1
while [ "${i}" -lt "${#COMP_WORDS[@]}" ]; do
local word="${COMP_WORDS[${i}]}"
if [[ ! "${word}" == -* ]]; then
if [ -z "${cmd}" ]; then
cmd="${word}"
else
subcmd="${word}"
fi
if [ -n "${subcmd}" ]; then
break
fi
fi
((i++))
done
if [ "${cur_index}" -eq "1" ]; then'
echo " COMPREPLY=( \$(compgen -W '-h --help $(get_self_funcs 'cmd_' | sed 's/cmd_//g' | tr '\n' ' ')' -- \"\${cur}\" ) )"
echo ' return
fi
if [ x"${cmd}" = x"adb" -a "${cur_index}" -eq "2" ]; then'
echo " COMPREPLY=( \$(compgen -W '$(get_self_funcs 'adb_' | sed 's/adb_//g' | tr '\n' ' ')' -- \"\${cur}\" ) )"
echo ' return
fi
if [[ x"${cmd}" != x"adb" && "${cur}" == -* ]]; then
case "${cmd}" in'
for c in $(get_self_funcs "cmd_" | sed 's/cmd_//g'); do
if [ x"${c}" != x"adb" ]; then
local args=$(get_cmd_args "${c}" | tr '\n' ' ')
echo " ${c}) COMPREPLY=( \$(compgen -W '${args}' -- \"\${cur}\" ) ); return;;"
fi
done
echo ' esac
fi
if [[ ! "${cur}" == @(-*|.*|../*|~/*|/*) ]]; then
if [ x"${cmd}" != x"adb" ]; then
case "${cmd}" in'
echo " $(get_cmd_funcs_with_arg_BACKUPAPPLINE | tr '\n' '|' | sed 's/|$//')) COMPREPLY=( \$(compgen -W \"\$(${funcname}_backupapplines)\" -- \"\${cur}\" ) ); return;;
$(get_cmd_funcs_with_arg_RESTOREAPPLINE | tr '\n' '|' | sed 's/|$//')) COMPREPLY=( \$(compgen -W \"\$(${funcname}_backupapplines)\" -- \"\${cur}\" ) ); return;;
$(get_cmd_funcs_with_arg_APP | tr '\n' '|' | sed 's/|$//')) COMPREPLY=( \$(compgen -W \"\$(${funcname}_fdroid_apps)\" -- \"\${cur}\" ) ); return;;
$(get_cmd_funcs_with_arg_DEVICEAPP | tr '\n' '|' | sed 's/|$//')) COMPREPLY=( \$(compgen -W \"\$(${funcname}_deviceapps)\" -- \"\${cur}\" ) ); return;;
$(get_cmd_funcs_with_arg_VERAPP | tr '\n' '|' | sed 's/|$//'))
if [ x\"\${cur}\" = x'=' ]; then
COMPREPLY=( \$(compgen -W \"\$(${funcname}_fdroid_verapps \"\${prev}\")\") )
elif [ x\"\${prev}\" = x'=' ]; then
COMPREPLY=( \$(compgen -W \"\$(${funcname}_fdroid_verapps \"\${prev2}\")\" -- \"\${cur}\" ) )
else
COMPREPLY=( \$(compgen -W \"\$(${funcname}_fdroid_apps)\" -- \"\${cur}\" ) )
fi
return;;
esac
else
case \"\${subcmd}\" in
$(get_adb_funcs_with_arg_PKG | tr '\n' '|' | sed 's/|$//')) COMPREPLY=( \$(compgen -W \"\$(${funcname}_deviceapps)\" -- \"\${cur}\" ) ); return;;"
echo ' esac
fi
fi
if [[ "${cur}" == @(.*|../*|~/*|/*) ]]; then
COMPREPLY=( $(compgen -d -f -- "${cur}") )
fi
} &&'
echo "complete -o filenames -o bashdefault -F ${funcname} ${app_name}"
}
function _cmd_complete_fish {
local funcname="__fish_complete_${app_name//./_}"
echo "function ${funcname}_no_subcommand
for i in (commandline -opc)
if contains -- \$i $(get_self_funcs 'cmd_' | sed 's/cmd_//g' | tr '\n' ' ')
return 1
end
end
return 0
end
function ${funcname}_contains_file
set -l cmd (commandline -poc)
if string match -r -q -- '^[./~]' \$cmd[(count \$cmd)]
return 0
end
return 1
end
function ${funcname}_apps_cond
set -l cmd (commandline -po)
set -e cmd[1]
if __fish_seen_subcommand_from \$argv
and string match -r -q -- '^[^./~-]' \$cmd[(count \$cmd)]
return 0
end
return 1
end
function ${funcname}_adb_devices
adb devices -l | string replace -rf '(\S+) +.' '\$1'\t | string replace -rf \t'.*model:(\S+).*' \t'\$1'
end
function ${funcname}_adb_users
adb shell pm list users 2>/dev/null | grep -o '{.*}' | tr -d '{}' | awk -F: '{print \$1}'
end
function ${funcname}_backupapplines
for i in ${backup_spec_apps[@]}
echo \$i\tSpecialApp
end
for i in (${funcname}_adb_users)
if test \$i -eq 0
adb shell pm list packages -3 --user \$i 2>/dev/null | string replace 'package:' '' | string replace -a \r '' | string replace -r '$' '\tUserApp'
else
adb shell pm list packages -3 --user \$i 2>/dev/null | string replace 'package:' \"user_\$i:\" | string replace -a \r '' | string replace -r '$' '\tUserApp'
end
end
adb shell pm list packages -s 2>/dev/null | string replace 'package:' 'user_system:' | string replace -a \r '' | string replace -r '$' '\tSystemApp'
end
function ${funcname}_deviceapps
adb shell pm list packages -3 2>/dev/null | string replace 'package:' '' | string replace -a \r '' | string replace -r '$' '\tUserApp'
adb shell pm list packages -s 2>/dev/null | string replace 'package:' '' | string replace -a \r '' | string replace -r '$' '\tSystemApp'
end
function ${funcname}_fdroid_apps
set -l cmd (commandline -po)
set -l app_file \$cmd[1]
\$app_file list --no-color | sed 's/ - / /' 2>/dev/null
end
function ${funcname}_fdroid_verapps
set -l cmd (commandline -po)
set -l app_file \$cmd[1]
\$app_file list --only-ver --appid 2>/dev/null
end
"
echo "complete -x -n '${funcname}_no_subcommand' -c ${app_name} -a '-h' -d 'show help message'"
echo "complete -x -n '${funcname}_no_subcommand' -c ${app_name} -a '--help' -d 'show help message'"
for cmd in $(get_self_funcs "cmd_" | sed 's/cmd_//g'); do
echo "complete -x -n '${funcname}_no_subcommand' -c ${app_name} -a '${cmd}' -d '$(get_cmd_desc ${cmd})'"
if [ x"${cmd}" != x"adb" ]; then
for arg in $(get_cmd_args "${cmd}"); do
if [ x"${arg}" = x"--serial" ]; then
echo "complete -x -n '__fish_seen_subcommand_from ${cmd}' -c ${app_name} -l '${arg#--}' -d '$(get_arg_desc ${cmd} ${arg})' -a \"(${funcname}_adb_devices)\""
elif [ x"${arg}" = x"-s" ]; then
echo "complete -x -n '__fish_seen_subcommand_from ${cmd}' -c ${app_name} -s '${arg#-}' -d '$(get_arg_desc ${cmd} ${arg})' -a \"(${funcname}_adb_devices)\""
elif [[ "${arg}" == --* ]]; then
echo "complete -n '__fish_seen_subcommand_from ${cmd}' -c ${app_name} -l '${arg#--}' -d '$(get_arg_desc ${cmd} ${arg})'"
else
echo "complete -n '__fish_seen_subcommand_from ${cmd}' -c ${app_name} -s '${arg#-}' -d '$(get_arg_desc ${cmd} ${arg})'"
fi
done
if is_cmd_args_contains_BACKUPAPPLINE "${cmd}"; then
echo "complete -f -n '${funcname}_apps_cond ${cmd}' -c ${app_name} -a \"(${funcname}_backupapplines)\""
fi
if is_cmd_args_contains_RESTOREAPPLINE "${cmd}"; then
echo "complete -f -n '${funcname}_apps_cond ${cmd}' -c ${app_name} -a \"(${funcname}_backupapplines)\""
fi
if is_cmd_args_contains_APP "${cmd}"; then
echo "complete -f -n '${funcname}_apps_cond ${cmd}' -c ${app_name} -a \"(${funcname}_fdroid_apps)\""
fi
if is_cmd_args_contains_VERAPP "${cmd}"; then
echo "complete -f -n '${funcname}_apps_cond ${cmd}' -c ${app_name} -a \"(${funcname}_fdroid_verapps)\""
fi
if is_cmd_args_contains_DEVICEAPP "${cmd}"; then
echo "complete -f -n '${funcname}_apps_cond ${cmd}' -c ${app_name} -a \"(${funcname}_deviceapps)\""
fi
else
for subcmd in $(get_self_funcs "adb_" | sed 's/adb_//g'); do
echo "complete -n '__fish_seen_subcommand_from adb' -c ${app_name} -a '${subcmd}' -d 'Subcommand'"
done
fi
done
}
###** Main loop
arg_config=
arg_complete_shell='bash'
arg_no_color=
arg_verbose=
arg_version=
arg_help=
arg_cmd=
declare -a arg_cmd_args
global_option_PKG="PKG, package name, such as org.fdroid.fdroid"
global_option_APP="APP, application id, should be same with package name"
global_option_VERAPP="VERAPP, application that could special the version code, such as org.fdroid.fdroid, org.fdroid.fdroid=1007051"
global_option_DEVICEAPP="DEVICEAPP, installed applications in android device"
global_option_APPLINE="APPLINE, the format is [user_x:]appid[:options]
appid could be application id or special app name that defined in \$backup_spec_apps
if user field missing, it will be set to user_0
for example org.fdroid.fdroid, user_0:org.fdroid.fdroid:type=app+data;repo=fdroid"
global_option_BACKUPAPPLINE="BACKUPAPPLINE, same format with APPLINE, and the app should be installed in device"
global_option_RESTOREAPPLINE="RESTOREAPPLINE, same format with APPLINE, and the app should be backup-ed in data dir"
global_option_config="-c, --config, special config file [default: '~/.config/${app_name}/config.sh']"
global_option_cache_dir="--cache-dir, special cache directory [default: '~/.cache/${app_name}']"
global_option_user="-u, --user, special user id, could set multiple times
the value could be 'cur', 'current', 'sys', 'system', 'all' and the user id number
if not specialized will action for all users
for command new, it means generate apps list from special users
for command restore, it means restore apps to special users, and no effect if --profile exists
for command install, it means install apps to special users
for command uninstall, it means uninstall apps from special users"
global_option_no_color="--no-color, remove colorful message"
global_option_verbose="-v, --verbose, show verbose message"
global_option_verbose2="-vv, --verbose2, show more verbose message"
global_option_version="-V, --version, show version"
global_option_help="-h, --help, show help message"
global_option_busybox="--busybox, special busybox command in android
could set the value to 'toybox' or 'busybox' command, if not set just use
the sub-commands(cp, rm, tar, etc) that linked to system path"
global_option_device_tmpdir="--device-tmpdir, special tmpdir in android [default: '${ADB_DEFAULT_DEVICE_TMPDIR}']"
global_option_root_type="--root-type, could be 'auto', 'su', 'adb-root' or 'none' (default: '${ADB_DEFAULT_ROOT_TYPE}')"
global_option_serial="-s, --serial, use device with given serial (overrides \$ANDROID_SERIAL)"
global_option_appdir="--appdir, special application direcotory [default: '${BACKUP_DEFAULT_APPDIR}']"
global_option_archive_side="--archive-side, create and extract archive in device or host [default: '${BACKUP_DEFAULT_ARCHIVE_SIDE}']
could be 'auto', 'device' and 'host'. Archive in device will save lots of time
for the huge data, but 'tar' and 'gzip' commands requires in device."
global_option_name="--name, profile name [default: 'default' or profile filename]"
global_option_profile="-p, --profile, target profile file"
global_option_password="--password, encrypt the user data archive with password
if set to '-', will read from input"
global_option_options="--options, override options in APPLINE, such as type=app+data;repo=fdroid"
global_option_filter="--filter, only apply APPLINE that match the filter
the format is [user_x:][keyword], such as user_0, org.fdroid.fdroid and user_0:org.fdroid.fdroid"
global_option_allow_unstable="--allow-unstable, allow unstable application release"
global_option_repo="-r, --repo, special repo, could set multiple times"
global_options="${global_option_PKG}
${global_option_APP}
${global_option_VERAPP}
${global_option_DEVICEAPP}
${global_option_APPLINE}
${global_option_BACKUPAPPLINE}
${global_option_RESTOREAPPLINE}
${global_option_config}
${global_option_cache_dir}
${global_option_user}
${global_option_no_color}
${global_option_verbose}
${global_option_verbose2}
${global_option_version}
${global_option_help}
ADB function options:
$(echo "${global_option_busybox}" | indent_lv1)
$(echo "${global_option_device_tmpdir}" | indent_lv1)
$(echo "${global_option_root_type}" | indent_lv1)
$(echo "${global_option_serial}" | indent_lv1)
Backup command options:
$(echo "${global_option_appdir}" | indent_lv1)
$(echo "${global_option_archive_side}" | indent_lv1)
$(echo "${global_option_name}" | indent_lv1)
$(echo "${global_option_profile}" | indent_lv1)
$(echo "${global_option_password}" | indent_lv1)
$(echo "${global_option_options}" | indent_lv1)
$(echo "${global_option_filter}" | indent_lv1)
F-Droid command options:
$(echo "${global_option_allow_unstable}" | indent_lv1)
$(echo "${global_option_repo}" | indent_lv1)"
function show_usage {
cat <<EOF
${app_name} <command> [Global Options] [args] [-h|--help]
Global Options:
$(echo "${global_options}" | indent_lv1)
Commands:
$(get_all_cmd_usage | indent_lv1)
Adb Wrapped Commands:
$(get_all_adb_usage | indent_lv1)
Usage:
${app_name} update
${app_name} list fdroid
${app_name} list --cat --desc --ver --sug --appid org.fdroid.fdroid
${app_name} install org.fdroid.fdroid=1007051
${app_name} backup org.fdroid.fdroid cf.vojtechh.apkmirror
${app_name} restore --user 10 --user 11 org.fdroid.fdroid
${app_name} restore --user 10 --user 11 ./data/default/user_0/org.fdroid.fdroid/appinfo.sh
${app_name} new --3rd-app --3rd-data --freq-data --profile ./test.sh
${app_name} backup --profile ./test.sh
${app_name} restore --profile ./test.sh
${app_name} check-backup .
EOF
}
function show_usage_for_cmd {
get_cmd_usage "${arg_cmd}"
echo
echo "Global Options:"
if is_cmd_args_contains_APP "${arg_cmd}"; then
echo "${global_option_PKG}" | indent_lv1
fi
if is_cmd_args_contains_VERAPP "${arg_cmd}"; then
echo "${global_option_VERAPP}" | indent_lv1
fi
if is_cmd_args_contains_DEVICEAPP "${arg_cmd}"; then
echo "${global_option_DEVICEAPP}" | indent_lv1
fi
if is_cmd_args_contains_BACKUPAPPLINE "${arg_cmd}"; then
echo "${global_option_BACKUPAPPLINE}" | indent_lv1
fi
local arg= desc_var_name= global_option_desc=
for arg in $(get_cmd_args "${arg_cmd}"); do
if [[ "${arg}" == --* ]]; then
desc_var_name="${arg##--}"
desc_var_name="global_option_${desc_var_name//-/_}"
declare -n global_option_desc="${desc_var_name}"
if [ -n "${global_option_desc}" ]; then
echo "${global_option_desc}" | indent_lv1
fi
fi
done
}
# dispatch config
declare -a new_args=()
while [ ${#} -gt 0 ]; do
case "${1}" in
--config) arg_config="${2}"; shift;shift;;
*) new_args+=("${1}"); shift;;
esac
done
set -- "${new_args[@]}"
# apply user config
if [ -n "${arg_config}" ]; then
if [ -f "${arg_config}" ]; then
app_config="${arg_config}"
else
warning "Configuration file ${arg_config} not exists, ignore it"
fi
fi
if [ -f "${app_config}" ]; then
source "${app_config}"
fi
# dispatch arguments
backup_dispatch_args "${@}"; set -- "${backup_left_args[@]}"
deploy_dispatch_args "${@}"; set -- "${deploy_left_args[@]}"
fdroid_dispatch_args "${@}"; set -- "${fdroid_left_args[@]}"
fdroid_arg_users=("${backup_arg_users[@]}")
while [ ${#} -gt 0 ]; do
case "${1}" in
--busybox) adb_opt_busybox="${2}"; shift;shift;;
--device-tmpdir) adb_opt_device_tmpdir="${2}"; shift;shift;;
--root-type) adb_opt_root_type="${2}"; shift;shift;;
-s|--serial) adb_opt_serial="${2}"; adb_cmd="${adb_cmd} -s ${2}"; shift;shift;;
--bash) arg_complete_shell="bash"; shift;;
--fish) arg_complete_shell="fish"; shift;;
--no-color) arg_no_color=1; shift;;
-v|--verbose) arg_verbose=1; adb_opt_verbose=1; backup_opt_verbose=1; fdroid_opt_verbose=1; shift;;
-vv|--verbose2) arg_verbose=2; adb_opt_verbose=2; backup_opt_verbose=2; fdroid_opt_verbose=2; shift;;
-V|--version) arg_version=1; shift;;
-h|--help) arg_help=1; shift;;
*) if [ -z "${arg_cmd}" ]; then arg_cmd="${1}"; else arg_cmd_args+=("${1}"); fi; shift;;
esac
done
if [ -z "${arg_no_color}" ]; then
enable_colors
fi
if [ "${arg_version}" ]; then
echo "${app_version}"
exit
fi
if [ -z "${arg_cmd}" -a -z "${arg_help}" ]; then
warning "Need a command"
arg_help=1
fi
if [ "${arg_help}" ]; then
if [ -n "${arg_cmd}" ]; then
show_usage_for_cmd
else
show_usage
fi
exit
fi
# check depends
if [ x"${arg_cmd}" != x"complete" ]; then
check_depends utils_depends
check_depends backup_depends
check_depends fdroid_depends
check_depends app_depends
fi
if is_need_adb_cmd "${arg_cmd}"; then
if ! is_optional_need_adb_cmd "${arg_cmd}"; then
check_depends adb_depends
adb_check-online
fi
if adb_is-online && [ x"${arg_cmd}" != x'adb' ]; then
_adb_fetch_cache
fi
fi
cmd_"${arg_cmd}" "${arg_cmd_args[@]}"
if [ x"${arg_cmd}" != x"list" -a x"${arg_cmd}" != x"adb" -a x"${arg_cmd}" != x"complete" ]; then
msg "Done"
fi