mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 19:34:03 +01:00
The device owner is provisioned, so enforcement stops being theoretical. Three things had to be true before it was safe to switch on. Release signing. A device owner can only be replaced by an APK with the same signature, and `dpm remove-active-admin` does not work on one, so a debug-signed device owner could never be updated -- the only exit would be another factory reset. focus_owner now uses the shared key.properties config the other apps use. No minify: an untested ProGuard pass that strips a DevicePolicyManager callback produces a device owner that cannot release itself. Uninstall blocking. Device owner hides Settings' uninstall button, but `adb uninstall` still worked, and that is the route that strands ownership with no holder. setSelfUninstallBlocked closes it, re-asserted every pass so it self-heals. The ordering in releaseDeviceOwner is not stylistic: unblock, THEN clear. Reversed, the block outlives the ownership required to lift it. The allowlist. config.sh describes the rooted phone, so the hide set was computed on the PC before any pass ran -- which caught pl.infakt.infakt in it. infakt is device-paired to a bank over SMS, so hiding it would have stranded the same re-authentication chain as hiding Messages. Verified on the Pixel 6a as a real device owner: hid 27, and youtube, youtube.music, chrome, vending and videos are all hidden=true, while phone, messages, keepass, mbank, revolut, infakt, mobywatel, settings and signal stay hidden=false. `adb uninstall` returns DELETE_FAILED_DEVICE_POLICY_MANAGER. All of it survives a reboot, which is exactly what `pm suspend` and `pm disable-user` failed to do here. restore_apps.sh reinstalls the 54 backed-up packages via install-multiple, since split APKs make a bare `adb install` fail. It skips the device owner's own package by name. Enforcement is permanent for now, not geofenced: push_home_location.sh delivers coordinates through `run-as`, which is dead on a release build, so every pass decides LOCATION_UNKNOWN and fails closed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014DqEd1uUhyT52KjaNeLanC
185 lines
5.6 KiB
Bash
Executable File
185 lines
5.6 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ============================================================================
|
|
# Reinstall the APKs captured by phone_backup.sh onto a wiped device.
|
|
#
|
|
# Split APKs are the norm on a modern Pixel (130 files for 54 packages here),
|
|
# and `adb install base.apk` on a split package fails with
|
|
# INSTALL_FAILED_INVALID_APK -- every package therefore goes through
|
|
# `install-multiple` with its splits.
|
|
#
|
|
# Failures are expected and are NOT fatal: preinstalled system apps come back
|
|
# with the factory image, and some Play-licensed apps refuse a sideload. The
|
|
# script collects them and prints a summary instead of aborting, because a
|
|
# stop-on-first-error run would leave the restore half-done with no report.
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_NAME
|
|
|
|
# Never reinstall the device owner from a backup. The captured APK predates the
|
|
# release signing config and is debug-signed, so it cannot install over the
|
|
# provisioned build anyway -- but a successful downgrade would be far worse
|
|
# than a failed one, so it is skipped by name rather than left to chance.
|
|
readonly DEVICE_OWNER_PKG="com.kuhy.focus_owner"
|
|
|
|
BACKUP_DIR=""
|
|
SERIAL=""
|
|
DRY_RUN=0
|
|
|
|
usage() {
|
|
echo "Usage: $SCRIPT_NAME --backup DIR [--serial SERIAL] [--dry-run]"
|
|
echo "Options:"
|
|
echo " -b, --backup Backup directory from phone_backup.sh (required)"
|
|
echo " -s, --serial ADB serial (required when several devices attached)"
|
|
echo " -n, --dry-run List what would be installed, install nothing"
|
|
echo " -h, --help Show this help"
|
|
exit 0
|
|
}
|
|
|
|
# adb with the serial applied only when one was given, so the script also
|
|
# works with a single attached device.
|
|
adb_dev() {
|
|
if [[ -n "$SERIAL" ]]; then
|
|
adb -s "$SERIAL" "$@"
|
|
else
|
|
adb "$@"
|
|
fi
|
|
}
|
|
|
|
validate_requirements() {
|
|
if [[ -z "$BACKUP_DIR" ]]; then
|
|
echo "Error: --backup is required" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -d "$BACKUP_DIR/apks" ]]; then
|
|
echo "Error: no apks/ directory under $BACKUP_DIR" >&2
|
|
exit 1
|
|
fi
|
|
if ! command -v adb >/dev/null 2>&1; then
|
|
echo "Error: adb not found. Install with: sudo pacman -S android-tools" >&2
|
|
exit 1
|
|
fi
|
|
local state
|
|
state="$(adb_dev get-state 2>/dev/null || true)"
|
|
if [[ "$state" != "device" ]]; then
|
|
echo "Error: device not ready (adb state: ${state:-unreachable})" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Guard the whole point of the exercise: a restore must never cost device
|
|
# ownership, and re-provisioning is impossible once an account exists.
|
|
check_device_owner() {
|
|
local dump
|
|
dump="$(adb_dev shell dumpsys device_policy 2>/dev/null || true)"
|
|
if [[ "$dump" == *"Device Owner Type: 0"* ]]; then
|
|
echo "Device owner: present (preserved by this script)"
|
|
else
|
|
echo "Device owner: NOT set -- continuing, but note it cannot be"
|
|
echo " provisioned again once any account is added."
|
|
fi
|
|
}
|
|
|
|
install_one() {
|
|
local pkg_dir="$1"
|
|
local pkg
|
|
pkg="$(basename "$pkg_dir")"
|
|
|
|
if [[ "$pkg" == "$DEVICE_OWNER_PKG" ]]; then
|
|
echo " SKIP $pkg (device owner; keeping the provisioned build)"
|
|
return 0
|
|
fi
|
|
|
|
local apks=()
|
|
mapfile -t apks < <(find "$pkg_dir" -maxdepth 1 -name '*.apk' | sort)
|
|
if [[ ${#apks[@]} -eq 0 ]]; then
|
|
echo " SKIP $pkg (no apk files)"
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
echo " WOULD INSTALL $pkg (${#apks[@]} apk(s))"
|
|
return 0
|
|
fi
|
|
|
|
# install-multiple handles the single-APK case too, so there is no need to
|
|
# branch on the split/non-split distinction.
|
|
local output
|
|
if output="$(adb_dev install-multiple -r "${apks[@]}" 2>&1)"; then
|
|
echo " OK $pkg"
|
|
return 0
|
|
fi
|
|
|
|
# Trim to the failure reason; the streamed-install chatter is noise.
|
|
local reason
|
|
reason="$(printf '%s' "$output" | grep -oE 'INSTALL_[A-Z_]+' | head -1)"
|
|
echo " FAIL $pkg (${reason:-unknown})"
|
|
return 1
|
|
}
|
|
|
|
main() {
|
|
validate_requirements
|
|
|
|
echo "============================================================================"
|
|
echo "Restoring APKs from $BACKUP_DIR"
|
|
echo "============================================================================"
|
|
check_device_owner
|
|
|
|
local pkg_dirs=()
|
|
mapfile -t pkg_dirs < <(find "$BACKUP_DIR/apks" -mindepth 1 -maxdepth 1 -type d | sort)
|
|
echo "Packages in backup: ${#pkg_dirs[@]}"
|
|
echo
|
|
|
|
local ok=0
|
|
local failed=0
|
|
local failed_pkgs=()
|
|
local dir
|
|
for dir in "${pkg_dirs[@]}"; do
|
|
if install_one "$dir"; then
|
|
ok=$((ok + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
failed_pkgs+=("$(basename "$dir")")
|
|
fi
|
|
done
|
|
|
|
echo
|
|
echo "============================================================================"
|
|
echo "Installed: $ok Failed: $failed"
|
|
if [[ ${#failed_pkgs[@]} -gt 0 ]]; then
|
|
echo "Failed packages (expected for preinstalled/Play-licensed apps):"
|
|
printf ' %s\n' "${failed_pkgs[@]}"
|
|
fi
|
|
check_device_owner
|
|
echo "============================================================================"
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-b | --backup)
|
|
BACKUP_DIR="$2"
|
|
shift 2
|
|
;;
|
|
-s | --serial)
|
|
SERIAL="$2"
|
|
shift 2
|
|
;;
|
|
-n | --dry-run)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
main "$@"
|