5160 lines
163 KiB
Bash
Executable File
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
|