Compare commits

..

13 Commits

119 changed files with 2961 additions and 9173 deletions

1299
android/0_termux-setup_v2.sh Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,245 @@
#!/data/data/com.termux/files/usr/bin/bash
set -euo pipefail
RED="\033[31m"
YEL="\033[33m"
GRN="\033[32m"
BLU="\033[34m"
RST="\033[0m"
BOLD="\033[1m"
log() { printf "${BLU}[iiab]${RST} %s\n" "$*"; }
ok() { printf "${GRN}[iiab]${RST} %s\n" "$*"; }
warn() { printf "${YEL}[iiab] WARNING:${RST} %s\n" "$*" >&2; }
warn_red() { printf "${RED}${BOLD}[iiab] WARNING:${RST} %s\n" "$*" >&2; }
have() { command -v "$1" >/dev/null 2>&1; }
HOST="127.0.0.1"
CONNECT_PORT=""
TIMEOUT_SECS=180
STATE_DIR="${TMPDIR:-/data/data/com.termux/files/usr/tmp}/adbw_pair"
NOTIF_ID=9400
CLEANUP_OFFLINE=1
DEBUG=0
mkdir -p "$STATE_DIR"
need() { command -v "$1" >/dev/null 2>&1; }
die(){ echo "[!] $*" >&2; exit 1; }
dbg(){ [[ "$DEBUG" == "1" ]] && echo "[DBG] $*" >&2 || true; }
# Avoid dpkg conffile prompts (Termux layer)
TERMUX_APT_OPTS=(
"-y"
"-o" "Dpkg::Options::=--force-confdef"
"-o" "Dpkg::Options::=--force-confold"
)
termux_apt() { apt-get "${TERMUX_APT_OPTS[@]}" "$@"; }
step_termux_repo_select_once() {
local stamp="$STATE_DIR/stamp.termux_repo_selected"
if [[ -f "$stamp" ]]; then
return 0
fi
if ! have termux-change-repo; then
warn "termux-change-repo not found; skipping mirror selection."
return 0
fi
# When running via "curl | bash", stdin is not a TTY.
# Try to prompt via /dev/tty if available. If not, skip WITHOUT stamping.
if [[ -r /dev/tty ]]; then
printf "\n${YEL}[iiab] One-time setup:${RST} Select a nearby Termux repository mirror for faster downloads.\n" >&2
local ans="Y"
if ! read -r -p "[iiab] Launch termux-change-repo now? [Y/n]: " ans < /dev/tty; then
warn "No interactive TTY available; skipping mirror selection (run the script directly to be prompted)."
return 0
fi
ans="${ans:-Y}"
if [[ "$ans" =~ ^[Yy]$ ]]; then
termux-change-repo || true
ok "Mirror selection completed (or skipped inside the UI)."
else
warn "Mirror selection skipped by user."
fi
date > "$stamp"
return 0
fi
warn "No /dev/tty available; skipping mirror selection (run the script directly to be prompted)."
return 0
}
install_if_missing() {
local pkgs=()
need adb || pkgs+=("android-tools")
need termux-notification || pkgs+=("termux-api")
termux_apt update || true
termux_apt upgrade || true
if ((${#pkgs[@]})); then
echo "[*] Installing: ${pkgs[*]}"
termux_apt install "${pkgs[@]}" >/dev/null
fi
need adb || die "Missing adb. Install: pkg install android-tools"
need termux-notification || die "Missing termux-notification. Install: pkg install termux-api (and install Termux:API app)"
}
cleanup_notif() {
termux-notification-remove "$NOTIF_ID" >/dev/null 2>&1 || true
}
notify_ask_one() {
# args: key title content
local key="$1" title="$2" content="$3"
local out="$STATE_DIR/$key.txt"
rm -f "$out"
# Force a "fresh" notification so Android plays sound each time
termux-notification-remove "$NOTIF_ID" >/dev/null 2>&1 || true
termux-notification \
--id "$NOTIF_ID" \
--ongoing \
--alert-once \
--priority max \
--title "$title" \
--content "$content" \
--sound \
--button1 "Answer" \
--button1-action "sh -lc 'echo \"\$REPLY\" > \"$out\"'"
local start now
start="$(date +%s)"
while true; do
if [[ -s "$out" ]]; then
tr -d '\r\n' < "$out"
return 0
fi
now="$(date +%s)"
if (( now - start >= TIMEOUT_SECS )); then
return 1
fi
sleep 1
done
}
ask_port_5digits() {
# args: key title
local key="$1" title="$2"
local v=""
while true; do
v="$(notify_ask_one "$key" "$title" "(5 digits)")" || return 1
v="${v//[[:space:]]/}"
[[ "$v" =~ ^[0-9]{5}$ ]] || continue
echo "$v"
return 0
done
}
ask_code_6digits() {
local v=""
while true; do
v="$(notify_ask_one code "PAIR CODE" "(6 digits)")" || return 1
v="${v//[[:space:]]/}"
[[ -n "$v" ]] || continue
[[ "$v" =~ ^[0-9]+$ ]] || continue
# Allow missing leading zeros, then normalize to exactly 6 digits
if ((${#v} < 6)); then
v="$(printf "%06d" "$v")"
fi
[[ "$v" =~ ^[0-9]{6}$ ]] || continue
echo "$v"
return 0
done
}
cleanup_offline_loopback() {
local keep_serial="$1" # e.g. 127.0.0.1:42371
local line serial state
while read -r line; do
serial="$(echo "$line" | awk '{print $1}')"
state="$(echo "$line" | awk '{print $2}')"
[[ "$serial" == ${HOST}:* ]] || continue
[[ "$state" == "offline" ]] || continue
[[ "$serial" == "$keep_serial" ]] && continue
adb disconnect "$serial" >/dev/null 2>&1 || true
done < <(adb devices 2>/dev/null | tail -n +2 | sed '/^\s*$/d')
}
usage() {
cat <<EOF
Usage:
$0 [--connect-port 41313] [--host 127.0.0.1] [--no-cleanup-offline] [--debug]
Prompts:
CONNECT PORT (5 digits) # only if --connect-port not provided
PAIR PORT (5 digits)
PAIR CODE (6 digits)
EOF
}
main() {
while [[ $# -gt 0 ]]; do
case "$1" in
--host) HOST="${2:-}"; shift 2 ;;
--connect-port) CONNECT_PORT="${2:-}"; shift 2 ;;
--no-cleanup-offline) CLEANUP_OFFLINE=0; shift ;;
--debug) DEBUG=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "Unknown arg: $1" ;;
esac
done
install_if_missing
trap cleanup_notif EXIT
adb start-server >/dev/null 2>&1 || true
echo "[*] adb: $(adb version | head -n 1)"
if [[ -n "$CONNECT_PORT" ]]; then
CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}"
[[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid --connect-port (must be 5 digits): '$CONNECT_PORT'"
else
echo "[*] Asking CONNECT PORT..."
CONNECT_PORT="$(ask_port_5digits connect "CONNECT PORT")" || die "Timeout waiting CONNECT PORT."
fi
echo "[*] Asking PAIR PORT..."
local pair_port
pair_port="$(ask_port_5digits pair "PAIR PORT")" || die "Timeout waiting PAIR PORT."
echo "[*] Asking PAIR CODE..."
local code
code="$(ask_code_6digits)" || die "Timeout waiting PAIR CODE."
local serial="${HOST}:${CONNECT_PORT}"
adb disconnect "$serial" >/dev/null 2>&1 || true
echo "[*] adb pair ${HOST}:${pair_port}"
printf '%s\n' "$code" | adb pair "${HOST}:${pair_port}"
echo "[*] adb connect $serial"
adb connect "$serial" >/dev/null
if [[ "$CLEANUP_OFFLINE" == "1" ]]; then
cleanup_offline_loopback "$serial"
fi
echo "[*] Devices:"
adb devices -l
echo "[*] ADB check (shell):"
adb -s "$serial" shell sh -lc 'echo "it worked: adb shell is working"; getprop ro.product.model; getprop ro.build.version.release' || true
echo "[+] OK"
}
main "$@"

View File

@ -0,0 +1,28 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
RED="\033[31m"; YEL="\033[33m"; GRN="\033[32m"; BLU="\033[34m"; RST="\033[0m"; BOLD="\033[1m"
log() { printf "${BLU}[iiab]${RST} %s\n" "$*"; }
ok() { printf "${GRN}[iiab]${RST} %s\n" "$*"; }
warn() { printf "${YEL}[iiab] WARNING:${RST} %s\n" "$*" >&2; }
warn_red() { printf "${RED}${BOLD}[iiab] WARNING:${RST} %s\n" "$*" >&2; }
have() { command -v "$1" >/dev/null 2>&1; }
need() { have "$1" || return 1; }
die() { echo "[!] $*" >&2; exit 1; }
# -------------------------
# Global defaults (may be overridden via environment)
# -------------------------
STATE_DIR="${STATE_DIR:-${HOME}/.iiab-android}"
ADB_STATE_DIR="${ADB_STATE_DIR:-${STATE_DIR}/adbw_pair}"
LOG_DIR="${LOG_DIR:-${STATE_DIR}/logs}"
HOST="${HOST:-127.0.0.1}"
CONNECT_PORT="${CONNECT_PORT:-}"
TIMEOUT_SECS="${TIMEOUT_SECS:-180}"
# Defaults used by ADB flows / logging / misc
CLEANUP_OFFLINE="${CLEANUP_OFFLINE:-1}"
DEBUG="${DEBUG:-0}"

View File

@ -0,0 +1,76 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# -------------------------
# Logging
# -------------------------
LOG_ENABLED=1
LOG_FILE="" # if empty, auto-generate under $LOG_DIR
LOG_KEEP=20 # keep last N logs
rotate_logs() {
[[ -d "${LOG_DIR:-}" ]] || return 0
local n=$((LOG_KEEP + 1))
while IFS= read -r f; do
[[ -n "${f:-}" ]] || continue
[[ -n "${LOG_FILE:-}" && "$f" == "$LOG_FILE" ]] && continue
rm -f -- "$f" 2>/dev/null || true
done < <(ls -1t "$LOG_DIR"/*.log 2>/dev/null | tail -n +"$n" || true)
}
setup_logging() {
# Save original console fds so interactive tools still work after we redirect stdout/stderr.
exec 3>&1 4>&2
# If logging is disabled, still allow --debug to trace to console.
if [[ "${LOG_ENABLED:-1}" -ne 1 ]]; then
if [[ "${DEBUG:-0}" -eq 1 ]]; then
set -x
ok "Debug trace enabled (bash -x) -> console (logging disabled)"
fi
return 0
fi
mkdir -p "$LOG_DIR" 2>/dev/null || true
if [[ -z "${LOG_FILE:-}" ]]; then
LOG_FILE="${LOG_DIR}/0_termux-setupv2.$(date +%Y%m%d-%H%M%S).log"
else
# Best-effort: ensure parent dir exists
mkdir -p "$(dirname -- "$LOG_FILE")" 2>/dev/null || true
fi
# Header (best-effort)
local started
started="$(date -Is 2>/dev/null || date 2>/dev/null || echo "?")"
{
echo "=== iiab termux setup v2 log ==="
echo "Started: $started"
echo "Script: $0"
echo "Args: ${*:-}"
echo "Android SDK=${ANDROID_SDK:-?} Release=${ANDROID_REL:-?}"
echo "PWD: $(pwd 2>/dev/null || true)"
echo "================================"
} >>"$LOG_FILE" 2>/dev/null || true
# Best-effort: restrict log readability (may include debug/xtrace)
chmod 600 "$LOG_FILE" 2>/dev/null || true
rotate_logs
# Duplicate stdout/stderr to console + log (strip ANSI in log)
exec \
> >(tee >(sed -E 's/\x1B\[[0-9;]*[ -/]*[@-~]//g' >>"$LOG_FILE")) \
2> >(tee >(sed -E 's/\x1B\[[0-9;]*[ -/]*[@-~]//g' >>"$LOG_FILE") >&2)
ok "Logging to: $LOG_FILE"
# If --debug, send xtrace only to log
if [[ "${DEBUG:-0}" -eq 1 ]]; then
exec 9>>"$LOG_FILE"
export BASH_XTRACEFD=9
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
ok "Debug trace enabled (bash -x) -> log only"
fi
}

View File

@ -0,0 +1,115 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# Termux apt options (avoid conffile prompts)
TERMUX_APT_OPTS=( "-y" "-o" "Dpkg::Options::=--force-confdef" "-o" "Dpkg::Options::=--force-confold" )
termux_apt() { apt-get "${TERMUX_APT_OPTS[@]}" "$@"; }
# -------------------------
# Android info
# -------------------------
get_android_sdk() { getprop ro.build.version.sdk 2>/dev/null || true; }
get_android_release() { getprop ro.build.version.release 2>/dev/null || true; }
ANDROID_SDK="$(get_android_sdk)"
ANDROID_REL="$(get_android_release)"
# -------------------------
# Wakelock (Termux:API)
# -------------------------
WAKELOCK_HELD=0
acquire_wakelock() {
if have termux-wake-lock; then
if termux-wake-lock; then
WAKELOCK_HELD=1
ok "Wakelock acquired (termux-wake-lock)."
else
warn "Failed to acquire wakelock (termux-wake-lock)."
fi
else
warn "termux-wake-lock not available. Install: pkg install termux-api + Termux:API app."
fi
}
release_wakelock() {
if [[ "$WAKELOCK_HELD" -eq 1 ]] && have termux-wake-unlock; then
termux-wake-unlock || true
ok "Wakelock released (termux-wake-unlock)."
fi
}
# -------------------------
# One-time repo selector
# -------------------------
step_termux_repo_select_once() {
local stamp="$STATE_DIR/stamp.termux_repo_selected"
[[ -f "$stamp" ]] && return 0
if ! have termux-change-repo; then
warn "termux-change-repo not found; skipping mirror selection."
return 0
fi
if [[ -r /dev/tty ]]; then
printf "\n${YEL}[iiab] One-time setup:${RST} Select a nearby Termux repository mirror for faster downloads.\n" >&2
local ans="Y"
printf "[iiab] Launch termux-change-repo now? [Y/n]: " > /dev/tty
if ! read -r ans < /dev/tty; then
warn "No interactive TTY available; skipping mirror selection (run script directly to be prompted)."
return 0
fi
ans="${ans:-Y}"
if [[ "$ans" =~ ^[Yy]$ ]]; then
# Logging redirects stdout/stderr to pipes, which can break the UI.
# Run it using /dev/tty and the original console fds (3/4).
if [[ -r /dev/tty ]]; then
termux-change-repo </dev/tty >&3 2>&4 || true
else
termux-change-repo || true
fi
ok "Mirror selection completed (or skipped inside the UI)."
else
warn "Mirror selection skipped by user."
fi
date > "$stamp"
return 0
fi
warn "No /dev/tty available; skipping mirror selection."
return 0
}
# -------------------------
# Baseline packages
# -------------------------
step_termux_base() {
local stamp="$STATE_DIR/stamp.termux_base"
if [[ -f "$stamp" ]]; then
ok "Termux baseline already prepared (stamp found)."
return 0
fi
log "Updating Termux packages (noninteractive) and installing baseline dependencies..."
export DEBIAN_FRONTEND=noninteractive
termux_apt update || true
termux_apt upgrade || true
termux_apt install \
ca-certificates \
curl \
coreutils \
grep \
sed \
gawk \
openssh \
proot proot-distro \
android-tools \
termux-api \
|| true
if have proot-distro && \
have adb && have termux-notification && \
have termux-dialog; then
ok "Termux baseline ready."
date > "$stamp"
else
warn_red "Baseline incomplete (missing proot-distro/adb/termux-notification). Not stamping; rerun later."
fi
}

View File

@ -0,0 +1,79 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# -------------------------
# Debian bootstrap
# -------------------------
debian_exists() {
have proot-distro || return 1
proot-distro login debian -- true >/dev/null 2>&1
}
ensure_proot_distro() {
if have proot-distro; then return 0; fi
warn "proot-distro not found; attempting to install..."
termux_apt install proot-distro || true
have proot-distro
}
proot_install_debian_safe() {
local out rc
set +e
out="$(proot-distro install debian 2>&1)"
rc=$?
set -e
if [[ $rc -eq 0 ]]; then return 0; fi
if echo "$out" | grep -qi "already installed"; then
warn "Debian is already installed; continuing."
return 0
fi
printf "%s\n" "$out" >&2
return $rc
}
step_debian_bootstrap_default() {
if ! ensure_proot_distro; then
warn "Unable to ensure proot-distro; skipping Debian bootstrap."
return 0
fi
if [[ "$RESET_DEBIAN" -eq 1 ]]; then
warn "Reset requested: reinstalling Debian (clean environment)..."
if proot-distro help 2>/dev/null | grep -qE '\breset\b'; then
proot-distro reset debian || true
else
if debian_exists; then proot-distro remove debian || true; fi
proot_install_debian_safe || true
fi
else
if debian_exists; then
ok "Debian already present in proot-distro. Not reinstalling."
else
log "Installing Debian (proot-distro install debian)..."
proot_install_debian_safe || true
fi
fi
log "Installing minimal tools inside Debian (noninteractive)..."
if ! debian_exists; then
warn_red "Debian is not available in proot-distro (install may have failed). Rerun later."
return 0
fi
local rc=0
set +e
proot-distro login debian -- bash -lc '
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold \
install ca-certificates curl coreutils
'
rc=$?
set -e
if [[ $rc -eq 0 ]]; then
ok "Debian bootstrap complete."
else
warn_red "Debian bootstrap incomplete (inner apt-get failed, rc=$rc)."
warn "You can retry later with: proot-distro login debian"
fi
}

View File

@ -0,0 +1,129 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# -------------------------
# Termux:API notifications + prompts
# -------------------------
NOTIF_BASE_ID=9400
NOTIF_SEQ=0
LAST_NOTIF_ID=""
# Termux:API sanity check (notifications)
termux_api_ready() {
have termux-notification || return 1
# Quick probe: some setups fail until Termux:API app is installed/allowed.
local msg="iiab test notification"
if ! termux-notification --id "$NOTIF_BASE_ID" --title "iiab" --content "$msg" --priority max --sound >/dev/null 2>&1; then
return 1
fi
if have termux-notification-remove; then
termux-notification-remove "$NOTIF_BASE_ID" >/dev/null 2>&1 || true
fi
return 0
}
sanitize_timeout() {
# Ensure TIMEOUT_SECS is a positive integer
if ! [[ "${TIMEOUT_SECS:-}" =~ ^[0-9]+$ ]]; then
warn "Invalid --timeout='${TIMEOUT_SECS:-}'. Falling back to 180."
TIMEOUT_SECS=180
elif (( TIMEOUT_SECS < 5 )); then
warn "Very low --timeout=${TIMEOUT_SECS}. Forcing minimum 5 seconds."
TIMEOUT_SECS=5
fi
}
cleanup_notif() {
have termux-notification-remove || return 0
termux-notification-remove "$NOTIF_BASE_ID" >/dev/null 2>&1 || true
if [[ -n "${LAST_NOTIF_ID:-}" ]]; then
termux-notification-remove "$LAST_NOTIF_ID" >/dev/null 2>&1 || true
fi
}
notify_ask_one() {
# args: key title content
local key="$1" title="$2" content="$3"
local out="$ADB_STATE_DIR/$key.reply"
rm -f "$out"
# Fresh notification each time + sound (use a new ID so Android plays sound each time)
local nid
nid=$((NOTIF_BASE_ID + 1 + NOTIF_SEQ))
NOTIF_SEQ=$((NOTIF_SEQ + 1))
LAST_NOTIF_ID="$nid"
if have termux-notification-remove; then
termux-notification-remove "$nid" >/dev/null 2>&1 || true
fi
# Direct reply: Termux:API injects the user input into $REPLY for the action.
# Write it to a known file, then the main loop reads it.
local action
action="sh -lc 'umask 077; printf \"%s\" \"\$REPLY\" > \"${out}\"'"
termux-notification \
--id "$nid" \
--ongoing \
--priority max \
--title "$title" \
--content "$content" \
--sound \
--button1 "Answer" \
--button1-action "$action" \
|| return 1
local start now reply
start="$(date +%s)"
while true; do
if [[ -f "$out" ]]; then
reply="$(tr -d '\r\n' < "$out" 2>/dev/null || true)"
rm -f "$out" >/dev/null 2>&1 || true
if have termux-notification-remove; then
termux-notification-remove "$nid" >/dev/null 2>&1 || true
fi
printf '%s' "$reply"
return 0
fi
now="$(date +%s)"
if (( now - start >= TIMEOUT_SECS )); then
if have termux-notification-remove; then
termux-notification-remove "$nid" >/dev/null 2>&1 || true
fi
return 1
fi
sleep 1
done
}
ask_port_5digits() {
# args: key title
local key="$1" title="$2" v=""
while true; do
v="$(notify_ask_one "$key" "$title" "(5 digits)")" || return 1
v="${v//[[:space:]]/}"
[[ "$v" =~ ^[0-9]{5}$ ]] || continue
echo "$v"
return 0
done
}
ask_code_6digits() {
local v=""
while true; do
v="$(notify_ask_one code "PAIR CODE" "(6 digits)")" || return 1
v="${v//[[:space:]]/}"
[[ -n "$v" ]] || continue
[[ "$v" =~ ^[0-9]+$ ]] || continue
# Normalize to 6 digits (allow missing leading zeros)
if ((${#v} < 6)); then
v="$(printf "%06d" "$v")"
fi
[[ "$v" =~ ^[0-9]{6}$ ]] || continue
echo "$v"
return 0
done
}

View File

@ -0,0 +1,272 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# -------------------------
# ADB wireless pair/connect wizard
# -------------------------
# Local stamp so we can detect "connect-only" misuse after reinstall/clear-data.
ADB_PAIRED_STAMP="${ADB_STATE_DIR}/stamp.adb_paired"
adb_hostkey_fingerprint() {
# Returns a stable fingerprint for THIS Termux install's adb host key.
local pub="${HOME}/.android/adbkey.pub"
[[ -r "$pub" ]] || return 1
if have sha256sum; then
sha256sum "$pub" | awk '{print $1}'
elif have shasum; then
shasum -a 256 "$pub" | awk '{print $1}'
elif have openssl; then
openssl dgst -sha256 "$pub" 2>/dev/null | awk '{print $2}'
elif have md5sum; then
md5sum "$pub" | awk '{print $1}'
else
return 1
fi
}
adb_stamp_write() {
# args: mode serial
local mode="$1" serial="$2" fp=""
fp="$(adb_hostkey_fingerprint 2>/dev/null || true)"
{
echo "ts=$(date -Is 2>/dev/null || date || true)"
echo "mode=${mode}"
echo "host=${HOST}"
echo "serial=${serial}"
echo "connect_port=${CONNECT_PORT:-}"
echo "hostkey_fp=${fp}"
} >"$ADB_PAIRED_STAMP" 2>/dev/null || true
chmod 600 "$ADB_PAIRED_STAMP" 2>/dev/null || true
}
adb_stamp_read_fp() {
[[ -r "$ADB_PAIRED_STAMP" ]] || return 1
sed -n 's/^hostkey_fp=//p' "$ADB_PAIRED_STAMP" 2>/dev/null | head -n 1
}
adb_warn_connect_only_if_suspicious() {
# Called only in connect-only flows.
local cur_fp old_fp
cur_fp="$(adb_hostkey_fingerprint 2>/dev/null || true)"
old_fp="$(adb_stamp_read_fp 2>/dev/null || true)"
if [[ ! -f "$ADB_PAIRED_STAMP" ]]; then
warn "connect-only assumes THIS Termux install has been paired before."
warn "No local pairing stamp found ($ADB_PAIRED_STAMP)."
warn "If you reinstalled Termux / cleared data / changed user, you must re-pair (run: --adb-only)."
[[ -n "$cur_fp" ]] && warn "Current ADB hostkey fingerprint: ${cur_fp:0:12}..."
return 0
fi
if [[ -n "$old_fp" && -n "$cur_fp" && "$old_fp" != "$cur_fp" ]]; then
warn_red "ADB host key changed since last pairing stamp."
warn "Old fingerprint: ${old_fp:0:12}... Current: ${cur_fp:0:12}..."
warn "Android Wireless debugging -> Paired devices: remove the old entry, then run: --adb-only"
fi
}
adb_connect_verify() {
# args: serial (HOST:PORT)
local serial="$1" out rc start now state
set +e
out="$(adb connect "$serial" 2>&1)"
rc=$?
set -e
# Always verify via `adb devices` (adb may exit 0 even on failure).
start="$(date +%s)"
while true; do
state="$(adb_device_state "$serial" || true)"
[[ "$state" == "device" ]] && { printf '%s\n' "$out"; return 0; }
now="$(date +%s)"
(( now - start >= 5 )) && break
sleep 1
done
warn_red "adb connect did not result in a usable device entry for: $serial (state='${state:-none}')."
warn "adb connect output: ${out:-<none>}"
warn "If you recently reinstalled Termux/cleared data, the phone may show an OLD paired device. Remove it and re-pair."
return 1
}
cleanup_offline_loopback() {
local keep_serial="$1" # e.g. 127.0.0.1:41313
local serial state rest
while read -r serial state rest; do
[[ -n "${serial:-}" ]] || continue
[[ "$serial" == ${HOST}:* ]] || continue
[[ "$state" == "offline" ]] || continue
[[ "$serial" == "$keep_serial" ]] && continue
adb disconnect "$serial" >/dev/null 2>&1 || true
done < <(adb devices 2>/dev/null | tail -n +2 | sed '/^[[:space:]]*$/d')
}
adb_pair_connect() {
need adb || die "Missing adb. Install: pkg install android-tools"
# Only require Termux:API when we will prompt the user
if [[ "$ONLY_CONNECT" != "1" || -z "${CONNECT_PORT:-}" ]]; then
termux_api_ready || die "Termux:API not ready."
fi
echo "[*] adb: $(adb version | head -n 1)"
adb start-server >/dev/null 2>&1 || true
if [[ "$ONLY_CONNECT" == "1" ]]; then
adb_warn_connect_only_if_suspicious
if [[ -n "$CONNECT_PORT" ]]; then
CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}"
[[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid CONNECT PORT (must be 5 digits): '$CONNECT_PORT'"
else
echo "[*] Asking CONNECT PORT..."
CONNECT_PORT="$(ask_port_5digits connect "CONNECT PORT")" || die "Timeout waiting CONNECT PORT."
fi
local serial="${HOST}:${CONNECT_PORT}"
adb disconnect "$serial" >/dev/null 2>&1 || true
echo "[*] adb connect $serial"
adb_connect_verify "$serial" >/dev/null || die "adb connect failed to $serial. Verify Wireless debugging is enabled, and pairing exists for THIS Termux install."
if [[ "$CLEANUP_OFFLINE" == "1" ]]; then
cleanup_offline_loopback "$serial"
fi
echo "[*] Devices:"
adb devices -l
echo "[*] ADB check (shell):"
adb -s "$serial" shell sh -lc 'echo "it worked: adb shell is working"; id' || true
adb_stamp_write "connect-only" "$serial"
cleanup_notif
ok "ADB connected (connect-only): $serial"
return 0
fi
if [[ -n "$CONNECT_PORT" ]]; then
CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}"
[[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid --connect-port (must be 5 digits): '$CONNECT_PORT'"
else
echo "[*] Asking CONNECT PORT..."
CONNECT_PORT="$(ask_port_5digits connect "CONNECT PORT")" || die "Timeout waiting CONNECT PORT."
fi
echo "[*] Asking PAIR PORT..."
local pair_port
pair_port="$(ask_port_5digits pair "PAIR PORT")" || die "Timeout waiting PAIR PORT."
echo "[*] Asking PAIR CODE..."
local code
code="$(ask_code_6digits)" || die "Timeout waiting PAIR CODE."
local serial="${HOST}:${CONNECT_PORT}"
adb disconnect "$serial" >/dev/null 2>&1 || true
echo "[*] adb pair ${HOST}:${pair_port}"
printf '%s\n' "$code" | adb pair "${HOST}:${pair_port}" || die "adb pair failed. Verify PAIR PORT and PAIR CODE (and that the pairing dialog is showing)."
echo "[*] adb connect $serial"
adb_connect_verify "$serial" >/dev/null || die "adb connect failed after pairing. Re-check CONNECT PORT and Wireless debugging."
if [[ "$CLEANUP_OFFLINE" == "1" ]]; then
cleanup_offline_loopback "$serial"
fi
echo "[*] Devices:"
adb devices -l
echo "[*] ADB check (shell):"
adb -s "$serial" shell sh -lc 'echo "it worked: adb shell is working"; getprop ro.product.model; getprop ro.build.version.release' || true
adb_stamp_write "paired" "$serial"
cleanup_notif
ok "ADB connected: $serial"
}
# Return state for an exact serial (e.g. "device", "offline", empty)
adb_device_state() {
local s="$1"
adb devices 2>/dev/null | awk -v s="$s" 'NR>1 && $1==s {print $2; exit}'
}
# Return first loopback serial in "device" state (e.g. 127.0.0.1:41313)
adb_any_loopback_device() {
adb devices 2>/dev/null | awk -v h="$HOST" '
NR>1 && $2=="device" && index($1, h":")==1 {print $1; found=1; exit}
END { exit (found ? 0 : 1) }
'
}
# Pick the loopback serial we will operate on:
# - If CONNECT_PORT is set, require that exact HOST:PORT to be in "device" state.
# - Otherwise, return the first loopback device.
adb_pick_loopback_serial() {
if [[ -n "${CONNECT_PORT:-}" ]]; then
local p="${CONNECT_PORT//[[:space:]]/}"
[[ "$p" =~ ^[0-9]{5}$ ]] || return 1
local target="${HOST}:${p}"
[[ "$(adb_device_state "$target")" == "device" ]] && { echo "$target"; return 0; }
return 1
fi
adb_any_loopback_device
}
# If already connected, avoid re-pairing/re-connecting prompts (useful for --all),
# BUT only consider loopback/target connections as "already connected".
adb_pair_connect_if_needed() {
need adb || die "Missing adb. Install: pkg install android-tools"
adb start-server >/dev/null 2>&1 || true
local serial=""
# If user provided a connect-port, insist on that exact target serial.
if [[ -n "${CONNECT_PORT:-}" ]]; then
CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}"
[[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid --connect-port (must be 5 digits): '$CONNECT_PORT'"
local target="${HOST}:${CONNECT_PORT}"
if [[ "$(adb_device_state "$target")" == "device" ]]; then
ok "ADB already connected to target: $target (skipping pair/connect)."
return 0
fi
# Try connect-only first (in case it was already paired before)
adb connect "$target" >/dev/null 2>&1 || true
if [[ "$(adb_device_state "$target")" == "device" ]]; then
ok "ADB connected to target: $target (connect-only succeeded; skipping pair)."
return 0
fi
# Not connected: run full wizard (pair+connect)
adb_pair_connect
return $?
fi
# No explicit port: only skip if we already have a loopback device connected.
if serial="$(adb_any_loopback_device 2>/dev/null)"; then
ok "ADB already connected (loopback): $serial (skipping pair/connect)."
return 0
fi
adb_pair_connect
}
require_adb_connected() {
need adb || { warn_red "Missing adb. Install: pkg install android-tools"; return 1; }
adb start-server >/dev/null 2>&1 || true
if ! adb_pick_loopback_serial >/dev/null 2>&1; then
warn_red "No ADB device connected."
warn "If already paired before: run --connect-only [PORT]."
warn "Otherwise: run --adb-only to pair+connect."
return 1
fi
return 0
}
adb_loopback_serial_or_die() {
local s
s="$(adb_pick_loopback_serial 2>/dev/null)" || return 1
echo "$s"
}

View File

@ -0,0 +1,192 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# PPK / phantom-process checks and tuning via ADB (best-effort)
# Moved out of 99_main.sh to keep it as an orchestrator.
# -------------------------
# PPK / phantom-process tuning (best-effort)
# -------------------------
ppk_fix_via_adb() {
need adb || die "Missing adb. Install: pkg install android-tools"
local serial
if ! serial="$(adb_pick_loopback_serial)"; then
CHECK_NO_ADB=1
warn "No ADB loopback device connected (expected ${HOST}:${CONNECT_PORT:-*})."
return 1
fi
ok "Using ADB device: $serial"
log "Setting PPK: max_phantom_processes=256"
# Some Android versions may ignore/rename this; we don't hard-fail.
adb -s "$serial" shell sh -lc '
set -e
# Persist device_config changes if supported
if command -v device_config >/dev/null 2>&1; then
device_config set_sync_disabled_for_tests persistent >/dev/null 2>&1 || true
device_config put activity_manager max_phantom_processes 256 || true
echo "dumpsys effective max_phantom_processes:"
dumpsys activity settings 2>/dev/null | grep -i "max_phantom_processes=" | head -n 1 || true
else
echo "device_config not found; skipping."
fi
' || true
ok "PPK set done (best effort)."
return 0
}
# Prefer Android 14+ feature-flag override if present:
# OFF -> true, ON -> false
adb_get_child_restrictions_flag() {
local serial="$1"
adb -s "$serial" shell getprop persist.sys.fflag.override.settings_enable_monitor_phantom_procs \
2>/dev/null | tr -d '\r' || true
}
# -------------------------
# Check readiness (best-effort)
# -------------------------
check_readiness() {
# Reset exported check signals so final_advice() never sees stale values
CHECK_NO_ADB=0
CHECK_SDK=""
CHECK_MON=""
CHECK_PPK=""
need adb || die "Missing adb. Install: pkg install android-tools"
adb start-server >/dev/null 2>&1 || true
local serial
if ! serial="$(adb_pick_loopback_serial)"; then
CHECK_NO_ADB=1
# Best-effort: keep local SDK so final_advice can still warn on A12-13.
CHECK_SDK="${ANDROID_SDK:-}"
warn_red "No ADB device connected. Cannot run checks."
warn "If already paired before: run --connect-only [PORT]."
warn "Otherwise: run --adb-only to pair+connect."
return 1
fi
ok "Check using ADB device: $serial"
local dev_enabled sdk rel mon mon_fflag ds ppk_eff
sdk="$(adb -s "$serial" shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\r' || true)"
rel="$(adb -s "$serial" shell getprop ro.build.version.release 2>/dev/null | tr -d '\r' || true)"
dev_enabled="$(adb -s "$serial" shell settings get global development_settings_enabled 2>/dev/null | tr -d '\r' || true)"
mon_fflag="$(adb_get_child_restrictions_flag "$serial")"
if [[ "$mon_fflag" == "true" || "$mon_fflag" == "false" ]]; then
mon="$mon_fflag"
else
mon="$(adb -s "$serial" shell settings get global settings_enable_monitor_phantom_procs 2>/dev/null | tr -d '\r' || true)"
fi
# Get effective value from dumpsys (device_config get may return 'null' even when an effective value exists)
ds="$(adb -s "$serial" shell dumpsys activity settings 2>/dev/null | tr -d '\r' || true)"
ppk_eff="$(printf '%s\n' "$ds" | awk -F= '/max_phantom_processes=/{print $2; exit}' | tr -d '[:space:]' || true)"
# Export check signals for the final advice logic
CHECK_SDK="${sdk:-}"
CHECK_MON="${mon:-}"
CHECK_PPK="${ppk_eff:-}"
log " Android release=${rel:-?} sdk=${sdk:-?}"
if [[ "${dev_enabled:-}" == "1" ]]; then
ok " Developer options: enabled (development_settings_enabled=1)"
elif [[ -n "${dev_enabled:-}" ]]; then
warn " Developer options: unknown/disabled (development_settings_enabled=${dev_enabled})"
else
warn " Developer options: unreadable (permission/ROM differences)."
fi
# Android 14+ only: "Disable child process restrictions" proxy flag
if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
if [[ "${mon:-}" == "false" ]]; then
ok " Child restrictions: OK (monitor=false)"
elif [[ "${mon:-}" == "true" ]]; then
warn " Child restrictions: NOT OK (monitor=true)"
elif [[ -n "${mon:-}" && "${mon:-}" != "null" ]]; then
warn " Child restrictions: unknown (${mon})"
else
warn " Child restrictions: unreadable/absent"
fi
fi
# Android 12-13 only: PPK matters (use effective value from dumpsys)
if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
if [[ "${ppk_eff:-}" =~ ^[0-9]+$ ]]; then
if (( ppk_eff >= 256 )); then
ok " PPK: OK (max_phantom_processes=${ppk_eff})"
else
warn " PPK: low (max_phantom_processes=${ppk_eff}) -> suggest: run --ppk-only"
fi
else
warn " PPK: unreadable (dumpsys max_phantom_processes='${ppk_eff:-}')."
fi
fi
log " dumpsys (phantom-related):"
printf '%s\n' "$ds" | grep -i phantom || true
if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
log " Note: On A14+, max_phantom_processes is informational; rely on Child restrictions."
fi
if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 34 )) && [[ "${mon:-}" == "false" ]]; then
log " Child restrictions OK."
fi
return 0
}
self_check_android_flags() {
have adb || return 0
adb start-server >/dev/null 2>&1 || true
local serial sdk rel mon mon_fflag ds ppk_eff
serial="$(adb_pick_loopback_serial 2>/dev/null)" || {
warn "ADB: no loopback device connected. Tip: run --adb-only (pair+connect) or --check for more info."
return 0
}
sdk="$(adb -s "$serial" shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\r' || true)"
rel="$(adb -s "$serial" shell getprop ro.build.version.release 2>/dev/null | tr -d '\r' || true)"
log " Android flags (quick): release=${rel:-?} sdk=${sdk:-?} serial=$serial"
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
mon_fflag="$(adb_get_child_restrictions_flag "$serial")"
if [[ "$mon_fflag" == "true" || "$mon_fflag" == "false" ]]; then
mon="$mon_fflag"
else
mon="$(adb -s "$serial" shell settings get global settings_enable_monitor_phantom_procs 2>/dev/null | tr -d '\r' || true)"
fi
if [[ "$mon" == "false" ]]; then
ok " Child restrictions: OK (monitor=false)"
elif [[ "$mon" == "true" ]]; then
warn " Child restrictions: NOT OK (monitor=true) -> check Developer Options"
else
warn " Child restrictions: unknown/unreadable (monitor='${mon:-}')"
fi
fi
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
ds="$(adb -s "$serial" shell dumpsys activity settings 2>/dev/null | tr -d '\r' || true)"
ppk_eff="$(printf '%s\n' "$ds" | awk -F= '/max_phantom_processes=/{print $2; exit}' | tr -d '[:space:]' || true)"
if [[ "$ppk_eff" =~ ^[0-9]+$ ]]; then
if (( ppk_eff >= 256 )); then
ok " PPK: OK (max_phantom_processes=$ppk_eff)"
else
warn " PPK: low (max_phantom_processes=$ppk_eff) -> suggest: --ppk-only"
fi
else
warn " PPK: unreadable (max_phantom_processes='${ppk_eff:-}')"
fi
fi
# Avoid redundant tip when we're already in --check mode.
if [[ "${MODE:-}" != "check" && "${MODE:-}" != "all" ]]; then
log " Tip: run --check for full details."
fi
}

View File

@ -0,0 +1,358 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# 0_termux-setupv2.sh
# - Termux bootstrap (packages, wakelock)
# - proot-distro + Debian bootstrap
# - ADB wireless pair/connect via Termux:API notifications (no Shizuku)
# - Optional PPK / phantom-process tweaks (best-effort)
# -------------------------
# Defaults
# -------------------------
# NOTE: Core defaults live in 00_lib_common.sh to guarantee availability for all modules.
# Ensure state directories exist (safe even if user overrides via environment).
mkdir -p "$STATE_DIR" "$ADB_STATE_DIR" "$LOG_DIR" 2>/dev/null || true
RESET_DEBIAN=0
ONLY_CONNECT=0
CHECK_NO_ADB=0
CHECK_SDK=""
CHECK_MON=""
CHECK_PPK=""
# Modes are mutually exclusive (baseline is default)
MODE="baseline" # baseline|with-adb|adb-only|connect-only|ppk-only|check|all
MODE_SET=0
CONNECT_PORT_FROM="" # "", "flag", "positional"
usage() {
cat <<'EOF'
Usage:
./0_termux-setupv2.sh
-> Termux baseline + Debian bootstrap (idempotent). No ADB prompts.
./0_termux-setupv2.sh --with-adb
-> Termux baseline + Debian bootstrap + ADB pair/connect if needed (skips if already connected).
./0_termux-setupv2.sh --adb-only [--connect-port PORT]
-> Only ADB pair/connect if needed (no Debian; skips if already connected).
Tip: --connect-port skips the CONNECT PORT prompt (youll still be asked for PAIR PORT + PAIR CODE).
./0_termux-setupv2.sh --connect-only [CONNECT_PORT]
-> Connect-only (no pairing). Use this after the device was already paired before.
./0_termux-setupv2.sh --ppk-only
-> Set PPK only: max_phantom_processes=256 (requires ADB already connected).
Android 14-16 usually achieve this via "Disable child process restrictions" in Developer Options.
./0_termux-setupv2.sh --check
-> Check readiness: developer options flag (if readable),
(Android 14+) "Disable child process restrictions" proxy flag, and (Android 12-13) PPK effective value.
./0_termux-setupv2.sh --all
-> baseline + Debian + ADB pair/connect if needed + (Android 12-13 only) apply --ppk + run --check.
Optional:
--connect-port 41313 (5 digits) Skip CONNECT PORT prompt used with --adb-only
--timeout 180 Seconds to wait per prompt
--reset-debian Reset (reinstall) Debian in proot-distro
--no-log Disable logging
--log-file /path/file Write logs to a specific file
--debug Extra logs
Notes:
- ADB prompts require: `pkg install termux-api` + Termux:API app installed + notification permission.
- Wireless debugging must be enabled.
- This script never uses adb root.
EOF
}
trap 'cleanup_notif >/dev/null 2>&1 || true; release_wakelock >/dev/null 2>&1 || true' EXIT INT TERM
# NOTE: Termux:API prompts live in 40_mod_termux_api.sh
# -------------------------
# Self-check
# -------------------------
self_check() {
log "Self-check summary:"
log " Android release=${ANDROID_REL:-?} sdk=${ANDROID_SDK:-?}"
if have proot-distro; then
log " proot-distro: present"
log " proot-distro list:"
proot-distro list 2>/dev/null | sed 's/^/ /' || true
if debian_exists; then ok " Debian: present"; else warn " Debian: not present"; fi
else
warn " proot-distro: not present"
fi
if have adb; then
log " adb: present"
adb devices -l 2>/dev/null | sed 's/^/ /' || true
local serial
# renable in need for verbose output.
# if serial="$(adb_pick_loopback_serial 2>/dev/null)"; then
# log " adb shell id (first device):"
# adb -s "$serial" shell id 2>/dev/null | sed 's/^/ /' || true
# fi
else
warn " adb: not present"
fi
# Quick Android flags check (best-effort; no prompts)
self_check_android_flags || true
if have termux-wake-lock; then ok " Termux:API wakelock: available"; else warn " Termux:API wakelock: not available"; fi
if have termux-notification; then ok " Termux:API notifications: command present"; else warn " Termux:API notifications: missing"; fi
}
final_advice() {
# 1) Android-related warnings (only meaningful if we attempted checks)
local sdk="${CHECK_SDK:-${ANDROID_SDK:-}}"
local adb_connected=0
local serial="" mon="" mon_fflag=""
# Best-effort: detect whether an ADB loopback device is already connected.
# (We do NOT prompt/pair here; we only check current state.)
if have adb; then
adb start-server >/dev/null 2>&1 || true
if adb_pick_loopback_serial >/dev/null 2>&1; then
adb_connected=1
serial="$(adb_pick_loopback_serial 2>/dev/null || true)"
fi
fi
# Baseline safety gate:
# On Android 12-13 (SDK 31-33), IIAB/proot installs can fail if PPK is low (often 32).
# Baseline mode does NOT force ADB pairing nor run check_readiness(), so PPK may be unknown.
# If PPK is not determined, suggest running --all BEFORE telling user to proceed to proot-distro.
if [[ "$MODE" == "baseline" ]]; then
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
# If we didn't run checks, CHECK_PPK will be empty. Even with adb_connected=1, baseline
# still doesn't populate CHECK_PPK unless user ran --check/--all.
if [[ "${CHECK_PPK:-}" != "" && "${CHECK_PPK:-}" =~ ^[0-9]+$ ]]; then
: # PPK determined -> ok to continue with normal advice below
else
warn "Android 12-13: PPK value hasn't been verified (max_phantom_processes may be low, e.g. 32)."
warn "Before starting the IIAB install, run the complete setup so it can apply/check PPK=256; otherwise the installation may fail:"
ok " ./0_termux-setupv2.sh --all"
return 0
fi
elif [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
# On Android 14+, rely on "Disable child process restrictions"
# Proxy signals: settings_enable_monitor_phantom_procs (or the fflag override).
# Baseline does not run check_readiness(), so CHECK_MON is usually empty.
if [[ "${CHECK_MON:-}" == "false" ]]; then
: # Verified OK (rare in baseline) -> continue
else
# If ADB is already connected, try to read the flag best-effort (no prompts).
if [[ "$adb_connected" -eq 1 && -n "${serial:-}" ]]; then
mon_fflag="$(adb_get_child_restrictions_flag "$serial")"
if [[ "$mon_fflag" == "true" || "$mon_fflag" == "false" ]]; then
mon="$mon_fflag"
else
mon="$(adb -s "$serial" shell settings get global settings_enable_monitor_phantom_procs 2>/dev/null | tr -d '\r' || true)"
fi
fi
if [[ "${mon:-}" == "false" ]]; then
: # Restrictions already disabled -> ok to continue
else
if [[ "${mon:-}" == "true" ]]; then
warn "Android 14+: child process restrictions appear ENABLED (monitor=true)."
else
warn "Android 14+: child process restrictions haven't been verified (monitor flag unreadable/unknown)."
fi
warn "Before starting the IIAB install, run the complete setup (--all) so it can guide you to verify such setting; otherwise the installation may fail:"
ok " ./0_termux-setupv2.sh --all"
return 0
fi
fi
fi
fi
if [[ "${CHECK_NO_ADB:-0}" -eq 1 ]]; then
# If we could not check, still warn on A12-13 because PPK is critical there
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
warn "A12-13: verify PPK=256 before installing IIAB."
fi
else
# A14+ child restrictions proxy (only if readable)
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 34 )) && [[ "${CHECK_MON:-}" == "true" ]]; then
warn "A14+: disable child process restrictions before installing IIAB."
fi
# Only warn about PPK on A12-13 (A14+ uses child restrictions)
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
if [[ "${CHECK_PPK:-}" =~ ^[0-9]+$ ]] && (( CHECK_PPK < 256 )); then
warn "PPK is low (${CHECK_PPK}); consider --ppk-only."
fi
fi
fi
# 2) Debian “next step” should only be shown for modes that actually bootstrap Debian
case "$MODE" in
baseline|with-adb|all)
if debian_exists; then
ok "Next: proot-distro login debian"
else
warn "Debian not present. Run: proot-distro install debian"
fi
;;
*)
# adb-only/connect-only/ppk-only/check: do not suggest Debian login as a generic ending
;;
esac
}
# -------------------------
# Args
# -------------------------
set_mode() {
local new="$1"
if [[ "$MODE_SET" -eq 1 ]]; then
die "Modes are mutually exclusive. Already set: --${MODE}. Tried: --${new}"
fi
MODE="$new"
MODE_SET=1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--with-adb) set_mode "with-adb"; shift ;;
--adb-only) set_mode "adb-only"; shift ;;
--connect-only)
set_mode "connect-only"
ONLY_CONNECT=1
# Optional positional port (5 digits)
if [[ "${2:-}" =~ ^[0-9]{5}$ ]]; then
[[ -n "${CONNECT_PORT_FROM:-}" && "${CONNECT_PORT_FROM}" != "positional" ]] && \
die "CONNECT PORT specified twice (positional + --connect-port). Use only one."
CONNECT_PORT="$2"
CONNECT_PORT_FROM="positional"
shift 2
else
shift
fi
;;
--ppk-only) set_mode "ppk-only"; shift ;;
--check) set_mode "check"; shift ;;
--all) set_mode "all"; shift ;;
--connect-port)
[[ -n "${CONNECT_PORT_FROM:-}" && "${CONNECT_PORT_FROM}" != "flag" ]] && \
die "CONNECT PORT specified twice (positional + --connect-port). Use only one."
CONNECT_PORT="${2:-}"
CONNECT_PORT_FROM="flag"
shift 2
;;
--timeout) TIMEOUT_SECS="${2:-180}"; shift 2 ;;
--host) HOST="${2:-127.0.0.1}"; shift 2 ;;
--reset-debian|--clean-debian) RESET_DEBIAN=1; shift ;;
--no-log) LOG_ENABLED=0; shift ;;
--log-file) LOG_FILE="${2:-}"; shift 2 ;;
--debug) DEBUG=1; shift ;;
-h|--help) usage; exit 0 ;;
*) shift ;;
esac
done
validate_args() {
if [[ -n "${CONNECT_PORT:-}" ]]; then
CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}"
[[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid --connect-port (must be 5 digits): '$CONNECT_PORT'"
case "$MODE" in
adb-only|with-adb|connect-only|ppk-only|check|all) : ;;
baseline)
log "--connect-port requires an ADB mode."
die "Use along with: --adb-only / --with-adb / --connect-only / --check / --ppk-only / --all"
;;
*)
die "--connect-port is not valid with mode=$MODE"
;;
esac
fi
}
# -------------------------
# Main flows
# -------------------------
main() {
setup_logging "$@"
validate_args
sanitize_timeout
acquire_wakelock
case "$MODE" in
baseline)
step_termux_repo_select_once
step_termux_base
step_debian_bootstrap_default
;;
with-adb)
step_termux_repo_select_once
step_termux_base
step_debian_bootstrap_default
adb_pair_connect_if_needed
;;
adb-only)
step_termux_base
adb_pair_connect_if_needed
;;
connect-only)
step_termux_base
adb_pair_connect
;;
ppk-only)
# No baseline, no Debian. Requires adb already available + connected.
require_adb_connected || exit 1
ppk_fix_via_adb || true
;;
check)
step_termux_base
check_readiness || true
;;
all)
step_termux_repo_select_once
step_termux_base
step_debian_bootstrap_default
adb_pair_connect_if_needed
# Android 12-13 only (SDK 31-33): apply PPK tuning automatically
if [[ "${ANDROID_SDK:-}" =~ ^[0-9]+$ ]] && (( ANDROID_SDK >= 31 && ANDROID_SDK <= 33 )); then
log "Android SDK=${ANDROID_SDK} detected -> applying --ppk automatically (12-13 rule)."
ppk_fix_via_adb || true
else
log "Android SDK=${ANDROID_SDK:-?} -> skipping auto-PPK (only for Android 12-13)."
fi
check_readiness || true
;;
*)
die "Unknown MODE='$MODE'"
;;
esac
self_check
ok "0_termux-setupv2.sh completed (mode=$MODE)."
log "---- Mode list ----"
log "Connect-only --connect-only [PORT]"
log "Pair+connect --adb-only [--connect-port PORT]"
log "Check --check"
log "Apply PPK --ppk-only"
log "Base+Debian+Pair+connect --with-adb"
log "Full run --all"
log "Reset Debian --reset-debian"
log "-------------------"
final_advice
}
main "$@"

View File

@ -0,0 +1,27 @@
# termux-setup modules
This project is maintained as multiple Bash "modules" that are bundled into a single script:
`0_termux-setup_v2.sh` (the file served for: `curl ... | bash`).
## Rules
- Modules MUST NOT include a shebang (`#!...`).
- Modules SHOULD NOT run top-level code (prefer functions), except `99_main.sh`.
- Do not add `set -euo pipefail` in modules (the bundle already sets it once).
- Keep module names stable and ordered via `manifest.sh`.
Recommended header for every module:
```
# termux-setup module.
# DO NOT add a shebang or "set -euo pipefail" here.
# Keep only function/variable definitions (no top-level execution).
# See: termux-setup/README.md
```
## Rebuild:
```
cd termux-setup
bash build_bundle.sh
```

View File

@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
# build_bundle.sh
# Bundles modules listed in manifest.sh into ../0_termux-setup_v2.sh
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$SCRIPT_DIR"
MANIFEST="${ROOT_DIR}/manifest.sh"
[[ -f "$MANIFEST" ]] || { echo "[!] Missing manifest.sh at: $MANIFEST" >&2; exit 1; }
# Modules live next to build_bundle.sh (same dir)
MOD_DIR="$ROOT_DIR"
PARENT_DIR="$(cd -- "${ROOT_DIR}/.." && pwd)"
OUT_DIR="${ROOT_DIR}/dist"
OUT_FILE="${PARENT_DIR}/0_termux-setup_v2.sh"
TMP_FILE="${OUT_DIR}/.0_termux-setup_v2.tmp.$RANDOM$RANDOM"
mkdir -p "$OUT_DIR"
# Load MODULES array
# shellcheck source=/dev/null
source "$MANIFEST"
# With "set -u", don't reference an unset array directly.
if ! declare -p MODULES >/dev/null 2>&1; then
echo "[!] manifest.sh did not define MODULES array." >&2
exit 1
fi
if (( ${#MODULES[@]} < 1 )); then
echo "[!] MODULES array is empty in manifest.sh." >&2
exit 1
fi
cleanup_tmp() { rm -f -- "$TMP_FILE" 2>/dev/null || true; }
trap cleanup_tmp EXIT
# Bundle header
{
echo '#!/data/data/com.termux/files/usr/bin/bash'
echo 'set -euo pipefail'
echo
echo '# -----------------------------------------------------------------------------'
echo '# GENERATED FILE: do not edit directly.'
echo '# Source modules: termux-setup/*.sh + manifest.sh'
echo '# Rebuild: (cd termux-setup && bash build_bundle.sh)'
echo '# -----------------------------------------------------------------------------'
echo
} >"$TMP_FILE"
# Append each module
for mod in "${MODULES[@]}"; do
src="${MOD_DIR}/${mod}"
if [[ ! -f "$src" ]]; then
echo "[!] Missing module: $src" >&2
exit 1
fi
# Disallow standalone scripts: modules must not have a shebang
if head -n 1 "$src" | grep -q '^#!'; then
echo "[!] Module must NOT include a shebang: $src" >&2
exit 1
fi
{
echo
echo "# ---- BEGIN ${mod} ----"
cat "$src"
echo
echo "# ---- END ${mod} ----"
echo
} >>"$TMP_FILE"
done
# Ensure final newline
printf '\n' >>"$TMP_FILE"
# Install bundle atomically
chmod 700 "$TMP_FILE" 2>/dev/null || true
mv -f -- "$TMP_FILE" "$OUT_FILE"
chmod 700 "$OUT_FILE" 2>/dev/null || true
echo "[ok] Wrote: $OUT_FILE"

View File

@ -0,0 +1,13 @@
# termux-setup manifest
# Order matters: later modules can rely on functions/vars defined earlier.
MODULES=(
"00_lib_common.sh"
"10_mod_logging.sh"
"20_mod_termux_base.sh"
"30_mod_debian.sh"
"40_mod_termux_api.sh"
"50_mod_adb.sh"
"60_mod_ppk_checks.sh"
"99_main.sh"
)

View File

@ -1,16 +0,0 @@
local.properties
.gradle/
build/
*/build/
app/release/
.idea/
*.iml
.externalNativeBuild/
*/.externalNativeBuild/
.cxx/
*/.cxx/
.DS_Store

View File

@ -1,3 +0,0 @@
[submodule "app/src/main/jni/hev-socks5-tunnel"]
path = app/src/main/jni/hev-socks5-tunnel
url = https://github.com/heiher/hev-socks5-tunnel

View File

@ -1,38 +0,0 @@
# IIAB-oA Controller
**IIAB-oA Controller** is a specialized infrastructure component for the **Internet-in-a-Box (IIAB)** ecosystem on Android. It acts as a "Walled Garden" and persistent "Watchdog" designed to keep the Termux environment alive and accessible, even on devices with aggressive power management (e.g., Oppo/ColorOS, MIUI).
## Core Capabilities
### 🛡️ Master Watchdog (Supervision Layer)
An independent foreground service dedicated to environment stability:
* **CPU & Wi-Fi Shield**: Prevents the system from putting Termux into Doze mode or disabling the Wi-Fi radio.
* **Heartbeat Pulse**: Sends a regulated API signal every 20 seconds to maintain process priority.
* **Zero-Config Protection**: Works independently of the VPN tunnel.
### 🌐 Safe Pocket Web (Network Layer)
A high-performance VPN tunnel based on the tun2socks engine:
* **Friendly URLs**: Routes traffic through internal IIAB services seamlessly.
* **Walled Garden**: Ensures a secure, filtered browsing environment.
* **Per-App Routing**: Granular control over which applications use the secure tunnel.
### 🔒 Built-in Security
* **Biometric/PIN Lock**: Authentication is strictly required before the Watchdog or VPN can be disabled.
* **Safety Check**: Prevents activation if the device lacks a secure lock method (PIN/Pattern/Fingerprint), ensuring the user is never "locked out" of their own settings.
## Acknowledgments
This project is a heavily customized spin-off of **[SocksTun](https://github.com/heiher/sockstun)** created by **[heiher](https://github.com/heiher)**.
All credit for the core native tunneling engine goes to the original author. This derivative has been re-architected to meet the specific requirements of the IIAB project.
## Technical Details
* **Current Version**: v0.1.12alpha
* **License**: **MIT License** (See [LICENSE](LICENSE) for details).
* **Compatibility**: Android 8.0 (API 26) and above.
## Disclaimer
This is a preview and demo published in the hope that it will be useful, but WITHOUT ANY WARRANTY.
---
*Maintained by IIAB Contributors - 2026*

View File

@ -1,19 +0,0 @@
Copyright (c) 2023 hev
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,84 +0,0 @@
apply plugin: 'com.android.application'
android {
namespace "org.iiab.controller"
compileSdkVersion 34
ndkVersion "26.3.11579264"
defaultConfig {
applicationId "org.iiab.controller"
minSdkVersion 24
targetSdkVersion 34
versionCode 32
versionName "v0.2.2beta"
setProperty("archivesBaseName", "$applicationId-$versionName")
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
}
externalNativeBuild {
ndkBuild {
arguments "APP_CFLAGS+=-DPKGNAME=org/iiab/controller -DCLSNAME=TProxyService -ffile-prefix-map=${rootDir}=."
arguments "APP_LDFLAGS+=-Wl,--build-id=none"
}
}
}
signingConfigs {
release {
}
}
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.release
}
debug {
minifyEnabled false
signingConfig signingConfigs.release
}
}
def propsFile = rootProject.file('store.properties')
def configName = 'release'
if (propsFile.exists() && android.signingConfigs.hasProperty(configName)) {
def props = new Properties()
props.load(new FileInputStream(propsFile))
if (props!=null && props.containsKey('storeFile')) {
android.signingConfigs[configName].storeFile = rootProject.file(props['storeFile'])
android.signingConfigs[configName].storePassword = props['storePassword']
android.signingConfigs[configName].keyAlias = props['keyAlias']
android.signingConfigs[configName].keyPassword = props['keyPassword']
}
}
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
}
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.biometric:biometric:1.1.0'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.webkit:webkit:1.12.0'
// ZXing for QR Code generation
implementation 'com.google.zxing:core:3.5.2'
}

View File

@ -1,37 +0,0 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "org.iiab.controller",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 29,
"versionName": "v0.1.33beta",
"outputFile": "org.iiab.controller-v0.1.33beta-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/org.iiab.controller-v0.1.33beta-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/org.iiab.controller-v0.1.33beta-release.dm"
]
}
],
"minSdkVersionForDexing": 24
}

View File

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.iiab.controller"
xmlns:tools="http://schemas.android.com/tools">
<!-- Android 11+ Package Visibility -->
<queries>
<package android:name="com.termux" />
<package android:name="com.termux.api" />
</queries>
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.IIABController"
android:usesCleartextTraffic="true">
<!-- VPN Service (Network Layer) -->
<service android:name=".TProxyService" android:process=":native"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true"
android:foregroundServiceType="specialUse">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="VPN service"/>
</service>
<!-- Watchdog Service (Keep-Alive Layer) -->
<service android:name=".WatchdogService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Watchdog and Heartbeat"/>
</service>
<receiver android:enabled="true" android:name=".ServiceReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<!-- VPN Recovery Receiver -->
<receiver android:name=".VpnRecoveryReceiver" android:exported="false">
<intent-filter>
<action android:name="org.iiab.controller.RECOVER_VPN" />
</intent-filter>
</receiver>
<activity android:name=".QrActivity"
android:exported="false"
android:theme="@style/Theme.TransparentQR" />
<!-- Termux Result Callback Receiver -->
<receiver android:name=".TermuxCallbackReceiver" android:exported="false">
<intent-filter>
<action android:name="org.iiab.controller.TERMUX_OUTPUT" />
</intent-filter>
</receiver>
<activity android:name=".MainActivity" android:label="@string/app_name"
android:launchMode="singleTop"
android:exported="true"
android:configChanges="orientation|screenSize|keyboardHidden|screenLayout">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".AppListActivity" android:label="@string/app_name"/>
<activity android:name=".SetupActivity" android:exported="false" />
<activity
android:name=".PortalActivity"
android:theme="@style/Theme.AppCompat.NoActionBar" />
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="com.termux.permission.RUN_COMMAND" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
android:minSdkVersion="34" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
</manifest>

View File

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="512"
viewBox="0 0 135.46667 135.46667"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4.3 (1:1.4.3+202512261035+0d15f75042)"
sodipodi:docname="IIAB-on-Android-Controller.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.71891835"
inkscape:cx="-140.48883"
inkscape:cy="188.47759"
inkscape:window-width="1918"
inkscape:window-height="1008"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs1"><inkscape:path-effect
effect="powerclip"
message=""
id="path-effect2"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false" /><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath2"><ellipse
style="display:none;fill:none;fill-opacity:0;stroke:#000000;stroke-width:0.218723;stroke-opacity:1"
id="ellipse2"
cx="203.22194"
cy="44.468861"
rx="27.890638"
ry="27.66296"
d="m 231.11258,44.468861 a 27.890638,27.66296 0 0 1 -27.89064,27.66296 27.890638,27.66296 0 0 1 -27.89064,-27.66296 27.890638,27.66296 0 0 1 27.89064,-27.66296 27.890638,27.66296 0 0 1 27.89064,27.66296 z" /><path
id="lpe_path-effect2"
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:0.218723;stroke-opacity:1"
class="powerclip"
d="M 136.73034,-22.709513 H 269.71353 V 111.64723 H 136.73034 Z m 94.38224,67.178374 a 27.890638,27.66296 0 0 0 -27.89064,-27.66296 27.890638,27.66296 0 0 0 -27.89064,27.66296 27.890638,27.66296 0 0 0 27.89064,27.66296 27.890638,27.66296 0 0 0 27.89064,-27.66296 z" /></clipPath></defs><g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-32.997666,-44.40659)"><rect
style="fill:#000000;stroke:#000000;stroke-width:0.462333"
id="rect2"
width="135.00433"
height="135.00433"
x="33.228832"
y="44.637756" /><path
style="font-size:81.7498px;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;fill:#ffffff;stroke:#ffffff;stroke-width:0.491597"
d="m 72.627453,134.58207 h 1.560831 q -0.178844,1.68277 -1.268175,2.80462 -1.089332,1.12185 -3.219217,1.12185 -2.064851,0 -3.333026,-1.47141 -1.260049,-1.47954 -1.284437,-3.92647 v -1.26818 q 0,-2.48757 1.276307,-3.9915 1.284434,-1.50393 3.479356,-1.50393 2.007944,0 3.089147,1.10559 1.081201,1.10559 1.260045,2.8534 h -1.560831 q -0.178844,-1.23566 -0.796676,-1.95104 -0.617827,-0.72351 -1.991685,-0.72351 -1.568963,0 -2.381895,1.15436 -0.812935,1.15436 -0.812935,3.04038 v 1.19501 q 0,1.7478 0.739771,2.98346 0.739769,1.22753 2.316859,1.22753 1.495799,0 2.105498,-0.69912 0.617831,-0.69913 0.821063,-1.95104 z m 3.064759,-0.72351 q 0,-1.9104 1.073072,-3.1867 1.073073,-1.28444 2.918429,-1.28444 1.84536,0 2.918432,1.26005 1.073073,1.25192 1.097461,3.12979 v 0.26827 q 0,1.91039 -1.081202,3.1867 -1.073072,1.27631 -2.918431,1.27631 -1.853486,0 -2.934689,-1.27631 -1.073072,-1.27631 -1.073072,-3.1867 z m 1.503926,0.18697 q 0,1.30882 0.61783,2.26809 0.625959,0.95925 1.886005,0.95925 1.227529,0 1.853488,-0.943 0.625959,-0.95113 0.634089,-2.25995 v -0.21136 q 0,-1.29257 -0.625959,-2.25996 -0.625959,-0.97552 -1.877878,-0.97552 -1.243786,0 -1.869745,0.97552 -0.61783,0.96739 -0.61783,2.25996 z m 12.039543,-3.38993 q -0.723509,0 -1.276304,0.39021 -0.552795,0.3902 -0.86984,1.01616 v 6.28397 h -1.503926 v -8.79593 h 1.422635 l 0.04877,1.09746 q 0.999907,-1.26005 2.625774,-1.26005 1.292563,0 2.048591,0.72351 0.764158,0.72351 0.77229,2.43068 v 5.80433 h -1.512058 v -5.77995 q 0,-1.03243 -0.455243,-1.47141 -0.447112,-0.43898 -1.300693,-0.43898 z m 9.267443,7.69034 q -0.512151,0.1626 -1.162496,0.1626 -0.837321,0 -1.430763,-0.51216 -0.593442,-0.51214 -0.593442,-1.83722 v -5.45478 h -1.609606 v -1.15437 h 1.609606 V 127.412 h 1.503928 v 2.13801 h 1.642124 v 1.15437 h -1.642124 v 5.46291 q 0,0.67474 0.292656,0.8617 0.292658,0.18698 0.674734,0.18698 0.284527,0 0.707253,-0.0975 z m 5.235286,-7.5115 q -1.47141,0 -2.00795,1.26819 v 6.24331 h -1.50393 v -8.79593 h 1.46329 l 0.0325,1.00804 q 0.72351,-1.17063 2.08923,-1.17063 0.42273,0 0.66661,0.11381 l -0.008,1.39825 q -0.3333,-0.0651 -0.73164,-0.0651 z m 1.56895,3.02412 q 0,-1.9104 1.07308,-3.1867 1.07307,-1.28444 2.91843,-1.28444 1.84535,0 2.91843,1.26005 1.07307,1.25192 1.09746,3.12979 v 0.26827 q 0,1.91039 -1.0812,3.1867 -1.07307,1.27631 -2.91843,1.27631 -1.85349,0 -2.93469,-1.27631 -1.07308,-1.27631 -1.07308,-3.1867 z m 1.50393,0.18697 q 0,1.30882 0.61783,2.26809 0.62596,0.95925 1.88601,0.95925 1.22752,0 1.85348,-0.943 0.62596,-0.95113 0.6341,-2.25995 v -0.21136 q 0,-1.29257 -0.62596,-2.25996 -0.62596,-0.97552 -1.87788,-0.97552 -1.24379,0 -1.86975,0.97552 -0.61783,0.96739 -0.61783,2.25996 z m 10.02347,-8.18624 v 12.48665 h -1.51205 v -12.48665 z m 4.04841,0 v 12.48665 h -1.51206 v -12.48665 z m 9.37312,10.95022 q -0.43086,0.65034 -1.2194,1.17875 -0.78855,0.52028 -2.08923,0.52028 -1.83724,0 -2.94282,-1.19501 -1.09746,-1.19502 -1.09746,-3.05664 v -0.34143 q 0,-1.43889 0.54466,-2.44693 0.5528,-1.01616 1.43076,-1.54457 0.87797,-0.53654 1.86974,-0.53654 1.88602,0 2.74772,1.23566 0.86984,1.22753 0.86984,3.07289 v 0.67474 h -5.95067 q 0.0325,1.21126 0.71538,2.06485 0.691,0.84544 1.89413,0.84544 0.79668,0 1.34947,-0.32517 0.5528,-0.32517 0.96739,-0.86984 z m -3.50375,-6.18643 q -0.89422,0 -1.51205,0.65035 -0.61783,0.65035 -0.77228,1.86975 h 4.39797 v -0.11382 q -0.0569,-0.87796 -0.51215,-1.64212 -0.44712,-0.76416 -1.60149,-0.76416 z m 8.88537,0.21136 q -1.47141,0 -2.00794,1.26819 v 6.24331 h -1.50393 v -8.79593 h 1.46328 l 0.0325,1.00804 q 0.72351,-1.17063 2.08924,-1.17063 0.42273,0 0.66661,0.11381 l -0.008,1.39825 q -0.33331,-0.0651 -0.73165,-0.0651 z"
id="text2"
aria-label="Controller" /><g
id="g3"
transform="translate(-1.9438155)"><path
style="font-weight:bold;font-size:124.9px;font-family:Sans;-inkscape-font-specification:'Sans Bold';fill:#ffffff;stroke:#ffffff;stroke-width:0.751077"
d="M 64.68422,94.628369 V 112.71227 H 60.958142 V 94.628369 Z m 7.427314,0 V 112.71227 H 68.385456 V 94.628369 Z m 2.173546,18.083901 6.73178,-18.083901 h 3.452831 l 6.76905,18.083901 h -3.97449 l -1.24202,-3.72608 h -6.545487 l -1.242021,3.72608 z m 6.197711,-6.7442 h 4.5334 l -2.260495,-6.793888 z m 25.883829,1.4159 q 0,2.60826 -1.66432,3.96207 -1.66431,1.34139 -4.744537,1.36623 H 92.927901 V 94.628369 h 6.346742 q 3.142327,0 4.918427,1.204765 1.7761,1.204764 1.7761,3.738498 0,1.229608 -0.62102,2.260488 -0.62101,1.03088 -1.9624,1.56495 1.5898,0.39745 2.28533,1.54011 0.69554,1.14267 0.69554,2.44679 z m -9.700227,-9.737479 v 4.545819 h 2.60825 q 2.956027,0 2.956027,-2.235652 0,-2.260486 -2.831817,-2.310167 z m 5.974147,9.700229 q 0,-2.43438 -2.45922,-2.52132 h -3.514927 v 4.88116 h 3.17958 q 1.428337,0 2.111447,-0.67069 0.68312,-0.67069 0.68312,-1.68915 z"
id="text4"
aria-label="IIAB" /><path
id="path1"
style="fill:#ffffff;stroke:none;stroke-opacity:1;paint-order:stroke markers fill"
d="m 203.20824,-17.709513 a 65.463825,62.177783 0 0 0 -11.35899,0.982886 L 187.76371,2.4205246 A 46.925591,44.569562 0 0 0 172.59564,10.741464 L 153.09037,4.5268556 A 65.463825,62.177783 0 0 0 141.73034,23.215121 l 15.42283,12.940296 a 46.925591,44.569562 0 0 0 -0.85679,8.313698 46.925591,44.569562 0 0 0 0.85679,8.31319 l -15.42283,12.9403 a 65.463825,62.177783 0 0 0 11.36003,18.68826 l 19.50527,-6.21409 a 46.925591,44.569562 0 0 0 15.16807,8.32094 l 4.08554,19.147145 a 65.463825,62.177783 0 0 0 11.35899,0.98237 65.463825,62.177783 0 0 0 11.359,-0.98237 l 4.08295,-19.136295 a 46.925591,44.569562 0 0 0 15.19029,-8.32559 l 19.48512,6.20841 a 65.463825,62.177783 0 0 0 11.36003,-18.68827 l -15.39906,-12.92014 a 46.925591,44.569562 0 0 0 0.86093,-8.33334 46.925591,44.569562 0 0 0 -0.8351,-8.355038 L 264.71353,23.192383 A 65.463825,62.177783 0 0 0 253.36435,4.5149706 L 233.87304,10.725444 A 46.925591,44.569562 0 0 0 218.64296,2.3750486 l -4.08347,-19.1383656 a 65.463825,62.177783 0 0 0 -11.35125,-0.945679 z"
clip-path="url(#clipPath2)"
inkscape:path-effect="#path-effect2"
inkscape:original-d="m 203.20824,-17.709513 a 65.463825,62.177783 0 0 0 -11.35899,0.982886 L 187.76371,2.4205246 A 46.925591,44.569562 0 0 0 172.59564,10.741464 L 153.09037,4.5268556 A 65.463825,62.177783 0 0 0 141.73034,23.215121 l 15.42283,12.940296 a 46.925591,44.569562 0 0 0 -0.85679,8.313698 46.925591,44.569562 0 0 0 0.85679,8.31319 l -15.42283,12.9403 a 65.463825,62.177783 0 0 0 11.36003,18.68826 l 19.50527,-6.21409 a 46.925591,44.569562 0 0 0 15.16807,8.32094 l 4.08554,19.147145 a 65.463825,62.177783 0 0 0 11.35899,0.98237 65.463825,62.177783 0 0 0 11.359,-0.98237 l 4.08295,-19.136295 a 46.925591,44.569562 0 0 0 15.19029,-8.32559 l 19.48512,6.20841 a 65.463825,62.177783 0 0 0 11.36003,-18.68827 l -15.39906,-12.92014 a 46.925591,44.569562 0 0 0 0.86093,-8.33334 46.925591,44.569562 0 0 0 -0.8351,-8.355038 L 264.71353,23.192383 A 65.463825,62.177783 0 0 0 253.36435,4.5149706 L 233.87304,10.725444 A 46.925591,44.569562 0 0 0 218.64296,2.3750486 l -4.08347,-19.1383656 a 65.463825,62.177783 0 0 0 -11.35125,-0.945679 z"
transform="matrix(0.29688448,0,0,0.29688448,66.177687,89.523053)" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,227 +0,0 @@
/*
============================================================================
Name : AppListActivity.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2025 xyz
Copyright (c) 2026 IIAB Project
Description : App List Activity
============================================================================
*/
package org.iiab.controller;
import java.util.Set;
import java.util.List;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Comparator;
import java.util.Collections;
import java.util.ArrayList;
import android.Manifest;
import android.os.Bundle;
import android.app.ListActivity;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.EditText;
import android.text.TextWatcher;
import android.text.Editable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.ApplicationInfo;
public class AppListActivity extends ListActivity {
private Preferences prefs;
private AppArrayAdapter adapter;
private boolean isChanged = false;
private class Package {
public PackageInfo info;
public boolean selected;
public String label;
public Package(PackageInfo info, boolean selected, String label) {
this.info = info;
this.selected = selected;
this.label = label;
}
}
private class AppArrayAdapter extends ArrayAdapter<Package> {
private final List<Package> allPackages = new ArrayList<Package>();
private final List<Package> filteredPackages = new ArrayList<Package>();
private String lastFilter = "";
public AppArrayAdapter(Context context) {
super(context, R.layout.appitem);
}
@Override
public void add(Package pkg) {
allPackages.add(pkg);
if (matchesFilter(pkg, lastFilter))
filteredPackages.add(pkg);
notifyDataSetChanged();
}
@Override
public void clear() {
allPackages.clear();
filteredPackages.clear();
notifyDataSetChanged();
}
@Override
public void sort(Comparator<? super Package> cmp) {
Collections.sort(allPackages, (Comparator) cmp);
applyFilter(lastFilter);
}
@Override
public int getCount() {
return filteredPackages.size();
}
@Override
public Package getItem(int position) {
return filteredPackages.get(position);
}
public List<Package> getAllPackages() {
return allPackages;
}
private boolean matchesFilter(Package pkg, String filter) {
if (filter == null || filter.length() == 0)
return true;
return pkg.label.toLowerCase().contains(filter.toLowerCase());
}
public void applyFilter(String filter) {
lastFilter = filter != null ? filter : "";
filteredPackages.clear();
if (lastFilter.length() == 0) {
filteredPackages.addAll(allPackages);
} else {
String f = lastFilter.toLowerCase();
for (Package p : allPackages) {
if (p.label != null && p.label.toLowerCase().contains(f))
filteredPackages.add(p);
}
}
notifyDataSetChanged();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.appitem, parent, false);
ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
TextView textView = (TextView) rowView.findViewById(R.id.name);
CheckBox checkBox = (CheckBox) rowView.findViewById(R.id.checked);
Package pkg = getItem(position);
PackageManager pm = getContext().getPackageManager();
ApplicationInfo appinfo = pkg.info.applicationInfo;
imageView.setImageDrawable(appinfo.loadIcon(pm));
textView.setText(appinfo.loadLabel(pm).toString());
checkBox.setChecked(pkg.selected);
return rowView;
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
prefs = new Preferences(this);
Set<String> apps = prefs.getApps();
PackageManager pm = getPackageManager();
adapter = new AppArrayAdapter(this);
for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)) {
if (info.packageName.equals(getPackageName()))
continue;
if (info.requestedPermissions == null)
continue;
if (!Arrays.asList(info.requestedPermissions).contains(Manifest.permission.INTERNET))
continue;
boolean selected = apps.contains(info.packageName);
String label = info.applicationInfo.loadLabel(pm).toString();
Package pkg = new Package(info, selected, label);
adapter.add(pkg);
}
EditText searchBox = new EditText(this);
searchBox.setHint("Search");
int pad = (int) (8 * getResources().getDisplayMetrics().density);
searchBox.setPadding(pad, pad, pad, pad);
getListView().addHeaderView(searchBox, null, false);
adapter.sort(new Comparator<Package>() {
public int compare(Package a, Package b) {
if (a.selected != b.selected)
return a.selected ? -1 : 1;
return a.label.compareTo(b.label);
}
});
setListAdapter(adapter);
searchBox.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.applyFilter(s.toString());
}
@Override
public void afterTextChanged(Editable s) { }
});
}
@Override
protected void onDestroy() {
if (isChanged) {
Set<String> apps = new HashSet<String>();
for (Package pkg : adapter.getAllPackages()) {
if (pkg.selected)
apps.add(pkg.info.packageName);
}
prefs.setApps(apps);
}
super.onDestroy();
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
int headers = l.getHeaderViewsCount();
int adjPos = position - headers;
if (adjPos < 0)
return;
Package pkg = adapter.getItem(adjPos);
pkg.selected = !pkg.selected;
CheckBox checkbox = (CheckBox) v.findViewById(R.id.checked);
if (checkbox != null)
checkbox.setChecked(pkg.selected);
isChanged = true;
}
}

View File

@ -1,92 +0,0 @@
/*
* ============================================================================
* Name : BatteryUtils.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Manage battery permissions
* ============================================================================
*/
package org.iiab.controller;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import androidx.activity.result.ActivityResultLauncher;
import androidx.appcompat.app.AlertDialog;
public class BatteryUtils {
// Previously at MainActivity
public static void checkAndPromptOptimizations(Activity activity, ActivityResultLauncher<Intent> launcher) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PowerManager pm = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
if (pm != null && !pm.isIgnoringBatteryOptimizations(activity.getPackageName())) {
String manufacturer = Build.MANUFACTURER.toLowerCase();
String message = activity.getString(R.string.battery_opt_msg);
if (manufacturer.contains("oppo") || manufacturer.contains("realme") || manufacturer.contains("xiaomi")) {
if (manufacturer.contains("oppo") || manufacturer.contains("realme")) {
message += activity.getString(R.string.battery_opt_oppo_extra);
} else if (manufacturer.contains("xiaomi")) {
message += activity.getString(R.string.battery_opt_xiaomi_extra);
}
new AlertDialog.Builder(activity)
.setTitle(R.string.battery_opt_title)
.setMessage(message)
.setPositiveButton(R.string.go_to_settings, (dialog, which) -> openBatterySettings(activity, manufacturer))
.setNegativeButton(R.string.cancel, null)
.show();
} else {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + activity.getPackageName()));
launcher.launch(intent);
}
}
}
}
private static void openBatterySettings(Activity activity, String manufacturer) {
boolean success = false;
String packageName = activity.getPackageName();
if (manufacturer.contains("oppo") || manufacturer.contains("realme")) {
try {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
activity.startActivity(intent);
success = true;
} catch (Exception e) {
try {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.coloros.oppoguardelf", "com.coloros.oppoguardelf.Permission.BackgroundAllowAppListActivity"));
activity.startActivity(intent);
success = true;
} catch (Exception e2) {}
}
} else if (manufacturer.contains("xiaomi")) {
try {
Intent intent = new Intent("miui.intent.action.APP_BATTERY_SAVER_SETTINGS");
intent.setComponent(new ComponentName("com.miui.powerkeeper", "com.miui.powerkeeper.ui.HiddenAppsConfigActivity"));
intent.putExtra("package_name", packageName);
intent.putExtra("package_label", activity.getString(R.string.app_name));
activity.startActivity(intent);
success = true;
} catch (Exception e) {}
}
if (!success) {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + packageName));
activity.startActivity(intent);
} catch (Exception ex) {}
}
}
}

View File

@ -1,78 +0,0 @@
/*
* ============================================================================
* Name : BiometricHelper.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Biometrics helper
* ============================================================================
*/
package org.iiab.controller;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import java.util.concurrent.Executor;
public class BiometricHelper {
// This is the "phone line" that tells MainActivity the user succeeded
public interface AuthCallback {
void onSuccess();
}
public static boolean isDeviceSecure(Context context) {
BiometricManager bm = BiometricManager.from(context);
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
auth = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
}
android.app.KeyguardManager km = (android.app.KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
return bm.canAuthenticate(auth) == BiometricManager.BIOMETRIC_SUCCESS || (km != null && km.isDeviceSecure());
}
public static void showEnrollmentDialog(Context context) {
new AlertDialog.Builder(context)
.setTitle(R.string.security_required_title)
.setMessage(R.string.security_required_msg)
.setPositiveButton(R.string.go_to_settings, (dialog, which) -> {
Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
context.startActivity(intent);
})
.setNegativeButton(R.string.cancel, null)
.show();
}
public static void prompt(AppCompatActivity activity, String title, String subtitle, AuthCallback callback) {
Executor executor = ContextCompat.getMainExecutor(activity);
BiometricPrompt biometricPrompt = new BiometricPrompt(activity, executor, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
// Call back to MainActivity!
callback.onSuccess();
}
});
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
auth = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
}
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle(title)
.setSubtitle(subtitle)
.setAllowedAuthenticators(auth)
.build();
biometricPrompt.authenticate(promptInfo);
}
}

View File

@ -1,528 +0,0 @@
/*
* ============================================================================
* Name : DashboardFragment.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Initial dasboard status activity
* ============================================================================
*/
package org.iiab.controller;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public class DashboardFragment extends Fragment {
private TextView txtDeviceName;
private TextView txtWifiIp, txtHotspotIp, txtUptime, txtBattery, badgeStatus, txtStorage, txtRam, txtSwap, txtTermuxState;
private TextView modulesTitle;
private ProgressBar progStorage, progRam, progSwap;
private View ledTermuxState;
private LinearLayout modulesContainer;
private final Handler refreshHandler = new Handler(Looper.getMainLooper());
private Runnable refreshRunnable;
// List of modules to scan (Endpoint, Display Name)
private final Object[][] TARGET_MODULES = {
{"books", R.string.dash_books},
{"code", R.string.dash_code},
{"kiwix", R.string.dash_kiwix},
{"kolibri", R.string.dash_kolibri},
{"maps", R.string.dash_maps},
{"matomo", R.string.dash_matomo},
{"dashboard", R.string.dash_system}
};
public enum SystemState {
ONLINE, OFFLINE, DEBIAN_ONLY, INSTALLER, TERMUX_ONLY, NONE
}
private SystemState currentSystemState = SystemState.NONE;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_dashboard, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Bindings
txtDeviceName = view.findViewById(R.id.dash_text_device_name);
txtWifiIp = view.findViewById(R.id.dash_text_wifi_ip);
txtHotspotIp = view.findViewById(R.id.dash_text_hotspot_ip);
txtUptime = view.findViewById(R.id.dash_text_uptime);
txtBattery = view.findViewById(R.id.dash_text_battery);
badgeStatus = view.findViewById(R.id.dash_badge_status);
txtStorage = view.findViewById(R.id.dash_text_storage);
txtRam = view.findViewById(R.id.dash_text_ram);
txtSwap = view.findViewById(R.id.dash_text_swap);
progStorage = view.findViewById(R.id.dash_progress_storage);
progRam = view.findViewById(R.id.dash_progress_ram);
progSwap = view.findViewById(R.id.dash_progress_swap);
ledTermuxState = view.findViewById(R.id.led_termux_state);
txtTermuxState = view.findViewById(R.id.text_termux_state);
modulesContainer = view.findViewById(R.id.modules_container);
modulesTitle = view.findViewById(R.id.dash_modules_title);
modulesContainer.setVisibility(View.GONE);
modulesTitle.setText(String.format(getString(R.string.label_separator_up), getString(R.string.dash_installed_modules)));
// Listener to colapse/expande
modulesTitle.setOnClickListener(v -> {
boolean isGone = modulesContainer.getVisibility() == View.GONE;
modulesContainer.setVisibility(isGone ? View.VISIBLE : View.GONE);
modulesTitle.setText(String.format(getString(isGone ? R.string.label_separator_down : R.string.label_separator_up), getString(R.string.dash_installed_modules)));
});
// Generate module views dynamically
createModuleViews();
// Configure refresh timer (every 5 seconds)
refreshRunnable = new Runnable() {
@Override
public void run() {
updateSystemStats();
checkServerAndModules();
refreshHandler.postDelayed(this, 5000);
}
};
}
@Override
public void onResume() {
super.onResume();
refreshHandler.post(refreshRunnable);
}
@Override
public void onPause() {
super.onPause();
refreshHandler.removeCallbacks(refreshRunnable);
}
private void updateSystemStats() {
txtDeviceName.setText(getDeviceName());
// --- 0. CALCULATE SERVER UPTIME ---
long uptimeMillis = android.os.SystemClock.elapsedRealtime();
long minutes = (uptimeMillis / (1000 * 60)) % 60;
long hours = (uptimeMillis / (1000 * 60 * 60)) % 24;
long days = (uptimeMillis / (1000 * 60 * 60 * 24));
// Format: "Uptime: 2d 14h 05m" (Omit days if 0)
String timeStr = (days > 0) ?
String.format(Locale.US, "%dd %02dh %02dm", days, hours, minutes) :
String.format(Locale.US, "%02dh %02dm", hours, minutes);
txtUptime.setText(Html.fromHtml(getString(R.string.dash_uptime_format, timeStr), Html.FROM_HTML_MODE_LEGACY));
txtWifiIp.setText(Html.fromHtml(getString(R.string.dash_wifi_format, getWifiIp()), Html.FROM_HTML_MODE_LEGACY));
txtHotspotIp.setText(Html.fromHtml(getString(R.string.dash_hotspot_format, getHotspotIp()), Html.FROM_HTML_MODE_LEGACY));
int batteryLevel = getBatteryPercentage();
if (batteryLevel >= 0) {
txtBattery.setText(Html.fromHtml(getString(R.string.dash_battery_format, batteryLevel), Html.FROM_HTML_MODE_LEGACY));
} else {
txtBattery.setText(Html.fromHtml(getString(R.string.dash_battery_no_value), Html.FROM_HTML_MODE_LEGACY));
}
// --- 1. GET REAL RAM AND SWAP FROM LINUX ---
long memTotal = 0, memAvailable = 0, swapTotal = 0, swapFree = 0;
try (BufferedReader br = new BufferedReader(new FileReader("/proc/meminfo"))) {
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith("MemTotal:")) memTotal = parseMemLine(line);
else if (line.startsWith("MemAvailable:")) memAvailable = parseMemLine(line);
// If phone is old and doesn't have "MemAvailable", use "MemFree"
else if (memAvailable == 0 && line.startsWith("MemFree:")) memAvailable = parseMemLine(line);
else if (line.startsWith("SwapTotal:")) swapTotal = parseMemLine(line);
else if (line.startsWith("SwapFree:")) swapFree = parseMemLine(line);
}
} catch (Exception e) {
e.printStackTrace();
}
// Convert the values from kB to GB (1 GB = 1048576 kB)
double memTotalGb = memTotal / 1048576.0;
double memUsedGb = (memTotal - memAvailable) / 1048576.0;
int memProgress = memTotal > 0 ? (int) (((memTotal - memAvailable) * 100) / memTotal) : 0;
double swapTotalGb = swapTotal / 1048576.0;
double swapUsedGb = (swapTotal - swapFree) / 1048576.0;
int swapProgress = swapTotal > 0 ? (int) (((swapTotal - swapFree) * 100) / swapTotal) : 0;
// --- UPDATE UI (TEXT AND BARS) ---
txtRam.setText(String.format(Locale.US, "%.2f GB / %.2f GB", memUsedGb, memTotalGb));
progRam.setProgress(memProgress);
if (swapTotal > 0) {
txtSwap.setText(String.format(Locale.US, "%.2f GB / %.2f GB", swapUsedGb, swapTotalGb));
progSwap.setProgress(swapProgress);
} else {
// If the device does not use Swap
txtSwap.setText("-- / --");
progSwap.setProgress(0);
}
// 2. Get Internal Storage
File path = android.os.Environment.getDataDirectory();
long totalSpace = path.getTotalSpace() / (1024 * 1024 * 1024); // To GB
long freeSpace = path.getFreeSpace() / (1024 * 1024 * 1024);
long usedSpace = totalSpace - freeSpace;
txtStorage.setText(usedSpace + " GB / " + totalSpace + " GB");
progStorage.setProgress(totalSpace > 0 ? (int) ((usedSpace * 100) / totalSpace) : 0);
}
private void createModuleViews() {
modulesContainer.removeAllViews();
int numCols = 3;
int numRows = (int) Math.ceil((double) TARGET_MODULES.length / numCols);
for (int row = 0; row < numRows; row++) {
LinearLayout rowLayout = new LinearLayout(requireContext());
rowLayout.setOrientation(LinearLayout.HORIZONTAL);
rowLayout.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
rowLayout.setBaselineAligned(false);
rowLayout.setWeightSum(numCols);
rowLayout.setPadding(0, 0, 0, 16);
for (int col = 0; col < numCols; col++) {
int index = (row * numCols) + col;
LinearLayout cell = new LinearLayout(requireContext());
LinearLayout.LayoutParams cellParams = new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
// Margins to prevent them from sticking together
int margin = 8;
if (col == 0) cellParams.setMargins(0, 0, margin, 0); // Left
else if (col == 1) cellParams.setMargins(margin/2, 0, margin/2, 0); // Center
else cellParams.setMargins(margin, 0, 0, 0); // Right
cell.setLayoutParams(cellParams);
if (index < TARGET_MODULES.length) {
cell.setOrientation(LinearLayout.HORIZONTAL);
cell.setBackgroundResource(R.drawable.rounded_button);
cell.setBackgroundTintList(android.content.res.ColorStateList.valueOf(
androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_module_bg)));
cell.setPadding(16, 24, 16, 24);
cell.setGravity(android.view.Gravity.CENTER);
View led = new View(requireContext());
led.setLayoutParams(new LinearLayout.LayoutParams(20, 20));
led.setBackgroundResource(R.drawable.led_off);
led.setId(View.generateViewId());
TextView name = new TextView(requireContext());
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
textParams.setMargins(12, 0, 0, 0);
name.setLayoutParams(textParams);
name.setText(getString((Integer) TARGET_MODULES[index][1]));
name.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_module_text));
name.setTextSize(11f);
name.setSingleLine(true);
cell.addView(led);
cell.addView(name);
cell.setTag(TARGET_MODULES[index][0]);
} else {
cell.setVisibility(View.INVISIBLE);
}
rowLayout.addView(cell);
}
modulesContainer.addView(rowLayout);
}
}
private void checkServerAndModules() {
new Thread(() -> {
// 1. Ping the network once
boolean isMainServerAlive = pingUrl("http://localhost:8085/home");
if (!isAdded() || getActivity() == null) return;
// 2. Ask the State Machine for the definitive truth
currentSystemState = evaluateSystemState(isMainServerAlive);
// 3. Push the state to MainActivity
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).currentSystemState = currentSystemState;
getActivity().runOnUiThread(() -> {
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).updateUIColorsAndVisibility();
}
});
}
// --- CHECKPOINT 2 ---
if (!isAdded() || getActivity() == null) return;
// 4. Update the UI on the main thread
getActivity().runOnUiThread(() -> {
// Configure the Top Traffic Light (Server Status)
if (currentSystemState == SystemState.ONLINE) {
badgeStatus.setText(R.string.dash_online);
badgeStatus.setBackgroundTintList(android.content.res.ColorStateList.valueOf(
androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_status_online)));
} else {
badgeStatus.setText(R.string.dash_offline);
badgeStatus.setBackgroundTintList(android.content.res.ColorStateList.valueOf(
androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_secondary)));
}
// Configure the Bottom LED and Suggestion Message
switch (currentSystemState) {
case ONLINE:
ledTermuxState.setBackgroundResource(R.drawable.led_on_green);
txtTermuxState.setText(getString(R.string.dash_state_online));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_primary));
break;
case OFFLINE:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_offline));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_secondary));
break;
case DEBIAN_ONLY:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_debian_only));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_primary));
break;
case INSTALLER:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_installer));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_text_primary));
break;
case TERMUX_ONLY:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_termux_only));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_warning));
break;
case NONE:
ledTermuxState.setBackgroundResource(R.drawable.led_off);
txtTermuxState.setText(getString(R.string.dash_state_none));
txtTermuxState.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.dash_warning));
break;
}
});
// 5. Scan individual modules (Only if the system is ONLINE)
for (int r = 0; r < modulesContainer.getChildCount(); r++) {
LinearLayout row = (LinearLayout) modulesContainer.getChildAt(r);
for (int c = 0; c < row.getChildCount(); c++) {
LinearLayout card = (LinearLayout) row.getChildAt(c);
String endpoint = (String) card.getTag();
if (endpoint == null) continue;
View led = card.getChildAt(0);
// Module ON = (System is ONLINE) AND (URL responds)
boolean isModuleAlive = (currentSystemState == SystemState.ONLINE) && pingUrl("http://localhost:8085/" + endpoint);
if (!isAdded() || getActivity() == null) return;
getActivity().runOnUiThread(() -> {
led.setBackgroundResource(isModuleAlive ? R.drawable.led_on_green : R.drawable.led_off);
});
}
}
}).start();
}
private boolean pingUrl(String urlStr) {
try {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setUseCaches(false);
conn.setConnectTimeout(1500);
conn.setReadTimeout(1500);
conn.setRequestMethod("GET");
return (conn.getResponseCode() >= 200 && conn.getResponseCode() < 400);
} catch (Exception e) {
return false;
}
}
// Extracts the numbers (in kB) from the lines of /proc/meminfo
private long parseMemLine(String line) {
try {
String[] parts = line.split("\\s+");
return Long.parseLong(parts[1]);
} catch (Exception e) {
return 0;
}
}
// --- METHODS FOR OBTAINING IPs ---
private String getWifiIp() {
return getIpByInterface("wlan0");
}
private String getHotspotIp() {
String[] hotspotInterfaces = {"ap0", "wlan1", "swlan0"};
for (String iface : hotspotInterfaces) {
String ip = getIpByInterface(iface);
if (!ip.equals("--")) return ip;
}
return "--";
}
private String getIpByInterface(String interfaceName) {
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface intf : interfaces) {
if (intf.getName().equalsIgnoreCase(interfaceName)) {
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
for (InetAddress addr : addrs) {
if (!addr.isLoopbackAddress() && addr instanceof Inet4Address) {
return addr.getHostAddress();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "--";
}
private int getBatteryPercentage() {
try {
IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = requireContext().registerReceiver(null, iFilter);
if (batteryStatus != null) {
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
return (int) ((level / (float) scale) * 100);
}
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
// --- METHODS FOR OBTAINING THE DEVICE NAME ---
private String getDeviceName() {
String manufacturer = android.os.Build.MANUFACTURER;
String model = android.os.Build.MODEL;
if (model.toLowerCase().startsWith(manufacturer.toLowerCase())) {
return capitalize(model);
} else {
return capitalize(manufacturer) + " " + model;
}
}
private String capitalize(String s) {
if (s == null || s.length() == 0) return "";
char first = s.charAt(0);
if (Character.isUpperCase(first)) {
return s;
} else {
return Character.toUpperCase(first) + s.substring(1);
}
}
// The 5 possible system states
// --- MASTER STATE EVALUATOR ---
private SystemState evaluateSystemState(boolean isNginxAlive) {
// 1. Does the Nginx server respond? (The network doesn't lie)
if (isNginxAlive) {
return SystemState.ONLINE;
}
// 2. Does Termux physically exist on the Android device?
boolean isTermuxInstalled = false;
try {
requireContext().getPackageManager().getPackageInfo("com.termux", 0);
isTermuxInstalled = true;
} catch (PackageManager.NameNotFoundException e) {
isTermuxInstalled = false;
}
File stateDir = new File(Environment.getExternalStorageDirectory(), ".iiab_state");
// Ghost Handling: If Termux is uninstalled, but garbage remains, delete it.
if (!isTermuxInstalled) {
if (stateDir.exists()) {
deleteRecursive(stateDir);
}
return SystemState.NONE;
}
// 3. Is IIAB fully compiled/restored and ready?
File flagIiabReady = new File(stateDir, "flag_iiab_ready");
if (flagIiabReady.exists()) {
return SystemState.OFFLINE; // The real offline state
}
// 4. Is the base Debian OS installed, but NO IIAB yet? (The Virgin Debian Trap)
File flagSystem = new File(stateDir, "flag_system_installed");
if (flagSystem.exists()) {
return SystemState.DEBIAN_ONLY;
}
// 5. Is only the installer ready?
File flagInstaller = new File(stateDir, "flag_installer_present");
if (flagInstaller.exists()) {
return SystemState.INSTALLER;
}
// 6. Only the raw base app is present.
return SystemState.TERMUX_ONLY;
}
// Helper method to recursively delete the .iiab_state folder if Termux was uninstalled
private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
File[] children = fileOrDirectory.listFiles();
if (children != null) {
for (File child : children) {
deleteRecursive(child);
}
}
}
fileOrDirectory.delete();
}
}

View File

@ -1,120 +0,0 @@
/*
* ============================================================================
* Name : DashboardManager.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Initial dasboard status helper
* ============================================================================
*/
package org.iiab.controller;
import android.app.Activity;
import android.content.Intent;
import android.provider.Settings;
import android.transition.AutoTransition;
import android.transition.TransitionManager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
public class DashboardManager {
private final Activity activity;
private final LinearLayout dashboardContainer;
private final View dashWifi, dashHotspot, dashTunnel;
private final View ledWifi, ledHotspot, ledTunnel;
private final View standaloneEspwButton;
private final View standaloneEspwDescription;
// Memory variables to avoid freezing the screen
private boolean lastTunnelState = false;
private boolean lastDegradedState = false;
private boolean isFirstRun = true;
public interface DashboardActionCallback {
void onToggleEspwRequested();
}
public DashboardManager(Activity activity, View rootView, DashboardActionCallback callback) {
this.activity = activity;
// Bind all the views
dashboardContainer = (LinearLayout) rootView.findViewById(R.id.dashboard_container);
dashWifi = rootView.findViewById(R.id.dash_wifi);
dashHotspot = rootView.findViewById(R.id.dash_hotspot);
dashTunnel = rootView.findViewById(R.id.dash_tunnel);
ledWifi = rootView.findViewById(R.id.led_wifi);
ledHotspot = rootView.findViewById(R.id.led_hotspot);
ledTunnel = rootView.findViewById(R.id.led_tunnel);
standaloneEspwButton = rootView.findViewById(R.id.control);
standaloneEspwDescription = rootView.findViewById(R.id.control_description);
setupListeners(callback);
}
private void setupListeners(DashboardActionCallback callback) {
// Single tap opens Settings directly
dashWifi.setOnClickListener(v -> activity.startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)));
dashHotspot.setOnClickListener(v -> {
try {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
activity.startActivity(intent);
} catch (Exception e) {
activity.startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
}
});
// The Tunnel/ESPW toggle logic
View.OnClickListener toggleEspw = v -> callback.onToggleEspwRequested();
standaloneEspwButton.setOnClickListener(toggleEspw);
dashTunnel.setOnClickListener(toggleEspw);
}
// Updates the LED graphics based on actual OS connectivity states
public void updateConnectivityLeds(boolean isWifiOn, boolean isHotspotOn) {
ledWifi.setBackgroundResource(isWifiOn ? R.drawable.led_on_green : R.drawable.led_off);
ledHotspot.setBackgroundResource(isHotspotOn ? R.drawable.led_on_green : R.drawable.led_off);
}
// The Magic Morphing Animation!
public void setTunnelState(boolean isTunnelActive, boolean isDegraded) {
// ANTI-FREEZE SHIELD!
// If the state is exactly the same as 3 seconds ago, abort to avoid blocking the UI
if (!isFirstRun && lastTunnelState == isTunnelActive && lastDegradedState == isDegraded) {
return;
}
isFirstRun = false;
lastTunnelState = isTunnelActive;
lastDegradedState = isDegraded;
// Tells Android to smoothly animate any layout changes we make next
TransitionManager.beginDelayedTransition((ViewGroup) dashboardContainer.getParent(), new AutoTransition().setDuration(300));
if (isTunnelActive) {
// Morph into 33% / 33% / 33% Dashboard mode
standaloneEspwButton.setVisibility(View.GONE);
standaloneEspwDescription.setVisibility(View.GONE);
dashTunnel.setVisibility(View.VISIBLE);
ledTunnel.setBackgroundResource(isDegraded ? R.drawable.led_on_orange : R.drawable.led_on_green);
// Force recalculate
dashboardContainer.setWeightSum(3f);
} else {
// Morph back into 50% / 50% mode
dashTunnel.setVisibility(View.GONE);
standaloneEspwButton.setVisibility(View.VISIBLE);
standaloneEspwDescription.setVisibility(View.VISIBLE);
// The LED turns off implicitly since the whole dash_tunnel hides, but we can enforce it:
ledTunnel.setBackgroundResource(R.drawable.led_off);
// Force recalculate
dashboardContainer.setWeightSum(2f);
}
// Force recalculate
dashboardContainer.requestLayout();
}
}

View File

@ -1,26 +0,0 @@
/*
* ============================================================================
* Name : DeployFragment.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Installation / deployment view
* ============================================================================
*/
package org.iiab.controller;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class DeployFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_deploy, container, false);
}
}

View File

@ -1,230 +0,0 @@
/*
* ============================================================================
* Name : IIABWatchdog.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Watchdog activity
* ============================================================================
*/
package org.iiab.controller;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* A stateless utility class to perform keep-alive actions for Termux.
* The lifecycle (start/stop/loop) is managed by the calling service.
*/
public class IIABWatchdog {
private static final String TAG = "IIAB-Controller";
public static final String ACTION_LOG_MESSAGE = "org.iiab.controller.LOG_MESSAGE";
public static final String EXTRA_MESSAGE = "org.iiab.controller.EXTRA_MESSAGE";
public static final String ACTION_TERMUX_OUTPUT = "org.iiab.controller.TERMUX_OUTPUT";
public static final String PREF_RAPID_GROWTH = "log_rapid_growth";
private static final boolean DEBUG_ENABLED = true;
private static final String BLACKBOX_FILE = "watchdog_heartbeat_log.txt";
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
private static final int MAX_DAYS = 5;
/**
* Performs a full heartbeat pulse: sending stimulus.
*/
public static void performHeartbeat(Context context) {
sendStimulus(context);
// TROUBLESHOOTING: Uncomment to test NGINX status.
// performDebugPing(context);
}
/**
* Sends a keep-alive command to Termux via Intent.
*/
public static void sendStimulus(Context context) {
if (DEBUG_ENABLED) {
writeToBlackBox(context, context.getString(R.string.pulse_stimulating));
}
// Build the intent for Termux with exact payload requirements
Intent intent = new Intent();
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
intent.setAction("com.termux.RUN_COMMAND");
// 1. Absolute path to the command (String)
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/true");
// 2. Execute silently in the background (Boolean, critical)
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", true);
// 3. Avoid saving session history (String "0" = no action)
intent.putExtra("com.termux.RUN_COMMAND_SESSION_ACTION", "0");
// Callback mechanism to confirm execution
Intent callbackIntent = new Intent(context, TermuxCallbackReceiver.class);
callbackIntent.setAction(ACTION_TERMUX_OUTPUT);
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags |= PendingIntent.FLAG_IMMUTABLE;
}
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, callbackIntent, flags);
intent.putExtra("com.termux.service.RUN_COMMAND_CALLBACK", pendingIntent);
try {
context.startService(intent);
} catch (SecurityException e) {
// This catches specific permission errors on newer Android versions
Log.e(TAG, context.getString(R.string.permission_denied_log), e);
writeToBlackBox(context, context.getString(R.string.critical_os_blocked));
} catch (Exception e) {
Log.e(TAG, context.getString(R.string.unexpected_error_termux), e);
writeToBlackBox(context, context.getString(R.string.pulse_error_log, e.getMessage()));
}
}
/**
* Pings the Termux NGINX server to check responsiveness.
*/
public static void performDebugPing(Context context) {
final String NGINX_IP = "127.0.0.1";
final int NGINX_PORT = 8085;
new Thread(() -> {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(NGINX_IP, NGINX_PORT), 2000);
if (DEBUG_ENABLED) {
writeToBlackBox(context, context.getString(R.string.ping_ok));
}
} catch (IOException e) {
if (DEBUG_ENABLED) {
writeToBlackBox(context, context.getString(R.string.ping_fail, e.getMessage()));
}
}
}).start();
}
public static void logSessionStart(Context context) {
if (DEBUG_ENABLED) {
writeToBlackBox(context, context.getString(R.string.session_started));
}
}
public static void logSessionStop(Context context) {
if (DEBUG_ENABLED) {
writeToBlackBox(context, context.getString(R.string.session_stopped));
}
}
/**
* Writes a message to the local log file and broadcasts it for UI update.
*/
public static void writeToBlackBox(Context context, String message) {
File logFile = new File(context.getFilesDir(), BLACKBOX_FILE);
// 1. Perform maintenance if file size is nearing limit
if (logFile.exists() && logFile.length() > MAX_FILE_SIZE * 0.9) {
maintenance(context, logFile);
}
try (FileWriter writer = new FileWriter(logFile, true)) {
String datePrefix = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
writer.append(datePrefix).append(" - ").append(message).append("\n");
broadcastLog(context, message);
} catch (IOException e) {
Log.e(TAG, context.getString(R.string.failed_write_blackbox), e);
}
}
/**
* Handles log rotation based on date (5 days) and size (10MB).
*/
private static void maintenance(Context context, File logFile) {
List<String> lines = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -MAX_DAYS);
Date cutoffDate = cal.getTime();
boolean deletedByDate = false;
try (BufferedReader br = new BufferedReader(new FileReader(logFile))) {
String line;
while ((line = br.readLine()) != null) {
if (line.length() > 19) {
try {
Date lineDate = sdf.parse(line.substring(0, 19));
if (lineDate != null && lineDate.after(cutoffDate)) {
lines.add(line);
} else {
deletedByDate = true;
}
} catch (ParseException e) {
lines.add(line);
}
} else {
lines.add(line);
}
}
} catch (IOException e) {
return;
}
// If after date cleanup it's still too large, trim the oldest 20%
if (calculateSize(lines) > MAX_FILE_SIZE) {
int toRemove = lines.size() / 5;
if (toRemove > 0) {
lines = lines.subList(toRemove, lines.size());
}
// If deleting by size but not by date, it indicates rapid log growth
if (!deletedByDate) {
setRapidGrowthFlag(context, true);
}
}
// Write cleaned logs back to file
try (PrintWriter pw = new PrintWriter(new FileWriter(logFile))) {
for (String l : lines) {
pw.println(l);
}
} catch (IOException e) {
Log.e(TAG, context.getString(R.string.maintenance_write_failed), e);
}
}
private static long calculateSize(List<String> lines) {
long size = 0;
for (String s : lines) size += s.length() + 1;
return size;
}
private static void setRapidGrowthFlag(Context context, boolean enabled) {
SharedPreferences prefs = context.getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
prefs.edit().putBoolean(PREF_RAPID_GROWTH, enabled).apply();
}
private static void broadcastLog(Context context, String message) {
Intent intent = new Intent(ACTION_LOG_MESSAGE);
intent.putExtra(EXTRA_MESSAGE, message);
context.sendBroadcast(intent);
}
}

View File

@ -1,92 +0,0 @@
/*
* ============================================================================
* Name : LogManager.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Watchdog log manager
* ============================================================================
*/
package org.iiab.controller;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;
public class LogManager {
private static final String LOG_FILE_NAME = "watchdog_heartbeat_log.txt";
// Callbacks to communicate with MainActivity
public interface LogReadCallback {
void onResult(String logContent, boolean isRapidGrowth);
}
public interface LogClearCallback {
void onSuccess();
void onError(String message);
}
// Read the file in the background and return the result to the main thread
public static void readLogsAsync(Context context, LogReadCallback callback) {
new Thread(() -> {
File logFile = new File(context.getFilesDir(), LOG_FILE_NAME);
StringBuilder sb = new StringBuilder();
if (!logFile.exists()) {
sb.append(context.getString(R.string.no_blackbox_found)).append("\n");
} else {
sb.append(context.getString(R.string.loading_history)).append("\n");
try (BufferedReader br = new BufferedReader(new FileReader(logFile))) {
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
} catch (IOException e) {
sb.append(context.getString(R.string.error_reading_history, e.getMessage())).append("\n");
}
sb.append(context.getString(R.string.end_of_history)).append("\n");
}
SharedPreferences internalPrefs = context.getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
boolean isRapid = internalPrefs.getBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false);
String result = sb.toString();
// We return the call on the main UI thread
new Handler(Looper.getMainLooper()).post(() -> callback.onResult(result, isRapid));
}).start();
}
// Delete the file securely
public static void clearLogs(Context context, LogClearCallback callback) {
File logFile = new File(context.getFilesDir(), LOG_FILE_NAME);
try (PrintWriter pw = new PrintWriter(logFile)) {
pw.print("");
context.getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE)
.edit().putBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false).apply();
callback.onSuccess();
} catch (IOException e) {
callback.onError(e.getMessage());
}
}
// Calculate the file size
public static String getFormattedSize(Context context) {
File logFile = new File(context.getFilesDir(), LOG_FILE_NAME);
long size = logFile.exists() ? logFile.length() : 0;
if (size < 1024) {
return context.getString(R.string.log_size_bytes, size);
} else if (size < 1024 * 1024) {
return String.format(Locale.getDefault(), context.getString(R.string.log_size_kb), size / 1024.0);
} else {
return String.format(Locale.getDefault(), context.getString(R.string.log_size_mb), size / (1024.0 * 1024.0));
}
}
}

View File

@ -1,803 +0,0 @@
/*
============================================================================
Name : MainActivity.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2025 hev
Copyright (c) 2026 IIAB Project
Copyright : Copyright (c) 2023 xyz
Description : Main Activity
============================================================================
*/
package org.iiab.controller;
import android.Manifest;
import android.os.Bundle;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.app.AlertDialog;
import android.content.Intent;
import android.content.Context;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.res.ColorStateList;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.graphics.Color;
import android.view.MotionEvent;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.net.VpnService;
import android.net.Uri;
import android.text.method.ScrollingMovementMethod;
import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.provider.Settings;
import android.net.wifi.WifiManager;
import androidx.annotation.NonNull;
import androidx.biometric.BiometricManager;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import androidx.viewpager2.widget.ViewPager2;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.text.SimpleDateFormat;
import java.util.concurrent.Executor;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.Proxy;
import java.net.InetSocketAddress;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "IIAB-MainActivity";
private static final String TERMUX_PERMISSION = "com.termux.permission.RUN_COMMAND";
public Preferences prefs;
private ImageButton themeToggle;
private ImageButton btnSettings;
private android.widget.ImageView headerIcon;
// Tabs UI
private TabLayout tabLayout;
private ViewPager2 viewPager;
private TextView versionFooter;
public boolean isServerAlive = false;
public boolean isNegotiating = false;
public DashboardFragment.SystemState currentSystemState = DashboardFragment.SystemState.NONE;
public boolean isProxyDegraded = false;
public Boolean targetServerState = null;
public String serverTransitionText = "";
public UsageFragment usageFragment;
public void setUsageFragment(UsageFragment fragment) {
this.usageFragment = fragment;
}
private final Handler timeoutHandler = new Handler(android.os.Looper.getMainLooper());
private Runnable timeoutRunnable;
private boolean isWifiActive = false;
private boolean isHotspotActive = false;
private String currentTargetUrl = null;
private long pulseStartTime = 0;
private ActivityResultLauncher<Intent> vpnPermissionLauncher;
private ActivityResultLauncher<String[]> requestPermissionsLauncher;
private ActivityResultLauncher<Intent> batteryOptLauncher;
public boolean isReadingLogs = false;
private final Handler sizeUpdateHandler = new Handler();
private Runnable sizeUpdateRunnable;
// Variables for adaptive localhost server check
private final Handler serverCheckHandler = new Handler(android.os.Looper.getMainLooper());
private Runnable serverCheckRunnable;
private static final int CHECK_INTERVAL_MS = 3000;
private final BroadcastReceiver logReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (IIABWatchdog.ACTION_LOG_MESSAGE.equals(action)) {
String message = intent.getStringExtra(IIABWatchdog.EXTRA_MESSAGE);
addToLog(message);
if (usageFragment != null) usageFragment.updateLogSizeUI();
}
else if (WatchdogService.ACTION_STATE_STARTED.equals(action)) {
long elapsed = System.currentTimeMillis() - pulseStartTime;
long fullCycle = 1200;
// Find out how many milliseconds are left to finish the current wave
long remainder = elapsed % fullCycle;
long timeToNextCycleEnd = fullCycle - remainder;
// If the remaining time is too fast (< 1 second), add one more full cycle
// so the user actually has time to see the system notification drop down gracefully.
if (timeToNextCycleEnd < 1000) {
timeToNextCycleEnd += fullCycle;
}
// Wait exactly until the wave hits 1.0f alpha, then lock it!
new Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
if (usageFragment != null) usageFragment.finalizeEntryPulse();
}, timeToNextCycleEnd);
}
else if (WatchdogService.ACTION_STATE_STOPPED.equals(action)) {
// Service is down! Give it a 1.5 second visual margin, then stop the exit pulse.
new Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
if (usageFragment != null) usageFragment.finalizeExitPulse();
}, 1500);
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Intercept launch and redirect to Setup Wizard if first time
SharedPreferences internalPrefs = getSharedPreferences(getString(R.string.pref_file_internal), Context.MODE_PRIVATE);
if (!internalPrefs.getBoolean(getString(R.string.pref_key_setup_complete), false)) {
startActivity(new Intent(this, SetupActivity.class));
finish();
return;
}
prefs = new Preferences(this);
setContentView(R.layout.main);
// --- START TABS & VIEWPAGER ---
tabLayout = findViewById(R.id.tab_layout);
viewPager = findViewById(R.id.view_pager);
MainPagerAdapter pagerAdapter = new MainPagerAdapter(this);
viewPager.setAdapter(pagerAdapter);
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
switch (position) {
case 0: tab.setText(R.string.tab_status); break;
case 1: tab.setText(R.string.tab_usage); break;
case 2: tab.setText(R.string.tab_deploy); break;
}
}).attach();
versionFooter = findViewById(R.id.version_text);
setVersionFooter();
viewPager.setCurrentItem(0, false);
// 1. Initialize Result Launchers
vpnPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && prefs.getEnable()) {
connectVpn();
}
BatteryUtils.checkAndPromptOptimizations(MainActivity.this, batteryOptLauncher);
}
);
batteryOptLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
Log.d(TAG, "Returned from the battery settings screen");
BatteryUtils.checkAndPromptOptimizations(MainActivity.this, batteryOptLauncher);
}
);
requestPermissionsLauncher = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
result -> {
for (Map.Entry<String, Boolean> entry : result.entrySet()) {
if (entry.getKey().equals(TERMUX_PERMISSION)) {
addToLog(getString(entry.getValue() ? R.string.termux_perm_granted : R.string.termux_perm_denied));
} else if (entry.getKey().equals(Manifest.permission.POST_NOTIFICATIONS)) {
addToLog(getString(entry.getValue() ? R.string.notif_perm_granted : R.string.notif_perm_denied));
}
}
prepareVpn();
}
);
themeToggle = findViewById(R.id.theme_toggle);
btnSettings = findViewById(R.id.btn_settings);
headerIcon = findViewById(R.id.header_icon);
ImageButton btnShareQr = findViewById(R.id.btn_share_qr);
// Listeners
themeToggle.setOnClickListener(v -> toggleTheme());
btnSettings.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, SetupActivity.class)));
// --- QR Share Button Logic ---
btnShareQr.setOnClickListener(v -> {
if (!isServerAlive) {
// Rule 1: Server must be running
Snackbar.make(findViewById(android.R.id.content), R.string.qr_error_no_server, Snackbar.LENGTH_LONG).show();
return;
}
if (!isWifiActive && !isHotspotActive) {
// Rule 2: At least one network must be active
Snackbar.make(findViewById(android.R.id.content), R.string.qr_error_no_network, Snackbar.LENGTH_LONG).show();
return;
}
// Launch the new QrActivity
startActivity(new Intent(MainActivity.this, QrActivity.class));
});
applySavedTheme();
updateUI();
addToLog(getString(R.string.app_started));
sizeUpdateRunnable = new Runnable() {
@Override
public void run() {
if (usageFragment != null && usageFragment.isAdded()) usageFragment.updateLogSizeUI();
sizeUpdateHandler.postDelayed(this, 10000);
}
};
serverCheckRunnable = new Runnable() {
@Override
public void run() {
checkServerStatus();
updateConnectivityStatus(); // Check Wi-Fi & Hotspot states
serverCheckHandler.postDelayed(this, CHECK_INTERVAL_MS);
}
};
serverCheckHandler.post(serverCheckRunnable);
}
private void showBatterySnackbar() {
View rootView = findViewById(android.R.id.content);
Snackbar.make(rootView, R.string.battery_opt_denied, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.fix_action, v -> BatteryUtils.checkAndPromptOptimizations(MainActivity.this, batteryOptLauncher))
.show();
}
private void initiatePermissionChain() {
List<String> permissions = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.POST_NOTIFICATIONS);
}
}
if (ContextCompat.checkSelfPermission(this, TERMUX_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
permissions.add(TERMUX_PERMISSION);
}
if (!permissions.isEmpty()) {
requestPermissionsLauncher.launch(permissions.toArray(new String[0]));
} else {
prepareVpn();
}
}
private boolean pingUrl(String urlStr, boolean useProxy) {
try {
URL url = new URL(urlStr);
HttpURLConnection conn;
if (useProxy) {
// We routed the request directly to the app's SOCKS proxy
int socksPort = prefs.getSocksPort(); // generally 1080
Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", socksPort));
conn = (HttpURLConnection) url.openConnection(proxy);
} else {
// Normal request (for localhost)
conn = (HttpURLConnection) url.openConnection();
}
conn.setUseCaches(false);
conn.setConnectTimeout(1500);
conn.setReadTimeout(1500);
conn.setRequestMethod("GET");
return (conn.getResponseCode() >= 200 && conn.getResponseCode() < 400);
} catch (Exception e) {
return false;
}
}
private void runNegotiationSequence() {
isNegotiating = true;
runOnUiThread(() -> {
updateUIColorsAndVisibility(); // We forced an immediate visual update
});
new Thread(() -> {
boolean boxAlive = false;
// Attempt 1 (0 seconds)
boxAlive = pingUrl("http://box/home", true);
// Attempt 2 (At 2 seconds)
if (!boxAlive) {
try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
boxAlive = pingUrl("http://box/home", true);
}
// Attempt 3 (At 3 seconds)
if (!boxAlive) {
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
boxAlive = pingUrl("http://box/home", true);
}
// We validate if localhost serves as a fallback.
boolean localAlive = pingUrl("http://localhost:8085/home", false);
// We evaluate the results
isNegotiating = false;
isServerAlive = boxAlive || localAlive;
// If VPN is ON but box/proxy is dead, the tunnel is degraded (Orange).
if (prefs.getEnable()) {
isProxyDegraded = !boxAlive;
} else {
isProxyDegraded = false;
}
if (boxAlive) {
currentTargetUrl = "http://box/home";
} else if (localAlive) {
currentTargetUrl = "http://localhost:8085/home";
} else {
currentTargetUrl = null;
}
runOnUiThread(this::updateUIColorsAndVisibility);
}).start();
}
private void prepareVpn() {
Intent intent = VpnService.prepare(MainActivity.this);
if (intent != null) {
vpnPermissionLauncher.launch(intent);
} else {
if (prefs.getEnable()) connectVpn();
BatteryUtils.checkAndPromptOptimizations(MainActivity.this, batteryOptLauncher);
}
}
public void startLogSizeUpdates() {
sizeUpdateHandler.removeCallbacks(sizeUpdateRunnable);
sizeUpdateHandler.post(sizeUpdateRunnable);
}
public void stopLogSizeUpdates() {
sizeUpdateHandler.removeCallbacks(sizeUpdateRunnable);
}
private void connectVpn() {
Intent intent = new Intent(this, TProxyService.class);
startService(intent.setAction(TProxyService.ACTION_CONNECT));
addToLog(getString(R.string.vpn_permission_granted));
}
@Override
protected void onPause() {
super.onPause();
stopLogSizeUpdates();
serverCheckHandler.removeCallbacks(serverCheckRunnable);
}
@Override
protected void onResume() {
super.onResume();
// Check permissions status
updateHeaderIconsOpacity();
// Check battery status whenever returning to the app
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
Log.d(TAG, "onResume: Battery still optimized, showing warning");
showBatterySnackbar();
}
}
updateConnectivityStatus(); // Force instant UI refresh when returning to app
if (getIntent() != null && getIntent().getBooleanExtra(VpnRecoveryReceiver.EXTRA_RECOVERY, false)) {
addToLog(getString(R.string.recovery_pulse_received));
Intent vpnIntent = new Intent(this, TProxyService.class);
startService(vpnIntent.setAction(TProxyService.ACTION_CONNECT));
setIntent(null);
}
if (usageFragment != null && usageFragment.isLogVisible()) {
startLogSizeUpdates();
}
serverCheckHandler.removeCallbacks(serverCheckRunnable);
serverCheckHandler.post(serverCheckRunnable);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
private void toggleTheme() {
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
int currentMode = AppCompatDelegate.getDefaultNightMode();
int nextMode = (currentMode == AppCompatDelegate.MODE_NIGHT_NO) ? AppCompatDelegate.MODE_NIGHT_YES :
(currentMode == AppCompatDelegate.MODE_NIGHT_YES) ? AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM : AppCompatDelegate.MODE_NIGHT_NO;
sharedPref.edit().putInt("ui_mode", nextMode).apply();
AppCompatDelegate.setDefaultNightMode(nextMode);
updateThemeToggleButton(nextMode);
}
private void applySavedTheme() {
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
int savedMode = sharedPref.getInt("ui_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
AppCompatDelegate.setDefaultNightMode(savedMode);
updateThemeToggleButton(savedMode);
}
private void updateThemeToggleButton(int mode) {
if (mode == AppCompatDelegate.MODE_NIGHT_NO) themeToggle.setImageResource(R.drawable.ic_theme_dark);
else if (mode == AppCompatDelegate.MODE_NIGHT_YES) themeToggle.setImageResource(R.drawable.ic_theme_light);
else themeToggle.setImageResource(R.drawable.ic_theme_system);
}
@Override
protected void onStart() {
super.onStart();
IntentFilter filter = new IntentFilter();
filter.addAction(IIABWatchdog.ACTION_LOG_MESSAGE);
filter.addAction(WatchdogService.ACTION_STATE_STARTED);
filter.addAction(WatchdogService.ACTION_STATE_STOPPED);
ContextCompat.registerReceiver(this, logReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
}
@Override
protected void onStop() {
super.onStop();
try { unregisterReceiver(logReceiver); } catch (Exception e) {}
stopLogSizeUpdates();
}
@Override
public void onClick(View view) {
// Delegated
}
public void handleWatchdogClick() {
setWatchdogState(!prefs.getWatchdogEnable());
}
private void setWatchdogState(boolean enable) {
prefs.setWatchdogEnable(enable);
Intent intent = new Intent(this, WatchdogService.class);
if (enable) {
forceTermuxToForeground();
intent.setAction(WatchdogService.ACTION_START);
addToLog(getString(R.string.watchdog_started));
if (isServerAlive && usageFragment != null) usageFragment.startFusionPulse();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
} else {
startService(intent);
}
} else {
addToLog(getString(R.string.watchdog_stopped));
if (usageFragment != null) usageFragment.startExitPulse();
stopService(intent);
}
updateUI();
updateUIColorsAndVisibility();
}
public void handleControlClick() {
if (!isServerAlive) {
Snackbar.make(findViewById(android.R.id.content), R.string.qr_error_no_server, Snackbar.LENGTH_LONG).show();
return;
}
if (prefs.getEnable()) {
BiometricHelper.prompt(this,
getString(R.string.auth_required_title),
getString(R.string.auth_required_subtitle),
() -> {
addToLog(getString(R.string.auth_success_disconnect));
toggleService(true);
});
} else {
if (BiometricHelper.isDeviceSecure(this)) {
addToLog(getString(R.string.user_initiated_conn));
toggleService(false);
} else {
BiometricHelper.showEnrollmentDialog(this);
}
}
}
public void handleBrowseContentClick(View v) {
if (!isServerAlive) {
Snackbar.make(v, R.string.qr_error_no_server, Snackbar.LENGTH_LONG).show();
return;
}
if (currentTargetUrl != null) {
Intent intent = new Intent(this, PortalActivity.class);
intent.putExtra("TARGET_URL", currentTargetUrl);
startActivity(intent);
}
}
public void handleServerLaunchClick(View v) {
// Set a hard timeout as a safety net
timeoutRunnable = () -> {
if (targetServerState != null) {
targetServerState = null; // Abort transition
if (usageFragment != null) runOnUiThread(() -> usageFragment.stopBtnProgress());
updateUIColorsAndVisibility();
addToLog(getString(R.string.server_timeout_warning));
}
};
timeoutHandler.postDelayed(timeoutRunnable, getResources().getInteger(R.integer.server_cool_off_duration_ms));
// Execute the corresponding script command
if (!isServerAlive) {
startTermuxEnvironmentVisible("--start");
// Fallback for Oppo/Xiaomi
new Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
if (targetServerState != null && !isServerAlive) {
Snackbar.make(v, R.string.termux_stuck_warning, Snackbar.LENGTH_LONG).show();
}
}, getResources().getInteger(R.integer.server_snackbar_delay_ms));
} else {
startTermuxEnvironmentVisible("--stop");
// Turn off Watchdog gracefully when stopping the server manually
if (prefs.getWatchdogEnable()) {
setWatchdogState(false);
}
}
}
private void toggleService(boolean stop) {
prefs.setEnable(!stop);
savePrefs();
Intent intent = new Intent(this, TProxyService.class);
startService(intent.setAction(stop ? TProxyService.ACTION_DISCONNECT : TProxyService.ACTION_CONNECT));
addToLog(getString(stop ? R.string.vpn_stopping : R.string.vpn_starting));
if (!stop) {
runNegotiationSequence();
} else {
updateUIColorsAndVisibility();
}
}
public void updateUI() {
if (usageFragment != null) {
usageFragment.updateUI();
}
}
private void checkServerStatus() {
if (isNegotiating) return;
new Thread(() -> {
boolean localAlive = pingUrl("http://localhost:8085/home", false);
boolean vpnOn = prefs.getEnable();
boolean boxAlive = false;
if (vpnOn) {
// The passive radar must also use the proxy to test the tunnel.
boxAlive = pingUrl("http://box/home", true);
isProxyDegraded = !boxAlive;
} else {
isProxyDegraded = false;
}
isServerAlive = localAlive || boxAlive;
// STATE MACHINE: Has the target state been reached?
if (targetServerState != null && isServerAlive == targetServerState) {
targetServerState = null; // Transition complete!
timeoutHandler.removeCallbacks(timeoutRunnable); // Cancel safety net
if (usageFragment != null) runOnUiThread(() -> usageFragment.stopBtnProgress());
}
if (vpnOn && boxAlive) {
currentTargetUrl = "http://box/home";
} else if (localAlive) {
currentTargetUrl = "http://localhost:8085/home";
} else {
currentTargetUrl = null;
}
runOnUiThread(this::updateUIColorsAndVisibility);
}).start();
}
public void updateUIColorsAndVisibility() {
if (usageFragment != null) {
usageFragment.updateUIColorsAndVisibility();
}
}
private void startTermuxEnvironmentVisible(String actionFlag) {
Intent intent = new Intent();
intent.setClassName("com.termux", "com.termux.app.RunCommandService");
intent.setAction("com.termux.RUN_COMMAND");
intent.putExtra("com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home");
intent.putExtra("com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/env");
intent.putExtra("com.termux.RUN_COMMAND_ARGUMENTS", new String[]{
"INTENT_MODE=headless",
"/data/data/com.termux/files/usr/bin/bash",
"/data/data/com.termux/files/usr/bin/iiab-termux",
actionFlag
});
intent.putExtra("com.termux.RUN_COMMAND_BACKGROUND", false);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
} else {
startService(intent);
}
addToLog(getString(R.string.sent_to_termux, actionFlag));
} catch (Exception e) {
addToLog(getString(R.string.failed_termux_intent, e.getMessage()));
}
}
private void updateConnectivityStatus() {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
boolean isWifiOn = wifiManager != null && wifiManager.isWifiEnabled();
boolean isHotspotOn = false;
try {
// 1. Try standard reflection (Works on older Androids)
java.lang.reflect.Method method = wifiManager.getClass().getDeclaredMethod("isWifiApEnabled");
method.setAccessible(true);
isHotspotOn = (Boolean) method.invoke(wifiManager);
} catch (Throwable e) {
// 2. Fallback for Android 10+: Check physical network interfaces
try {
java.util.Enumeration<java.net.NetworkInterface> interfaces = java.net.NetworkInterface.getNetworkInterfaces();
while (interfaces != null && interfaces.hasMoreElements()) {
java.net.NetworkInterface iface = interfaces.nextElement();
String name = iface.getName();
if ((name.startsWith("ap") || name.startsWith("swlan")) && iface.isUp()) {
isHotspotOn = true;
break;
}
}
} catch (Exception ex) {}
}
// Store states for the QR button logic
this.isWifiActive = isWifiOn;
this.isHotspotActive = isHotspotOn;
if (usageFragment != null) {
runOnUiThread(() -> usageFragment.updateConnectivityLeds(this.isWifiActive, this.isHotspotActive));
}
}
public void savePrefs() {
if (usageFragment != null) {
usageFragment.savePrefsFromUI();
}
}
public void addToLog(String message) {
if (usageFragment != null) {
usageFragment.addToLog(message);
}
}
private void setVersionFooter() {
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
String version = pInfo.versionName;
String footerText = getString(R.string.version_footer_format, version);
versionFooter.setText(footerText);
} catch (PackageManager.NameNotFoundException e) {
versionFooter.setText(getString(R.string.version_footer_fallback));
}
}
private void forceTermuxToForeground() {
try {
Intent intent = getPackageManager().getLaunchIntentForPackage("com.termux");
if (intent != null) {
// Bring existing activity to the foreground
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
addToLog(getString(R.string.force_termux_foreground));
}
} catch (Exception e) {
addToLog(getString(R.string.termux_invocation_error, e.getMessage()));
}
}
// --- PERMISSION CHECKERS FOR UI OPACITY ---
private void updateHeaderIconsOpacity() {
boolean hasAllControllerPerms = hasNotifPermission() && hasTermuxPermission() && hasBatteryPermission() && hasStoragePermission();
boolean hasTermuxStorage = hasTermuxStoragePermission();
// If any vital permission is missing, dim the icons to 40% opacity (0.4f)
boolean allPerfect = hasAllControllerPerms && hasTermuxStorage;
float targetAlpha = allPerfect ? 1.0f : 0.4f;
if (btnSettings != null) btnSettings.setAlpha(targetAlpha);
if (headerIcon != null) headerIcon.setAlpha(targetAlpha);
}
private boolean hasNotifPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
}
return true;
}
private boolean hasTermuxPermission() {
return ContextCompat.checkSelfPermission(this, TERMUX_PERMISSION) == PackageManager.PERMISSION_GRANTED;
}
private boolean hasBatteryPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm != null && pm.isIgnoringBatteryOptimizations(getPackageName());
}
return true;
}
private boolean hasTermuxStoragePermission() {
try {
int result = getPackageManager().checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE, "com.termux");
if (result == PackageManager.PERMISSION_GRANTED) return true;
// Fallback: If Android denies the package query, check if the directory actually exists
File stateDir = new File(android.os.Environment.getExternalStorageDirectory(), ".iiab_state");
return stateDir.exists();
} catch (Exception e) {
return false;
}
}
private boolean hasStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
}
}

View File

@ -1,41 +0,0 @@
/*
* ============================================================================
* Name : MainPagerAdapter.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Main Pager Adapter
* ============================================================================
*/
package org.iiab.controller;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
public class MainPagerAdapter extends FragmentStateAdapter {
public MainPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new DashboardFragment();
case 1:
return new UsageFragment();
case 2:
return new DeployFragment();
default:
return new DashboardFragment();
}
}
@Override
public int getItemCount() {
return 3;
}
}

View File

@ -1,246 +0,0 @@
/*
* ============================================================================
* Name : PortalActivity.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Webview portal activity
* ============================================================================
*/
package org.iiab.controller;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.webkit.ProxyConfig;
import androidx.webkit.ProxyController;
import androidx.webkit.WebViewFeature;
import java.util.concurrent.Executor;
import android.graphics.Bitmap;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
public class PortalActivity extends AppCompatActivity {
private static final String TAG = "IIAB-Portal";
private WebView webView;
private boolean isPageLoading = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_portal);
// 1. Basic WebView configuration
webView = findViewById(R.id.myWebView);
LinearLayout bottomNav = findViewById(R.id.bottomNav);
Button btnHandle = findViewById(R.id.btnHandle); // The new handle
Button btnHideNav = findViewById(R.id.btnHideNav); // Button to close
Button btnBack = findViewById(R.id.btnBack);
Button btnHome = findViewById(R.id.btnHome);
Button btnReload = findViewById(R.id.btnReload);
Button btnExit = findViewById(R.id.btnExit);
Button btnForward = findViewById(R.id.btnForward);
// --- PREPARE HIDDEN BAR ---
// Wait for Android to draw the screen to determine bar height
// and hide it exactly below the bottom edge.
bottomNav.post(() -> {
bottomNav.setTranslationY(bottomNav.getHeight()); // Move outside the screen
bottomNav.setVisibility(View.VISIBLE); // Remove invisibility
});
// --- AUTO-HIDE TIMER ---
Handler hideHandler = new Handler(Looper.getMainLooper());
// This is the hiding action packaged for later use
Runnable hideRunnable = () -> {
bottomNav.animate().translationY(bottomNav.getHeight()).setDuration(250);
btnHandle.setVisibility(View.VISIBLE);
btnHandle.animate().alpha(1f).setDuration(150);
};
// --- Restart timer ---
Runnable resetTimer = () -> {
hideHandler.removeCallbacks(hideRunnable);
hideHandler.postDelayed(hideRunnable, 5000); // Restarts new 5 sec
};
// --- HANDLE LOGIC (Show Bar) ---
btnHandle.setOnClickListener(v -> {
// 1. Animate entry
btnHandle.animate().alpha(0f).setDuration(150).withEndAction(() -> btnHandle.setVisibility(View.GONE));
bottomNav.animate().translationY(0).setDuration(250);
// 2. Starts countdown
resetTimer.run();
});
// Button actions
btnBack.setOnClickListener(v -> {
if (webView.canGoBack()) webView.goBack();
resetTimer.run();
});
btnForward.setOnClickListener(v -> {
if (webView.canGoForward()) webView.goForward();
resetTimer.run();
});
Preferences prefs = new Preferences(this);
boolean isVpnActive = prefs.getEnable();
String rawUrl = getIntent().getStringExtra("TARGET_URL");
// If for some strange reason the URL arrives empty, we use the security fallback
if (rawUrl == null || rawUrl.isEmpty()) {
rawUrl = "http://localhost:8085/home";
}
// We are giving the URL secure global reach for all lambdas from now on
final String finalTargetUrl = rawUrl;
btnHome.setOnClickListener(v -> {
webView.loadUrl(finalTargetUrl);
resetTimer.run();
});
// Dual logic: Forced reload or Stop
btnReload.setOnClickListener(v -> {
if (isPageLoading) {
webView.stopLoading();
} else {
// Disable cache temporarily
webView.getSettings().setCacheMode(android.webkit.WebSettings.LOAD_NO_CACHE);
// Force download from scratch
webView.reload();
}
resetTimer.run();
});
// --- NEW: DETECT LOADING TO CHANGE BUTTON TO 'X' ---
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, android.webkit.WebResourceRequest request) {
String url = request.getUrl().toString();
String host = request.getUrl().getHost();
// Internal server link (Box)
if (host != null && (host.equals("box") || host.equals("127.0.0.1") || host.equals("localhost"))) {
return false; // Remains in our app and travels through the proxy
}
// External link (Real Internet)
try {
// Tell Android to find the correct app to open this (Chrome, YouTube, etc.)
Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
startActivity(intent);
} catch (Exception e) {
Log.e(TAG, "No app installed to open: " + url);
}
return true; // return true means: "WebView, I'll handle it, you ignore this click"
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
isPageLoading = true;
btnReload.setText(""); // Change to Stop
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
isPageLoading = false;
btnReload.setText(""); // Back to Reload
// Restore cache for normal browsing speed
view.getSettings().setCacheMode(android.webkit.WebSettings.LOAD_DEFAULT);
}
@Override
public void onReceivedError(WebView view, android.webkit.WebResourceRequest request, android.webkit.WebResourceError error) {
super.onReceivedError(view, request, error);
if (request.isForMainFrame()) {
String customErrorHtml = "<html><body style='background-color:#1A1A1A;color:#FFFFFF;text-align:center;padding-top:50px;font-family:sans-serif;'>"
+ "<h2>⚠️ Connection Failed</h2>"
+ "<p>Unable to reach the secure environment.</p>"
+ "<p style='color:#888;font-size:12px;'>Error: " + error.getDescription() + "</p>"
+ "</body></html>";
view.loadData(customErrorHtml, "text/html", "UTF-8");
isPageLoading = false;
btnReload.setText("");
}
}
});
// --- MANUALLY CLOSE BAR LOGIC ---
btnHideNav.setOnClickListener(v -> {
hideHandler.removeCallbacks(hideRunnable); // Cancel the timer so it doesn't conflict
hideRunnable.run(); // Execute hiding action immediately
});
// <-- EXIT ACTION -->
btnExit.setOnClickListener(v -> finish());
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setDomStorageEnabled(true);
// Port and Mirror logic
int tempPort = prefs.getSocksPort();
if (tempPort <= 0) tempPort = 1080;
// We restored the secure variable for the port
final int finalProxyPort = tempPort;
// 4. Proxy block (ONLY IF VPN IS ACTIVE)
if (isVpnActive) {
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
ProxyConfig proxyConfig = new ProxyConfig.Builder()
.addProxyRule("socks5://127.0.0.1:" + finalProxyPort)
.build();
Executor executor = ContextCompat.getMainExecutor(this);
ProxyController.getInstance().setProxyOverride(proxyConfig, executor, () -> {
Log.d(TAG, "Proxy configured on port: " + finalProxyPort);
// Load HTML only when proxy is ready
webView.loadUrl(finalTargetUrl);
});
} else {
// Fallback for older devices
Log.w(TAG, "Proxy Override not supported");
webView.loadUrl(finalTargetUrl);
}
} else {
// VPN is OFF. Do NOT use proxy. Just load localhost directly.
webView.loadUrl(finalTargetUrl);
}
}
// Cleanup (Important to not leave the proxy active)
@Override
protected void onDestroy() {
super.onDestroy();
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
ProxyController.getInstance().clearProxyOverride(Runnable::run, () -> {
Log.d(TAG, "WebView proxy released");
});
}
}
@Override
public void onBackPressed() {
if (webView.canGoBack()) {
webView.goBack();
} else {
super.onBackPressed();
}
}
}

View File

@ -1,232 +0,0 @@
/*
============================================================================
Name : Preferences.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2023 xyz
Copyright (c) 2026 IIAB Project
Description : Preferences
============================================================================
*/
package org.iiab.controller;
import java.util.Set;
import java.util.HashSet;
import android.content.Context;
import android.content.SharedPreferences;
public class Preferences
{
public static final String PREFS_NAME = "SocksPrefs";
public static final String SOCKS_ADDR = "SocksAddr";
public static final String SOCKS_UDP_ADDR = "SocksUdpAddr";
public static final String SOCKS_PORT = "SocksPort";
public static final String SOCKS_USER = "SocksUser";
public static final String SOCKS_PASS = "SocksPass";
public static final String DNS_IPV4 = "DnsIpv4";
public static final String DNS_IPV6 = "DnsIpv6";
public static final String IPV4 = "Ipv4";
public static final String IPV6 = "Ipv6";
public static final String GLOBAL = "Global";
public static final String UDP_IN_TCP = "UdpInTcp";
public static final String REMOTE_DNS = "RemoteDNS";
public static final String APPS = "Apps";
public static final String ENABLE = "Enable";
public static final String WATCHDOG_ENABLE = "WatchdogEnable";
public static final String MAINTENANCE_MODE = "MaintenanceMode";
private SharedPreferences prefs;
public Preferences(Context context) {
prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_MULTI_PROCESS);
}
public String getSocksAddress() {
return prefs.getString(SOCKS_ADDR, "127.0.0.1");
}
public void setSocksAddress(String addr) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(SOCKS_ADDR, addr);
editor.commit();
}
public String getSocksUdpAddress() {
return prefs.getString(SOCKS_UDP_ADDR, "");
}
public void setSocksUdpAddress(String addr) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(SOCKS_UDP_ADDR, addr);
editor.commit();
}
public int getSocksPort() {
return prefs.getInt(SOCKS_PORT, 1080);
}
public void setSocksPort(int port) {
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(SOCKS_PORT, port);
editor.commit();
}
public String getSocksUsername() {
return prefs.getString(SOCKS_USER, "");
}
public void setSocksUsername(String user) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(SOCKS_USER, user);
editor.commit();
}
public String getSocksPassword() {
return prefs.getString(SOCKS_PASS, "");
}
public void setSocksPassword(String pass) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(SOCKS_PASS, pass);
editor.commit();
}
public String getDnsIpv4() {
return prefs.getString(DNS_IPV4, "8.8.8.8");
}
public void setDnsIpv4(String addr) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(DNS_IPV4, addr);
editor.commit();
}
public String getDnsIpv6() {
return prefs.getString(DNS_IPV6, "2001:4860:4860::8888");
}
public void setDnsIpv6(String addr) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(DNS_IPV6, addr);
editor.commit();
}
public String getMappedDns() {
return "198.18.0.2";
}
public boolean getUdpInTcp() {
return prefs.getBoolean(UDP_IN_TCP, false);
}
public void setUdpInTcp(boolean enable) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(UDP_IN_TCP, enable);
editor.commit();
}
public boolean getRemoteDns() {
return prefs.getBoolean(REMOTE_DNS, true);
}
public void setRemoteDns(boolean enable) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(REMOTE_DNS, enable);
editor.commit();
}
public boolean getIpv4() {
return prefs.getBoolean(IPV4, true);
}
public void setIpv4(boolean enable) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(IPV4, enable);
editor.commit();
}
public boolean getIpv6() {
return prefs.getBoolean(IPV6, true);
}
public void setIpv6(boolean enable) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(IPV6, enable);
editor.commit();
}
public boolean getGlobal() {
return prefs.getBoolean(GLOBAL, false);
}
public void setGlobal(boolean enable) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(GLOBAL, enable);
editor.commit();
}
public Set<String> getApps() {
return prefs.getStringSet(APPS, new HashSet<String>());
}
public void setApps(Set<String> apps) {
SharedPreferences.Editor editor = prefs.edit();
editor.putStringSet(APPS, apps);
editor.commit();
}
public boolean getEnable() {
return prefs.getBoolean(ENABLE, false);
}
public void setEnable(boolean enable) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(ENABLE, enable);
editor.commit();
}
public boolean getWatchdogEnable() {
return prefs.getBoolean(WATCHDOG_ENABLE, false);
}
public void setWatchdogEnable(boolean enable) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(WATCHDOG_ENABLE, enable);
editor.commit();
}
public boolean getMaintenanceMode() {
return prefs.getBoolean(MAINTENANCE_MODE, true);
}
public void setMaintenanceMode(boolean enable) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(MAINTENANCE_MODE, enable);
editor.commit();
}
public int getTunnelMtu() {
return 8500;
}
public String getTunnelIpv4Address() {
return "198.18.0.1";
}
public int getTunnelIpv4Prefix() {
return 32;
}
public String getTunnelIpv6Address() {
return "fc00::1";
}
public int getTunnelIpv6Prefix() {
return 128;
}
public int getTaskStackSize() {
return 81920;
}
}

View File

@ -1,155 +0,0 @@
/*
* ============================================================================
* Name : ProgressButton.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Button animation helper
* ============================================================================
*/
package org.iiab.controller;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Path;
import android.util.AttributeSet;
import androidx.core.content.ContextCompat;
import androidx.appcompat.widget.AppCompatButton;
public class ProgressButton extends AppCompatButton {
private Paint progressPaint;
private Paint progressBackgroundPaint;
private int progressColor;
private int progressBackgroundColor;
private Path clipPath;
private RectF rectF;
private float cornerRadius;
private int progressHeight;
// Animation variables
private float currentProgress = 0f;
private boolean isRunning = false;
private ValueAnimator animator;
public ProgressButton(Context context) {
super(context);
init(context, null);
}
public ProgressButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ProgressButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressButton, 0, 0);
try {
progressColor = a.getColor(R.styleable.ProgressButton_progressButtonColor, ContextCompat.getColor(context, R.color.btn_danger));
progressBackgroundColor = a.getColor(R.styleable.ProgressButton_progressButtonBackgroundColor, Color.parseColor("#44888888"));
progressHeight = a.getDimensionPixelSize(R.styleable.ProgressButton_progressButtonHeight, (int) (6 * getResources().getDisplayMetrics().density));
// Note: We no longer read 'duration' from XML because the animation is infinite.
} finally {
a.recycle();
}
} else {
// Safe defaults
progressColor = ContextCompat.getColor(context, R.color.btn_danger);
progressBackgroundColor = Color.parseColor("#44888888");
progressHeight = (int) (6 * getResources().getDisplayMetrics().density);
}
progressPaint = new Paint();
progressPaint.setColor(progressColor);
progressPaint.setStyle(Paint.Style.FILL);
progressBackgroundPaint = new Paint();
progressBackgroundPaint.setColor(progressBackgroundColor);
progressBackgroundPaint.setStyle(Paint.Style.FILL);
// Initialize clipping path variables
clipPath = new Path();
rectF = new RectF();
cornerRadius = 8 * getResources().getDisplayMetrics().density;
}
@Override
protected void onDraw(Canvas canvas) {
// Draw the background and text first
super.onDraw(canvas);
// Draw the progress bar constrained by the button's rounded corners
if (progressHeight > 0 && isRunning) {
int buttonWidth = getWidth();
int buttonHeight = getHeight();
// Calculate width based on the current animated float (0.0f to 1.0f)
int progressWidth = (int) (buttonWidth * currentProgress);
// 1. Prepare the rounded mask (matches the button's bounds)
rectF.set(0, 0, buttonWidth, buttonHeight);
clipPath.reset();
clipPath.addRoundRect(rectF, cornerRadius, cornerRadius, Path.Direction.CW);
// 2. Save canvas state and apply the mask
canvas.save();
canvas.clipPath(clipPath);
// 3. Draw the tracks
canvas.drawRect(0, buttonHeight - progressHeight, buttonWidth, buttonHeight, progressBackgroundPaint);
canvas.drawRect(0, buttonHeight - progressHeight, progressWidth, buttonHeight, progressPaint);
// 4. Restore canvas
canvas.restore();
}
}
/**
* Starts an infinite cyclic animation (fills and empties the bar).
* Disables the button to prevent spam clicks.
*/
public void startProgress() {
if (isRunning) return;
isRunning = true;
setEnabled(false); // Lock the button immediately
// Create an animator that goes from 0.0 to 1.0 (empty to full)
animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(1200); // 1.2 seconds per sweep
animator.setRepeatMode(ValueAnimator.REVERSE); // Fill up, then empty down
animator.setRepeatCount(ValueAnimator.INFINITE); // Never stop until commanded
animator.addUpdateListener(animation -> {
currentProgress = (float) animation.getAnimatedValue();
invalidate(); // Force redraw on every frame
});
animator.start();
}
/**
* Stops the animation, clears the bar, and unlocks the button.
* To be called by the Controller when the backend confirms the state change.
*/
public void stopProgress() {
if (animator != null && animator.isRunning()) {
animator.cancel();
}
isRunning = false;
setEnabled(true); // Unlock button
currentProgress = 0f; // Reset width
invalidate(); // Clear the bar visually
}
}

View File

@ -1,192 +0,0 @@
/*
* ============================================================================
* Name : QrActivity.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : QR share content helper
* ============================================================================
*/
package org.iiab.controller;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
public class QrActivity extends AppCompatActivity {
private TextView titleText;
private TextView ipText;
private ImageView qrImageView;
private ImageButton btnFlip;
private View cardContainer;
private String wifiIp = null;
private String hotspotIp = null;
private boolean showingWifi = true; // Tracks which network is currently displayed
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qr); // Rename your dialog_qr.xml to this
titleText = findViewById(R.id.qr_network_title);
ipText = findViewById(R.id.qr_ip_text);
qrImageView = findViewById(R.id.qr_image_view);
btnFlip = findViewById(R.id.btn_flip_qr);
cardContainer = findViewById(R.id.qr_card_container);
Button btnClose = findViewById(R.id.btn_close_qr);
// Improve 3D perspective to avoid visual clipping during rotation
float distance = 8000 * getResources().getDisplayMetrics().density;
cardContainer.setCameraDistance(distance);
btnClose.setOnClickListener(v -> finish());
btnFlip.setOnClickListener(v -> {
// Disable button during animation to prevent spam
btnFlip.setEnabled(false);
animateCardFlip();
});
// 1. Fetch real physical IPs with strict interface naming
fetchNetworkInterfaces();
// 2. Determine initial state and button visibility
if (wifiIp != null && hotspotIp != null) {
btnFlip.setVisibility(View.VISIBLE); // Both active, enable flipping
showingWifi = true;
} else if (wifiIp != null) {
btnFlip.setVisibility(View.GONE);
showingWifi = true;
} else if (hotspotIp != null) {
btnFlip.setVisibility(View.GONE);
showingWifi = false;
} else {
// Fallback just in case they died between the MainActivity click and this onCreate
finish();
return;
}
updateQrDisplay();
}
/**
* Performs a 3D flip animation. Swaps the data halfway through when the card is invisible.
*/
private void animateCardFlip() {
// Phase 1: Rotate out (0 to 90 degrees)
ObjectAnimator flipOut = ObjectAnimator.ofFloat(cardContainer, "rotationY", 0f, 90f);
flipOut.setDuration(200); // 200ms
flipOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Card is edge-on (invisible). Swap the data!
showingWifi = !showingWifi;
updateQrDisplay();
// Phase 2: Rotate in from the other side (-90 to 0 degrees)
cardContainer.setRotationY(-90f);
ObjectAnimator flipIn = ObjectAnimator.ofFloat(cardContainer, "rotationY", -90f, 0f);
flipIn.setDuration(200);
flipIn.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
btnFlip.setEnabled(true); // Unlock button
}
});
flipIn.start();
}
});
flipOut.start();
}
/**
* Updates the UI text and generates the new QR Code
*/
private void updateQrDisplay() {
String currentIp = showingWifi ? wifiIp : hotspotIp;
String title = showingWifi ? getString(R.string.qr_title_wifi) : getString(R.string.qr_title_hotspot);
// 8085 is the default port for the IIAB interface
String url = "http://" + currentIp + ":8085/home";
titleText.setText(title);
ipText.setText(url);
Bitmap qrBitmap = generateQrCode(url);
if (qrBitmap != null) {
qrImageView.setImageBitmap(qrBitmap);
}
}
/**
* Strictly categorizes network interfaces to avoid Hotspot being labeled as Wi-Fi.
*/
private void fetchNetworkInterfaces() {
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface intf : interfaces) {
String name = intf.getName();
if (!intf.isUp()) continue;
// Strict categorizations
boolean isStrictWifi = name.equals("wlan0");
boolean isHotspot = name.startsWith("ap") || name.startsWith("swlan") || name.equals("wlan1") || name.equals("wlan2");
if (isStrictWifi || isHotspot) {
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
for (InetAddress addr : addrs) {
if (!addr.isLoopbackAddress() && addr instanceof Inet4Address) {
if (isStrictWifi) wifiIp = addr.getHostAddress();
if (isHotspot) hotspotIp = addr.getHostAddress();
}
}
}
}
} catch (Exception ignored) { }
}
/**
* Generates a pure Black & White Bitmap using ZXing.
*/
private Bitmap generateQrCode(String text) {
QRCodeWriter writer = new QRCodeWriter();
try {
// 800x800 guarantees high resolution on any screen
BitMatrix bitMatrix = writer.encode(text, BarcodeFormat.QR_CODE, 800, 800);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
bmp.setPixel(x, y, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE);
}
}
return bmp;
} catch (WriterException e) {
return null;
}
}
}

View File

@ -1,42 +0,0 @@
/*
============================================================================
Name : ServiceReceiver.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2023 xyz
Copyright (c) 2026 IIAB Project
Description : ServiceReceiver
============================================================================
*/
package org.iiab.controller;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.VpnService;
import android.os.Build;
public class ServiceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Preferences prefs = new Preferences(context);
/* Auto-start */
if (prefs.getEnable()) {
Intent i = VpnService.prepare(context);
if (i != null) {
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
i = new Intent(context, TProxyService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(i.setAction(TProxyService.ACTION_CONNECT));
} else {
context.startService(i.setAction(TProxyService.ACTION_CONNECT));
}
}
}
}
}

View File

@ -1,324 +0,0 @@
/*
* ============================================================================
* Name : SetupActivity.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Setup permission table helper
* ============================================================================
*/
package org.iiab.controller;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.net.VpnService;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.content.ContextCompat;
import com.google.android.material.snackbar.Snackbar;
public class SetupActivity extends AppCompatActivity {
private static final String TERMUX_PERMISSION = "com.termux.permission.RUN_COMMAND";
private SwitchCompat switchNotif, switchTermux, switchStorage, switchVpn, switchBattery;
private Button btnContinue;
private Button btnManageAll;
private Button btnTermuxOverlay;
private Button btnTermuxStorage;
private Button btnManageTermux;
private ActivityResultLauncher<String> requestPermissionLauncher;
private ActivityResultLauncher<Intent> storageLauncher;
private ActivityResultLauncher<Intent> vpnLauncher;
private ActivityResultLauncher<Intent> batteryLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_setup);
TextView welcomeText = findViewById(R.id.setup_welcome_text);
welcomeText.setText(getString(R.string.setup_welcome, getString(R.string.app_name)));
switchNotif = findViewById(R.id.switch_perm_notifications);
switchTermux = findViewById(R.id.switch_perm_termux);
switchStorage = findViewById(R.id.switch_perm_storage);
switchVpn = findViewById(R.id.switch_perm_vpn);
switchBattery = findViewById(R.id.switch_perm_battery);
btnContinue = findViewById(R.id.btn_setup_continue);
btnManageAll = findViewById(R.id.btn_manage_all);
btnTermuxOverlay = findViewById(R.id.btn_termux_overlay);
btnTermuxStorage = findViewById(R.id.btn_termux_storage);
btnManageTermux = findViewById(R.id.btn_manage_termux);
// Hide Notification switch if Android < 13
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
switchNotif.setVisibility(android.view.View.GONE);
}
setupLaunchers();
setupListeners();
checkAllPermissions();
btnContinue.setOnClickListener(v -> {
// Tell bash that permissions are handled
writeTermuxPermissionFlags();
// Save flag so we don't show this screen again
SharedPreferences prefs = getSharedPreferences(getString(R.string.pref_file_internal), Context.MODE_PRIVATE);
prefs.edit().putBoolean(getString(R.string.pref_key_setup_complete), true).apply();
finish();
});
}
private void setupLaunchers() {
requestPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
isGranted -> checkAllPermissions()
);
storageLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> checkAllPermissions()
);
vpnLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> checkAllPermissions()
);
batteryLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> checkAllPermissions()
);
}
private void setupListeners() {
switchNotif.setOnClickListener(v -> {
if (hasNotifPermission()) {
handleRevokeAttempt(v);
return;
}
if (switchNotif.isChecked()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
}
}
switchNotif.setChecked(false); // Force visual state back until system confirms
});
switchTermux.setOnClickListener(v -> {
if (hasTermuxPermission()) {
handleRevokeAttempt(v);
return;
}
if (switchTermux.isChecked()) {
requestPermissionLauncher.launch(TERMUX_PERMISSION);
}
switchTermux.setChecked(false);
});
switchStorage.setOnClickListener(v -> {
if (hasStoragePermission()) {
handleRevokeAttempt(v);
return;
}
if (switchStorage.isChecked()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s", getApplicationContext().getPackageName())));
storageLauncher.launch(intent);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
storageLauncher.launch(intent);
}
} else {
requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
}
switchStorage.setChecked(false); // Force visual state back until system confirms
});
switchVpn.setOnClickListener(v -> {
if (hasVpnPermission()) {
handleRevokeAttempt(v);
return;
}
if (switchVpn.isChecked()) {
Intent intent = VpnService.prepare(this);
if (intent != null) {
vpnLauncher.launch(intent);
} else {
checkAllPermissions(); // Already granted
}
}
switchVpn.setChecked(false);
});
switchBattery.setOnClickListener(v -> {
if (hasBatteryPermission()) {
handleRevokeAttempt(v);
return;
}
if (switchBattery.isChecked()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + getPackageName()));
batteryLauncher.launch(intent);
}
}
switchBattery.setChecked(false);
});
// Direct access to all the Controller permissions
btnManageAll.setOnClickListener(v -> openAppSettings());
// Direct access to Termux Overlay permissions
btnTermuxOverlay.setOnClickListener(v -> {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:com.termux"));
startActivity(intent);
} catch (Exception e) {
Snackbar.make(v, R.string.termux_not_installed_error, Snackbar.LENGTH_LONG).show();
}
});
// Direct access to Termux settings to grant Files/Storage permission
btnTermuxStorage.setOnClickListener(v -> {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:com.termux"));
startActivity(intent);
// Toast.makeText(this, "Please go to Permissions and allow Storage/Files", Toast.LENGTH_LONG).show();
} catch (Exception e) {
Snackbar.make(v, R.string.termux_not_installed_error, Snackbar.LENGTH_LONG).show();
}
});
// Direct access to Controller settings (Reuses the method from Phase 1)
btnManageTermux.setOnClickListener(v -> {
try {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:com.termux"));
startActivity(intent);
} catch (Exception e) {
Snackbar.make(v, R.string.termux_not_installed, Snackbar.LENGTH_LONG).show();
}
});
}
@Override
protected void onResume() {
super.onResume();
checkAllPermissions(); // Refresh state if user returns from settings
}
/**
* It displays visual feedback (shake) and a message when the user
* tries to turn off a permission.
*/
private void handleRevokeAttempt(View switchView) {
// Force the switch to stay checked visually
((SwitchCompat) switchView).setChecked(true);
// Animate the switch (Shake)
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
switchView.startAnimation(shake);
// Show Snackbar with action to go to Settings
Snackbar.make(findViewById(android.R.id.content), R.string.revoke_permission_warning, Snackbar.LENGTH_LONG)
.setAction(R.string.settings_label, v -> openAppSettings()).show();
}
private void openAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
private void checkAllPermissions() {
boolean notif = hasNotifPermission();
boolean termux = hasTermuxPermission();
boolean storage = hasStoragePermission();
boolean vpn = hasVpnPermission();
boolean battery = hasBatteryPermission();
switchNotif.setChecked(notif);
switchTermux.setChecked(termux);
switchStorage.setChecked(storage);
switchVpn.setChecked(vpn);
switchBattery.setChecked(battery);
boolean allGranted = termux && storage && vpn && battery;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
allGranted = allGranted && notif;
}
btnContinue.setEnabled(allGranted);
btnContinue.setBackgroundTintList(ContextCompat.getColorStateList(this,
allGranted ? R.color.btn_explore_ready : R.color.btn_explore_disabled));
}
private boolean hasNotifPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
}
return true;
}
private boolean hasTermuxPermission() {
return ContextCompat.checkSelfPermission(this, TERMUX_PERMISSION) == PackageManager.PERMISSION_GRANTED;
}
private boolean hasVpnPermission() {
return VpnService.prepare(this) == null;
}
private boolean hasBatteryPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm != null && pm.isIgnoringBatteryOptimizations(getPackageName());
}
return true;
}
private boolean hasStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}
}
private void writeTermuxPermissionFlags() {
java.io.File stateDir = new java.io.File(android.os.Environment.getExternalStorageDirectory(), ".iiab_state");
if (!stateDir.exists()) {
stateDir.mkdirs();
}
try {
new java.io.File(stateDir, "flag_perm_battery").createNewFile();
new java.io.File(stateDir, "flag_perm_overlay").createNewFile();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -1,343 +0,0 @@
/*
============================================================================
Name : TProxyService.java
Author : hev <r@hev.cc>
Contributors: IIAB Project
Copyright : Copyright (c) 2024 xyz
Copyright (c) 2026 IIAB Project
Description : TProxy Service with integrated Watchdog
============================================================================
*/
package org.iiab.controller;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.VpnService;
import android.net.wifi.WifiManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.util.Log;
import androidx.core.app.NotificationCompat;
public class TProxyService extends VpnService {
private static final String TAG = "IIAB-TProxy";
private static native void TProxyStartService(String config_path, int fd);
private static native void TProxyStopService();
private static native long[] TProxyGetStats();
public static final String ACTION_CONNECT = "org.iiab.controller.CONNECT";
public static final String ACTION_DISCONNECT = "org.iiab.controller.DISCONNECT";
public static final String ACTION_WATCHDOG_SYNC = "org.iiab.controller.WATCHDOG_SYNC";
private PowerManager.WakeLock wakeLock;
private WifiManager.WifiLock wifiLock;
private Thread watchdogThread;
private volatile boolean isWatchdogRunning = false;
static {
System.loadLibrary("hev-socks5-tunnel");
}
private ParcelFileDescriptor tunFd = null;
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
String action = intent.getAction();
if (ACTION_DISCONNECT.equals(action)) {
stopService();
return START_NOT_STICKY;
} else if (ACTION_WATCHDOG_SYNC.equals(action)) {
syncWatchdogLocks();
return START_STICKY;
}
}
startService();
return START_STICKY;
}
private void syncWatchdogLocks() {
Preferences prefs = new Preferences(this);
boolean watchdogEnabled = prefs.getWatchdogEnable();
Log.d(TAG, getString(R.string.syncing_watchdog, watchdogEnabled));
if (watchdogEnabled) {
acquireLocks();
startWatchdogLoop();
} else {
stopWatchdogLoop();
releaseLocks();
}
}
private void startWatchdogLoop() {
if (isWatchdogRunning) return;
isWatchdogRunning = true;
IIABWatchdog.logSessionStart(this);
watchdogThread = new Thread(() -> {
Log.i(TAG, getString(R.string.watchdog_thread_started));
while (isWatchdogRunning) {
try {
// Perform only the heartbeat stimulus (Intent-based)
IIABWatchdog.performHeartbeat(this);
// TROUBLESHOOTING: Uncomment to test Termux responsiveness via ping
// IIABWatchdog.performDebugPing(this);
// Sleep for 30 seconds
Thread.sleep(30000);
} catch (InterruptedException e) {
Log.i(TAG, getString(R.string.watchdog_thread_interrupted));
break;
} catch (Exception e) {
Log.e(TAG, getString(R.string.watchdog_thread_error), e);
}
}
Log.i(TAG, getString(R.string.watchdog_thread_ended));
});
watchdogThread.setName("IIAB-Watchdog-Thread");
watchdogThread.start();
}
private void stopWatchdogLoop() {
isWatchdogRunning = false;
if (watchdogThread != null) {
watchdogThread.interrupt();
watchdogThread = null;
}
IIABWatchdog.logSessionStop(this);
}
private void acquireLocks() {
try {
if (wakeLock == null) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "IIAB:TProxyWakeLock");
wakeLock.acquire();
Log.i(TAG, getString(R.string.cpu_wakelock_acquired));
}
if (wifiLock == null) {
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "IIAB:TProxyWifiLock");
wifiLock.acquire();
Log.i(TAG, getString(R.string.wifi_lock_acquired));
}
} catch (Exception e) {
Log.e(TAG, getString(R.string.error_acquiring_locks), e);
}
}
private void releaseLocks() {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
wakeLock = null;
Log.i(TAG, getString(R.string.cpu_wakelock_released));
}
if (wifiLock != null && wifiLock.isHeld()) {
wifiLock.release();
wifiLock = null;
Log.i(TAG, getString(R.string.wifi_lock_released));
}
}
@Override
public void onDestroy() {
stopWatchdogLoop();
releaseLocks();
super.onDestroy();
}
@Override
public void onRevoke() {
super.onRevoke();
}
public void startService() {
if (tunFd != null) {
syncWatchdogLocks();
return;
}
Preferences prefs = new Preferences(this);
/* VPN */
String session = new String();
VpnService.Builder builder = new VpnService.Builder();
builder.setBlocking(false);
builder.setMtu(prefs.getTunnelMtu());
if (prefs.getIpv4()) {
String addr = prefs.getTunnelIpv4Address();
int prefix = prefs.getTunnelIpv4Prefix();
String dns = prefs.getDnsIpv4();
builder.addAddress(addr, prefix);
builder.addRoute("0.0.0.0", 0);
if (!prefs.getRemoteDns() && !dns.isEmpty())
builder.addDnsServer(dns);
session += "IPv4";
}
if (prefs.getIpv6()) {
String addr = prefs.getTunnelIpv6Address();
int prefix = prefs.getTunnelIpv6Prefix();
String dns = prefs.getDnsIpv6();
builder.addAddress(addr, prefix);
builder.addRoute("::", 0);
if (!prefs.getRemoteDns() && !dns.isEmpty())
builder.addDnsServer(dns);
if (!session.isEmpty())
session += " + ";
session += "IPv6";
}
if (prefs.getRemoteDns()) {
builder.addDnsServer(prefs.getMappedDns());
}
boolean disallowSelf = true;
if (prefs.getGlobal()) {
session += "/Global";
} else {
for (String appName : prefs.getApps()) {
try {
builder.addAllowedApplication(appName);
disallowSelf = false;
} catch (NameNotFoundException e) {
}
}
session += "/per-App";
}
if (disallowSelf) {
String selfName = getApplicationContext().getPackageName();
try {
builder.addDisallowedApplication(selfName);
if (prefs.getMaintenanceMode()) { // Verify if the maintenance mode is enabled
builder.addDisallowedApplication("com.termux");
Log.i(TAG, getString(R.string.maintenance_mode_enabled));
}
} catch (NameNotFoundException e) {
}
}
builder.setSession(session);
tunFd = builder.establish();
if (tunFd == null) {
stopSelf();
return;
}
/* TProxy */
File tproxy_file = new File(getCacheDir(), "tproxy.conf");
try {
tproxy_file.createNewFile();
FileOutputStream fos = new FileOutputStream(tproxy_file, false);
String tproxy_conf = "misc:\n" +
" task-stack-size: " + prefs.getTaskStackSize() + "\n" +
"tunnel:\n" +
" mtu: " + prefs.getTunnelMtu() + "\n";
tproxy_conf += "socks5:\n" +
" port: " + prefs.getSocksPort() + "\n" +
" address: '" + prefs.getSocksAddress() + "'\n" +
" udp: '" + (prefs.getUdpInTcp() ? "tcp" : "udp") + "'\n";
if (!prefs.getSocksUdpAddress().isEmpty()) {
tproxy_conf += " udp-address: '" + prefs.getSocksUdpAddress() + "'\n";
}
if (!prefs.getSocksUsername().isEmpty() &&
!prefs.getSocksPassword().isEmpty()) {
tproxy_conf += " username: '" + prefs.getSocksUsername() + "'\n";
tproxy_conf += " password: '" + prefs.getSocksPassword() + "'\n";
}
if (prefs.getRemoteDns()) {
tproxy_conf += "mapdns:\n" +
" address: " + prefs.getMappedDns() + "\n" +
" port: 53\n" +
" network: 240.0.0.0\n" +
" netmask: 240.0.0.0\n" +
" cache-size: 10000\n";
}
fos.write(tproxy_conf.getBytes());
fos.close();
} catch (IOException e) {
return;
}
TProxyStartService(tproxy_file.getAbsolutePath(), tunFd.getFd());
prefs.setEnable(true);
String channelName = getString(R.string.tproxy_channel_name);
initNotificationChannel(channelName);
createNotification(channelName);
// Start loop and locks if enabled
syncWatchdogLocks();
}
public void stopService() {
if (tunFd == null)
return;
stopWatchdogLoop();
releaseLocks();
stopForeground(true);
/* TProxy */
TProxyStopService();
/* VPN */
try {
tunFd.close();
} catch (IOException e) {
}
tunFd = null;
System.exit(0);
}
private void createNotification(String channelName) {
Intent i = new Intent(this, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, channelName);
Notification notify = notification
.setContentTitle(getString(R.string.app_name))
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setContentIntent(pi)
.build();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(1, notify);
} else {
startForeground(1, notify, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
}
}
// create NotificationChannel
private void initNotificationChannel(String channelName) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.app_name);
NotificationChannel channel = new NotificationChannel(channelName, name, NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
}
}

View File

@ -1,41 +0,0 @@
/*
* ============================================================================
* Name : TermuxCallbackReceiver.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Termux callback helper
* ============================================================================
*/
package org.iiab.controller;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class TermuxCallbackReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (IIABWatchdog.ACTION_TERMUX_OUTPUT.equals(intent.getAction())) {
Bundle resultExtras = intent.getExtras();
if (resultExtras != null) {
int exitCode = resultExtras.getInt("exitCode", -1);
String stdout = resultExtras.getString("stdout", "");
String stderr = resultExtras.getString("stderr", "");
String logMsg;
if (exitCode == 0) {
logMsg = "[Termux] Stimulus OK (exit 0)";
} else {
logMsg = "[Termux] Pulse Error (exit " + exitCode + ")";
if (!stderr.isEmpty()) {
logMsg += ": " + stderr;
}
}
// Write to BlackBox log
IIABWatchdog.writeToBlackBox(context, logMsg);
}
}
}
}

View File

@ -1,495 +0,0 @@
/*
* ============================================================================
* Name : UsageFragment.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Usage Fragment Activity
* ============================================================================
*/
package org.iiab.controller;
import android.content.Context;
import android.content.Intent;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.text.method.ScrollingMovementMethod;
import android.view.MotionEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.android.material.snackbar.Snackbar;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class UsageFragment extends Fragment implements View.OnClickListener {
private MainActivity mainActivity;
// INTERFACE VARS
private EditText edittext_socks_addr, edittext_socks_udp_addr, edittext_socks_port, edittext_socks_user, edittext_socks_pass, edittext_dns_ipv4, edittext_dns_ipv6;
private CheckBox checkbox_udp_in_tcp, checkbox_remote_dns, checkbox_global, checkbox_maintenance, checkbox_ipv4, checkbox_ipv6;
private TextView textview_maintenance_warning, configLabel, advConfigLabel, logLabel, logWarning, logSizeText, connectionLog;
private Button button_apps, button_save, button_control, button_browse_content, watchdogControl, btnClearLog, btnCopyLog;
private LinearLayout logActions, configLayout, advancedConfig, deckContainer;
private ProgressBar logProgress;
private ProgressButton btnServerControl;
private ObjectAnimator fusionAnimator;
private DashboardManager dashboardManager;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof MainActivity) {
mainActivity = (MainActivity) context;
mainActivity.setUsageFragment(this);
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_usage, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// UI Bindings
edittext_socks_addr = view.findViewById(R.id.socks_addr);
edittext_socks_udp_addr = view.findViewById(R.id.socks_udp_addr);
edittext_socks_port = view.findViewById(R.id.socks_port);
edittext_socks_user = view.findViewById(R.id.socks_user);
edittext_socks_pass = view.findViewById(R.id.socks_pass);
edittext_dns_ipv4 = view.findViewById(R.id.dns_ipv4);
edittext_dns_ipv6 = view.findViewById(R.id.dns_ipv6);
checkbox_ipv4 = view.findViewById(R.id.ipv4);
checkbox_ipv6 = view.findViewById(R.id.ipv6);
checkbox_global = view.findViewById(R.id.global);
checkbox_udp_in_tcp = view.findViewById(R.id.udp_in_tcp);
checkbox_remote_dns = view.findViewById(R.id.remote_dns);
checkbox_maintenance = view.findViewById(R.id.checkbox_maintenance);
textview_maintenance_warning = view.findViewById(R.id.maintenance_warning);
button_apps = view.findViewById(R.id.apps);
button_save = view.findViewById(R.id.save);
button_control = view.findViewById(R.id.control);
button_browse_content = view.findViewById(R.id.btnBrowseContent);
watchdogControl = view.findViewById(R.id.watchdog_control);
logActions = view.findViewById(R.id.log_actions);
btnClearLog = view.findViewById(R.id.btn_clear_log);
btnCopyLog = view.findViewById(R.id.btn_copy_log);
connectionLog = view.findViewById(R.id.connection_log);
logProgress = view.findViewById(R.id.log_progress);
logWarning = view.findViewById(R.id.log_warning_text);
logSizeText = view.findViewById(R.id.log_size_text);
configLayout = view.findViewById(R.id.config_layout);
configLabel = view.findViewById(R.id.config_label);
advancedConfig = view.findViewById(R.id.advanced_config);
advConfigLabel = view.findViewById(R.id.adv_config_label);
logLabel = view.findViewById(R.id.log_label);
deckContainer = view.findViewById(R.id.deck_container);
btnServerControl = view.findViewById(R.id.btn_server_control);
dashboardManager = new DashboardManager(requireActivity(), view, () -> {
mainActivity.handleControlClick();
});
// Listeners
watchdogControl.setOnClickListener(v -> {
if (mainActivity.currentSystemState == DashboardFragment.SystemState.NONE) {
Snackbar.make(v, R.string.termux_not_installed_error, Snackbar.LENGTH_LONG).show();
return;
}
mainActivity.handleWatchdogClick();
});
button_control.setOnClickListener(v -> mainActivity.handleControlClick());
button_browse_content.setOnClickListener(v -> mainActivity.handleBrowseContentClick(v));
btnClearLog.setOnClickListener(this);
btnCopyLog.setOnClickListener(this);
configLabel.setOnClickListener(v -> handleConfigToggle());
advConfigLabel.setOnClickListener(v -> toggleVisibility(advancedConfig, advConfigLabel, getString(R.string.advanced_settings_label)));
logLabel.setOnClickListener(v -> handleLogToggle());
checkbox_udp_in_tcp.setOnClickListener(this);
checkbox_remote_dns.setOnClickListener(this);
checkbox_global.setOnClickListener(this);
checkbox_maintenance.setOnClickListener(this);
button_apps.setOnClickListener(this);
button_save.setOnClickListener(this);
btnServerControl.setOnClickListener(v -> {
// --- Intercept based on State Machine ---
DashboardFragment.SystemState state = mainActivity.currentSystemState;
boolean isFullyInstalled = (state == DashboardFragment.SystemState.ONLINE || state == DashboardFragment.SystemState.OFFLINE);
if (!isFullyInstalled) {
Snackbar.make(v, R.string.server_not_installed_warning, 6000).show();
return; // Stop execution here
}
// --------------------------------------------------
if (mainActivity.targetServerState != null) return;
mainActivity.serverTransitionText = !mainActivity.isServerAlive ? getString(R.string.server_booting) : getString(R.string.server_shutting_down);
mainActivity.targetServerState = !mainActivity.isServerAlive;
updateUIColorsAndVisibility();
btnServerControl.startProgress();
mainActivity.handleServerLaunchClick(v);
});
connectionLog.setMovementMethod(new ScrollingMovementMethod());
connectionLog.setTextIsSelectable(true);
connectionLog.setOnTouchListener((v, event) -> {
v.getParent().requestDisallowInterceptTouchEvent(true);
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
v.getParent().requestDisallowInterceptTouchEvent(false);
}
return false;
});
updateUI();
}
@Override
public void onClick(View v) {
if (v == checkbox_global || v == checkbox_remote_dns || v == checkbox_maintenance) {
mainActivity.savePrefs();
updateUI();
} else if (v == button_apps) {
startActivity(new Intent(requireContext(), AppListActivity.class));
} else if (v.getId() == R.id.save) {
mainActivity.savePrefs();
Toast.makeText(requireContext(), R.string.saved_toast, Toast.LENGTH_SHORT).show();
addToLog(getString(R.string.settings_saved));
} else if (v.getId() == R.id.btn_clear_log) {
showResetLogConfirmation();
} else if (v.getId() == R.id.btn_copy_log) {
ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("IIAB Log", connectionLog.getText().toString());
if (clipboard != null) {
clipboard.setPrimaryClip(clip);
Toast.makeText(requireContext(), R.string.log_copied_toast, Toast.LENGTH_SHORT).show();
}
}
}
public void updateUI() {
if (button_control == null) return;
boolean vpnActive = mainActivity.prefs.getEnable();
boolean watchdogActive = mainActivity.prefs.getWatchdogEnable();
if (dashboardManager != null) dashboardManager.setTunnelState(vpnActive, mainActivity.isProxyDegraded);
if (vpnActive) {
button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_on));
} else {
button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_off));
}
if (watchdogActive) {
watchdogControl.setText(R.string.watchdog_disable);
} else {
watchdogControl.setText(R.string.watchdog_enable);
}
edittext_socks_addr.setText(mainActivity.prefs.getSocksAddress());
edittext_socks_udp_addr.setText(mainActivity.prefs.getSocksUdpAddress());
edittext_socks_port.setText(String.valueOf(mainActivity.prefs.getSocksPort()));
edittext_socks_user.setText(mainActivity.prefs.getSocksUsername());
edittext_socks_pass.setText(mainActivity.prefs.getSocksPassword());
edittext_dns_ipv4.setText(mainActivity.prefs.getDnsIpv4());
edittext_dns_ipv6.setText(mainActivity.prefs.getDnsIpv6());
checkbox_ipv4.setChecked(mainActivity.prefs.getIpv4());
checkbox_ipv6.setChecked(mainActivity.prefs.getIpv6());
checkbox_global.setChecked(mainActivity.prefs.getGlobal());
checkbox_udp_in_tcp.setChecked(mainActivity.prefs.getUdpInTcp());
checkbox_remote_dns.setChecked(mainActivity.prefs.getRemoteDns());
checkbox_maintenance.setChecked(mainActivity.prefs.getMaintenanceMode());
boolean editable = !vpnActive;
edittext_socks_addr.setEnabled(editable);
edittext_socks_port.setEnabled(editable);
button_save.setEnabled(editable);
checkbox_maintenance.setEnabled(editable);
if (textview_maintenance_warning != null) {
textview_maintenance_warning.setVisibility(vpnActive ? View.VISIBLE : View.GONE);
}
}
public void updateUIColorsAndVisibility() {
if (!isAdded() || getContext() == null) {
return;
}
if (button_control == null) return;
boolean isVpnActive = mainActivity.prefs.getEnable();
boolean isWatchdogOn = mainActivity.prefs.getWatchdogEnable();
if (dashboardManager != null) {
dashboardManager.setTunnelState(isVpnActive, mainActivity.isProxyDegraded);
}
// Main VPN Button
if (!mainActivity.isServerAlive) {
if (isVpnActive) {
button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_on_dim));
} else {
button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_off_dim));
}
} else {
button_control.setEnabled(true);
if (isVpnActive) {
button_control.setText(R.string.control_disable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_on));
} else {
button_control.setText(R.string.control_enable);
button_control.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_vpn_off));
}
}
// Explore Button
button_browse_content.setVisibility(View.VISIBLE);
if (!mainActivity.isServerAlive) {
button_browse_content.setEnabled(true);
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_disabled));
button_browse_content.setAlpha(1.0f);
button_browse_content.setTextColor(Color.parseColor("#888888"));
} else if (mainActivity.isNegotiating) {
button_browse_content.setEnabled(true);
button_browse_content.setTextColor(Color.WHITE);
} else {
button_browse_content.setEnabled(true);
button_browse_content.setTextColor(Color.WHITE);
button_browse_content.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_ready));
if (isVpnActive && !mainActivity.isProxyDegraded) {
button_browse_content.setAlpha(1.0f);
} else {
button_browse_content.setAlpha(0.6f);
}
}
// Server Control Logic
DashboardFragment.SystemState state = mainActivity.currentSystemState;
boolean isFullyInstalled = (state == DashboardFragment.SystemState.ONLINE || state == DashboardFragment.SystemState.OFFLINE);
if (!isFullyInstalled) {
// SYSTEM NOT READY: Gray out the button
btnServerControl.setAlpha(0.6f);
btnServerControl.setText(R.string.launch_server);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_disabled));
// Also gray out the watchdog since the server isn't installed
deckContainer.setBackgroundColor(Color.TRANSPARENT);
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_watchdog_off));
} else if (mainActivity.targetServerState != null) {
// TRANSITIONING STATE
btnServerControl.setAlpha(0.6f);
btnServerControl.setText(mainActivity.serverTransitionText);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_explore_disabled));
} else {
// SYSTEM READY: Normal behavior
btnServerControl.setAlpha(1.0f);
if (mainActivity.isServerAlive) {
btnServerControl.setText(R.string.stop_server);
if (isWatchdogOn) {
deckContainer.setBackgroundColor(Color.parseColor("#44FF9800"));
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_watchdog_on));
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_watchdog_on));
} else {
if (fusionAnimator == null || !fusionAnimator.isRunning()) deckContainer.setBackgroundColor(Color.TRANSPARENT);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_danger));
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_watchdog_off));
}
} else {
deckContainer.setBackgroundColor(Color.TRANSPARENT);
btnServerControl.setText(R.string.launch_server);
btnServerControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), R.color.btn_success));
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(requireContext(), isWatchdogOn ? R.color.btn_watchdog_on : R.color.btn_watchdog_off));
}
}
}
public void stopBtnProgress() {
btnServerControl.stopProgress();
}
public void startFusionPulse() {
deckContainer.setBackgroundColor(Color.parseColor("#44FF9800"));
if (fusionAnimator != null && fusionAnimator.isRunning()) fusionAnimator.cancel();
fusionAnimator = ObjectAnimator.ofFloat(deckContainer, "alpha", 1f, 0.4f);
fusionAnimator.setDuration(600);
fusionAnimator.setRepeatCount(ObjectAnimator.INFINITE);
fusionAnimator.setRepeatMode(ObjectAnimator.REVERSE);
fusionAnimator.start();
}
public void startExitPulse() {
if (fusionAnimator != null && fusionAnimator.isRunning()) fusionAnimator.cancel();
fusionAnimator = ObjectAnimator.ofFloat(deckContainer, "alpha", deckContainer.getAlpha(), 0.3f);
fusionAnimator.setDuration(800);
fusionAnimator.setRepeatCount(ObjectAnimator.INFINITE);
fusionAnimator.setRepeatMode(ObjectAnimator.REVERSE);
fusionAnimator.start();
}
public void finalizeEntryPulse() {
if (fusionAnimator != null) fusionAnimator.cancel();
deckContainer.setAlpha(1f);
}
public void finalizeExitPulse() {
if (fusionAnimator != null) fusionAnimator.cancel();
deckContainer.animate().alpha(1f).setDuration(300).withEndAction(() -> deckContainer.setBackgroundColor(Color.TRANSPARENT)).start();
}
public void addToLog(String message) {
requireActivity().runOnUiThread(() -> {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
String currentTime = sdf.format(new Date());
String logEntry = "[" + currentTime + "] " + message + "\n";
if (connectionLog != null) {
connectionLog.append(logEntry);
scrollToBottom();
}
});
}
private void scrollToBottom() {
if (connectionLog != null && connectionLog.getLayout() != null) {
int scroll = connectionLog.getLayout().getLineTop(connectionLog.getLineCount()) - connectionLog.getHeight();
if (scroll > 0) connectionLog.scrollTo(0, scroll);
}
}
public void updateLogSizeUI() {
if (logSizeText == null) return;
String sizeStr = LogManager.getFormattedSize(requireContext());
logSizeText.setText(getString(R.string.log_size_format, sizeStr));
}
public void updateConnectivityLeds(boolean wifiOn, boolean hotspotOn) {
if (dashboardManager != null) {
dashboardManager.updateConnectivityLeds(wifiOn, hotspotOn);
}
}
public boolean isLogVisible() {
return connectionLog != null && connectionLog.getVisibility() == View.VISIBLE;
}
private void handleLogToggle() {
boolean isOpening = connectionLog.getVisibility() == View.GONE;
if (isOpening) {
if (mainActivity.isReadingLogs) return;
mainActivity.isReadingLogs = true;
if (logProgress != null) logProgress.setVisibility(View.VISIBLE);
LogManager.readLogsAsync(requireContext(), (logContent, isRapidGrowth) -> {
if (connectionLog != null) {
connectionLog.setText(logContent);
scrollToBottom();
}
if (logProgress != null) logProgress.setVisibility(View.GONE);
if (logWarning != null) logWarning.setVisibility(isRapidGrowth ? View.VISIBLE : View.GONE);
updateLogSizeUI();
mainActivity.isReadingLogs = false;
});
mainActivity.startLogSizeUpdates();
} else {
mainActivity.stopLogSizeUpdates();
}
toggleVisibility(connectionLog, logLabel, getString(R.string.connection_log_label));
logActions.setVisibility(connectionLog.getVisibility());
if (logSizeText != null) logSizeText.setVisibility(connectionLog.getVisibility());
}
private void handleConfigToggle() {
if (configLayout.getVisibility() == View.GONE) {
if (BiometricHelper.isDeviceSecure(requireContext())) {
BiometricHelper.prompt((androidx.appcompat.app.AppCompatActivity) requireActivity(),
getString(R.string.auth_required_title),
getString(R.string.auth_required_subtitle),
() -> toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label)));
} else {
BiometricHelper.showEnrollmentDialog(requireContext());
}
} else {
toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label));
}
}
private void toggleVisibility(View view, TextView label, String text) {
boolean isGone = view.getVisibility() == View.GONE;
view.setVisibility(isGone ? View.VISIBLE : View.GONE);
label.setText(String.format(getString(isGone ? R.string.label_separator_down : R.string.label_separator_up), text));
}
private void showResetLogConfirmation() {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.log_reset_confirm_title)
.setMessage(R.string.log_reset_confirm_msg)
.setPositiveButton(R.string.reset_log, (dialog, which) -> {
LogManager.clearLogs(requireContext(), new LogManager.LogClearCallback() {
@Override
public void onSuccess() {
connectionLog.setText("");
addToLog(getString(R.string.log_reset_user));
if (logWarning != null) logWarning.setVisibility(View.GONE);
updateLogSizeUI();
Toast.makeText(requireContext(), R.string.log_cleared_toast, Toast.LENGTH_SHORT).show();
}
@Override
public void onError(String message) {
Toast.makeText(requireContext(), getString(R.string.failed_reset_log, message), Toast.LENGTH_SHORT).show();
}
});
})
.setNegativeButton(R.string.cancel, null).show();
}
public void savePrefsFromUI() {
mainActivity.prefs.setSocksAddress("127.0.0.1");
mainActivity.prefs.setSocksPort(1080);
mainActivity.prefs.setSocksUdpAddress("");
mainActivity.prefs.setSocksUsername("");
mainActivity.prefs.setSocksPassword("");
mainActivity.prefs.setIpv4(true);
mainActivity.prefs.setIpv6(true);
mainActivity.prefs.setUdpInTcp(false);
mainActivity.prefs.setRemoteDns(true);
mainActivity.prefs.setGlobal(true);
mainActivity.prefs.setDnsIpv4(edittext_dns_ipv4.getText().toString());
mainActivity.prefs.setDnsIpv6(edittext_dns_ipv6.getText().toString());
mainActivity.prefs.setMaintenanceMode(checkbox_maintenance.isChecked());
}
}

View File

@ -1,76 +0,0 @@
/*
* ============================================================================
* Name : VpnRecoveryReceiver
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Button Tunnel helper
* ============================================================================
*/
package org.iiab.controller;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import androidx.core.app.NotificationCompat;
public class VpnRecoveryReceiver extends BroadcastReceiver {
private static final String TAG = "IIAB-VpnRecovery";
public static final String EXTRA_RECOVERY = "recovery_mode";
private static final String CHANNEL_ID = "recovery_channel";
private static final int NOTIFICATION_ID = 911;
@Override
public void onReceive(Context context, Intent intent) {
if ("org.iiab.controller.RECOVER_VPN".equals(intent.getAction())) {
Log.i(TAG, "Boomerang Signal Received! Triggering high-priority recovery...");
Preferences prefs = new Preferences(context);
if (prefs.getEnable()) {
showRecoveryNotification(context);
}
}
}
private void showRecoveryNotification(Context context) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, context.getString(R.string.recovery_channel_name),
NotificationManager.IMPORTANCE_HIGH
);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
if (manager != null) manager.createNotificationChannel(channel);
}
Intent uiIntent = new Intent(context, MainActivity.class);
uiIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
uiIntent.putExtra(EXTRA_RECOVERY, true);
PendingIntent pendingIntent = PendingIntent.getActivity(
context, 0, uiIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setContentTitle(context.getString(R.string.recovery_notif_title))
.setContentText(context.getString(R.string.recovery_notif_text))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setAutoCancel(true)
.setOngoing(true)
.setFullScreenIntent(pendingIntent, true) // High priority request to open
.setContentIntent(pendingIntent);
if (manager != null) {
manager.notify(NOTIFICATION_ID, builder.build());
}
}
}

View File

@ -1,154 +0,0 @@
/*
* ============================================================================
* Name : WatchdogService.java
* Author : IIAB Project
* Copyright : Copyright (c) 2026 IIAB Project
* Description : Watchdog service helper
* ============================================================================
*/
package org.iiab.controller;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import androidx.core.app.NotificationCompat;
public class WatchdogService extends Service {
private static final String CHANNEL_ID = "watchdog_channel";
private static final int NOTIFICATION_ID = 2;
public static final String ACTION_START = "org.iiab.controller.WATCHDOG_START";
public static final String ACTION_STOP = "org.iiab.controller.WATCHDOG_STOP";
public static final String ACTION_HEARTBEAT = "org.iiab.controller.HEARTBEAT";
public static final String ACTION_STATE_STARTED = "org.iiab.controller.WATCHDOG_STARTED";
public static final String ACTION_STATE_STOPPED = "org.iiab.controller.WATCHDOG_STOPPED";
private static final int HEARTBEAT_INTERVAL_MS = 20 * 1000;
@Override
public void onCreate() {
super.onCreate();
createNotificationChannel();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
String action = intent.getAction();
if (ACTION_START.equals(action)) {
startWatchdog();
} else if (ACTION_HEARTBEAT.equals(action)) {
IIABWatchdog.performHeartbeat(this);
scheduleHeartbeat();
}
}
return START_STICKY;
}
private void startWatchdog() {
Notification notification = createNotification();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification);
} else {
startForeground(NOTIFICATION_ID, notification);
}
IIABWatchdog.logSessionStart(this);
scheduleHeartbeat();
Intent startIntent = new Intent(ACTION_STATE_STARTED);
startIntent.setPackage(getPackageName());
sendBroadcast(startIntent);
}
@Override
public void onDestroy() {
// We immediately notify the UI that we are shutting down.
Intent stopIntent = new Intent(ACTION_STATE_STOPPED);
stopIntent.setPackage(getPackageName());
sendBroadcast(stopIntent);
// We clean up the trash
cancelHeartbeat();
IIABWatchdog.logSessionStop(this);
stopForeground(true);
super.onDestroy();
}
private PendingIntent getHeartbeatPendingIntent() {
Intent intent = new Intent(this, WatchdogService.class);
intent.setAction(ACTION_HEARTBEAT);
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
flags |= PendingIntent.FLAG_IMMUTABLE;
}
return PendingIntent.getService(this, 0, intent, flags);
}
@android.annotation.SuppressLint("ScheduleExactAlarm")
private void scheduleHeartbeat() {
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = getHeartbeatPendingIntent();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// This wakes up the device even in Doze Mode
alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + HEARTBEAT_INTERVAL_MS,
pendingIntent);
} else {
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + HEARTBEAT_INTERVAL_MS,
pendingIntent);
}
}
private void cancelHeartbeat() {
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = getHeartbeatPendingIntent();
if (alarmManager != null) {
alarmManager.cancel(pendingIntent);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, getString(R.string.watchdog_channel_name),
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription(getString(R.string.watchdog_channel_desc));
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.createNotificationChannel(channel);
}
}
}
private Notification createNotification() {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE);
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.watchdog_notif_title))
.setContentText(getString(R.string.watchdog_notif_text))
.setSmallIcon(android.R.drawable.ic_lock_idle_lock)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.build();
}
}

View File

@ -1,16 +0,0 @@
# Copyright (C) 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
include $(call all-subdir-makefiles)

View File

@ -1,21 +0,0 @@
# Copyright (C) 2023 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
APP_OPTIM := release
APP_PLATFORM := android-29
APP_ABI := armeabi-v7a arm64-v8a
APP_CFLAGS := -O3 -DPKGNAME=hev/sockstun
APP_CPPFLAGS := -O3 -std=c++11
NDK_TOOLCHAIN_VERSION := clang

@ -1 +0,0 @@
Subproject commit 4d6c334dbfb68a79d1970c2744e62d09f71df12f

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/overshoot_interpolator"
android:fillAfter="true">
<translate
android:startOffset="0"
android:fromXDelta="0%p"
android:toXDelta="5%p"
android:duration="50" />
<translate
android:startOffset="50"
android:fromXDelta="5%p"
android:toXDelta="-5%p"
android:duration="50" />
<translate
android:startOffset="100"
android:fromXDelta="-5%p"
android:toXDelta="5%p"
android:duration="50" />
<translate
android:startOffset="150"
android:fromXDelta="5%p"
android:toXDelta="-5%p"
android:duration="50" />
<translate
android:startOffset="200"
android:fromXDelta="-5%p"
android:toXDelta="0%p"
android:duration="50" />
</set>

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M40,680L40,520L368,520L120,324L120,440L40,440L40,200L80,200L520,442L520,160L720,160L920,400L920,680L820,680Q820,730 785,765Q750,800 700,800Q650,800 615,765Q580,730 580,680L360,680Q360,730 325,765Q290,800 240,800Q190,800 155,765Q120,730 120,680L40,680ZM283,723Q300,706 300,680Q300,654 283,637Q266,620 240,620Q214,620 197,637Q180,654 180,680Q180,706 197,723Q214,740 240,740Q266,740 283,723ZM743,723Q760,706 760,680Q760,654 743,637Q726,620 700,620Q674,620 657,637Q640,654 640,680Q640,706 657,723Q674,740 700,740Q726,740 743,723ZM600,400L816,400L682,240L600,240L600,400Z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM5,15L3,15v4c0,1.1 0.9,2 2,2h4v-2L5,19v-4zM5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2v4h2L5,5zM19,3h-4v2h4v4h2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4z"/>
</vector>

View File

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -1,30 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="135.47"
android:viewportHeight="135.47">
<path
android:pathData="M0.23,0.23h135v135h-135z"
android:strokeWidth="0.462333"
android:fillColor="#000000"
android:strokeColor="#000000"/>
<path
android:pathData="m39.63,90.18h1.56q-0.18,1.68 -1.27,2.8 -1.09,1.12 -3.22,1.12 -2.06,0 -3.33,-1.47 -1.26,-1.48 -1.28,-3.93v-1.27q0,-2.49 1.28,-3.99 1.28,-1.5 3.48,-1.5 2.01,0 3.09,1.11 1.08,1.11 1.26,2.85h-1.56q-0.18,-1.24 -0.8,-1.95 -0.62,-0.72 -1.99,-0.72 -1.57,0 -2.38,1.15 -0.81,1.15 -0.81,3.04v1.2q0,1.75 0.74,2.98 0.74,1.23 2.32,1.23 1.5,0 2.11,-0.7 0.62,-0.7 0.82,-1.95zM42.69,89.45q0,-1.91 1.07,-3.19 1.07,-1.28 2.92,-1.28 1.85,0 2.92,1.26 1.07,1.25 1.1,3.13v0.27q0,1.91 -1.08,3.19 -1.07,1.28 -2.92,1.28 -1.85,0 -2.93,-1.28 -1.07,-1.28 -1.07,-3.19zM44.2,89.64q0,1.31 0.62,2.27 0.63,0.96 1.89,0.96 1.23,0 1.85,-0.94 0.63,-0.95 0.63,-2.26v-0.21q0,-1.29 -0.63,-2.26 -0.63,-0.98 -1.88,-0.98 -1.24,0 -1.87,0.98 -0.62,0.97 -0.62,2.26zM56.24,86.25q-0.72,0 -1.28,0.39 -0.55,0.39 -0.87,1.02v6.28h-1.5v-8.8h1.42l0.05,1.1q1,-1.26 2.63,-1.26 1.29,0 2.05,0.72 0.76,0.72 0.77,2.43v5.8h-1.51v-5.78q0,-1.03 -0.46,-1.47 -0.45,-0.44 -1.3,-0.44zM65.51,93.94q-0.51,0.16 -1.16,0.16 -0.84,0 -1.43,-0.51 -0.59,-0.51 -0.59,-1.84v-5.45h-1.61v-1.15h1.61L62.32,83.01h1.5v2.14h1.64v1.15h-1.64v5.46q0,0.67 0.29,0.86 0.29,0.19 0.67,0.19 0.28,0 0.71,-0.1zM70.74,86.43q-1.47,0 -2.01,1.27v6.24h-1.5v-8.8h1.46l0.03,1.01q0.72,-1.17 2.09,-1.17 0.42,0 0.67,0.11l-0.01,1.4q-0.33,-0.07 -0.73,-0.07zM72.31,89.45q0,-1.91 1.07,-3.19 1.07,-1.28 2.92,-1.28 1.85,0 2.92,1.26 1.07,1.25 1.1,3.13v0.27q0,1.91 -1.08,3.19 -1.07,1.28 -2.92,1.28 -1.85,0 -2.93,-1.28 -1.07,-1.28 -1.07,-3.19zM73.81,89.64q0,1.31 0.62,2.27 0.63,0.96 1.89,0.96 1.23,0 1.85,-0.94 0.63,-0.95 0.63,-2.26v-0.21q0,-1.29 -0.63,-2.26 -0.63,-0.98 -1.88,-0.98 -1.24,0 -1.87,0.98 -0.62,0.97 -0.62,2.26zM83.84,81.45v12.49h-1.51v-12.49zM87.89,81.45v12.49h-1.51v-12.49zM97.26,92.4q-0.43,0.65 -1.22,1.18 -0.79,0.52 -2.09,0.52 -1.84,0 -2.94,-1.2 -1.1,-1.2 -1.1,-3.06v-0.34q0,-1.44 0.54,-2.45 0.55,-1.02 1.43,-1.54 0.88,-0.54 1.87,-0.54 1.89,0 2.75,1.24 0.87,1.23 0.87,3.07v0.67h-5.95q0.03,1.21 0.72,2.06 0.69,0.85 1.89,0.85 0.8,0 1.35,-0.33 0.55,-0.33 0.97,-0.87zM93.75,86.22q-0.89,0 -1.51,0.65 -0.62,0.65 -0.77,1.87h4.4v-0.11q-0.06,-0.88 -0.51,-1.64 -0.45,-0.76 -1.6,-0.76zM102.64,86.43q-1.47,0 -2.01,1.27v6.24h-1.5v-8.8h1.46l0.03,1.01q0.72,-1.17 2.09,-1.17 0.42,0 0.67,0.11l-0.01,1.4q-0.33,-0.07 -0.73,-0.07z"
android:strokeWidth="0.491597"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"/>
<path
android:pathData="M29.74,50.22L29.74,68.31L26.02,68.31L26.02,50.22ZM37.17,50.22L37.17,68.31L33.44,68.31L33.44,50.22ZM39.34,68.31 L46.08,50.22h3.45l6.77,18.08h-3.97l-1.24,-3.73h-6.55l-1.24,3.73zM45.54,61.56h4.53l-2.26,-6.79zM71.43,62.98q0,2.61 -1.66,3.96 -1.66,1.34 -4.74,1.37L57.99,68.31L57.99,50.22h6.35q3.14,0 4.92,1.2 1.78,1.2 1.78,3.74 0,1.23 -0.62,2.26 -0.62,1.03 -1.96,1.56 1.59,0.4 2.29,1.54 0.7,1.14 0.7,2.45zM61.72,53.24v4.55h2.61q2.96,0 2.96,-2.24 0,-2.26 -2.83,-2.31zM67.7,62.94q0,-2.43 -2.46,-2.52h-3.51v4.88h3.18q1.43,0 2.11,-0.67 0.68,-0.67 0.68,-1.69z"
android:strokeWidth="0.751077"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"/>
<group>
<clip-path
android:pathData="M71.83,38.37L111.31,38.37L111.31,78.26L71.83,78.26ZM99.85,58.32a8.21,8.28 90,0 0,-8.28 -8.21,8.21 8.28,90 0,0 -8.28,8.21 8.21,8.28 90,0 0,8.28 8.21,8.21 8.28,90 0,0 8.28,-8.21z"/>
<path
android:strokeWidth="1"
android:pathData="m91.57,39.86a18.46,19.44 90,0 0,-3.37 0.29L86.98,45.84A13.23,13.93 90,0 0,82.48 48.31L76.69,46.46A18.46,19.44 90,0 0,73.31 52.01l4.58,3.84a13.23,13.93 90,0 0,-0.25 2.47,13.23 13.93,90 0,0 0.25,2.47l-4.58,3.84a18.46,19.44 90,0 0,3.37 5.55l5.79,-1.84a13.23,13.93 90,0 0,4.5 2.47l1.21,5.68a18.46,19.44 90,0 0,3.37 0.29,18.46 19.44,90 0,0 3.37,-0.29l1.21,-5.68a13.23,13.93 90,0 0,4.51 -2.47l5.78,1.84a18.46,19.44 90,0 0,3.37 -5.55l-4.57,-3.84a13.23,13.93 90,0 0,0.26 -2.47,13.23 13.93,90 0,0 -0.25,-2.48L109.83,52A18.46,19.44 90,0 0,106.46 46.46L100.67,48.3A13.23,13.93 90,0 0,96.15 45.82l-1.21,-5.68a18.46,19.44 90,0 0,-3.37 -0.28z"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
</group>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m620,676 l56,-56q6,-6 6,-14t-6,-14L540,455q4,-11 6,-22t2,-25q0,-57 -40.5,-97.5T410,270q-17,0 -34,4.5T343,287l94,94 -56,56 -94,-94q-8,16 -12.5,33t-4.5,34q0,57 40.5,97.5T408,548q13,0 24.5,-2t22.5,-6l137,136q6,6 14,6t14,-6ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93ZM480,480Z"
android:fillColor="#e3e3e3"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="m370,880 l-16,-128q-13,-5 -24.5,-12T307,725l-119,50L78,585l103,-78q-1,-7 -1,-13.5v-27q0,-6.5 1,-13.5L78,375l110,-190 119,50q11,-8 23,-15t24,-12l16,-128h220l16,128q13,5 24.5,12t22.5,15l119,-50 110,190 -103,78q1,7 1,13.5v27q0,6.5 -2,13.5l103,78 -110,190 -118,-50q-11,8 -23,15t-24,12L590,880L370,880ZM440,800h79l14,-106q31,-8 57.5,-23.5T639,633l99,41 39,-68 -86,-65q5,-14 7,-29.5t2,-31.5q0,-16 -2,-31.5t-7,-29.5l86,-65 -39,-68 -99,42q-22,-23 -48.5,-38.5T533,266l-13,-106h-79l-14,106q-31,8 -57.5,23.5T321,327l-99,-41 -39,68 86,64q-5,15 -7,30t-2,32q0,16 2,31t7,30l-86,65 39,68 99,-42q22,23 48.5,38.5T427,694l13,106ZM482,620q58,0 99,-41t41,-99q0,-58 -41,-99t-99,-41q-59,0 -99.5,41T342,480q0,58 40.5,99t99.5,41ZM480,480Z"
android:fillColor="#e3e3e3"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36 -0.98,1.37 -2.58,2.26 -4.4,2.26 -3.03,0 -5.5,-2.47 -5.5,-5.5 0,-1.82 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="@android:color/white"
android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM2,13h2c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L2,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM20,13h2c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-2c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM11,2v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1L13,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1zM11,20v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1zM5.99,4.58c-0.39,-0.39 -1.03,-0.39 -1.41,0s-0.39,1.03 0,1.41l1.06,1.06c0.39,0.39 1.03,0.39 1.41,0s0.39,-1.03 0,-1.41L5.99,4.58zM18.36,16.95c-0.39,-0.39 -1.03,-0.39 -1.41,0s-0.39,1.03 0,1.41l1.06,1.06c0.39,0.39 1.03,0.39 1.41,0s0.39,-1.03 0,-1.41l-1.06,-1.06zM19.42,5.99c0.39,-0.39 0.39,-1.03 0,-1.41s-1.03,-0.39 -1.41,0l-1.06,1.06c-0.39,0.39 -0.39,1.03 0,1.41s1.03,0.39 1.41,0l1.06,-1.06zM7.05,18.36c0.39,-0.39 0.39,-1.03 0,-1.41s-1.03,-0.39 -1.41,0l-1.06,1.06c-0.39,0.39 -0.39,1.03 0,1.41s1.03,0.39 1.41,0l1.06,-1.06z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20V4c4.42,0 8,3.58 8,8s-3.58,8 -8,8z" />
</vector>

View File

@ -1,7 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportHeight="24"
android:tint="?attr/colorPrimary">
<path android:fillColor="@android:color/white"
android:pathData="M12,2a3,3 0 0,0 -3,3v2L6.5,7a1.5,1.5 0 0,0 -1.5,1.5v1h14v-1A1.5,1.5 0 0,0 17.5,7L15,7V5A3,3 0 0,0 12,2M12,4a1,1 0 0,1 1,1v2h-2V5A1,1 0 0,1 12,4M4,11v9a2,2 0 0,0 2,2h12a2,2 0 0,0 2,-2v-9H4M11,13h2v7h-2v-7M7.5,13h1.5v5H7.5v-5M15,13h1.5v5H15v-5z"/>
</vector>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#333333" /> <stroke android:width="1dp" android:color="#222222" />
</shape>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#00FF00" /> <stroke android:width="2dp" android:color="#4400FF00" />
</shape>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#FF9800" />
<stroke android:width="2dp" android:color="#44FF9800" />
</shape>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="12dp" />
<solid android:color="#FFFFFF" /> <!-- Default color, replaced by backgroundTint -->
</shape>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="6dp" />
<solid android:color="@color/dash_bar_bg" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="6dp" />
<solid android:color="#FFFFFF" /> </shape>
</clip>
</item>
</layer-list>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#888888" />
<corners android:radius="5dp" />
</shape>

View File

@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/myWebView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/btnHandle"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="#AA000000"
android:text="⌃"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:padding="0dp"
android:stateListAnimator="@null" />
<LinearLayout
android:id="@+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#EEF2F2F2"
android:orientation="vertical"
android:visibility="invisible"
android:elevation="8dp">
<Button
android:id="@+id/btnHideNav"
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="?android:attr/selectableItemBackground"
android:text="⌄"
android:textSize="20sp"
android:textColor="#555555"
android:stateListAnimator="@null" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="8dp">
<Button android:id="@+id/btnBack" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="◀" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
<Button android:id="@+id/btnHome" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="🏠" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
<Button android:id="@+id/btnReload" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="↻" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
<Button android:id="@+id/btnExit" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="✖" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle" android:textColor="#D32F2F" />
<Button android:id="@+id/btnForward" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="▶" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>

View File

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#DD000000"
android:padding="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center">
<LinearLayout
android:id="@+id/qr_card_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/rounded_button"
android:backgroundTint="#1A1A1A"
android:elevation="8dp"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp">
<TextView
android:id="@+id/qr_network_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/btn_flip_qr"
android:text="Wi-Fi Network"
android:textColor="#FFFFFF"
android:textSize="22sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_flip_qr"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_popup_sync"
android:contentDescription="@string/qr_flip_network"
android:tint="#FFFFFF"
android:scaleType="fitCenter"
android:padding="8dp"
android:visibility="gone" />
</RelativeLayout>
<TextView
android:id="@+id/qr_ip_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="http://---"
android:textColor="#AAAAAA"
android:textSize="16sp"
android:layout_marginBottom="16dp" />
<ImageView
android:id="@+id/qr_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:background="#FFFFFF"
android:padding="16dp" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/btn_close_qr"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="24dp"
android:text="Close"
android:backgroundTint="@color/btn_danger"
android:textColor="#FFFFFF" />
</LinearLayout>

View File

@ -1,163 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground">
<Button
android:id="@+id/btn_setup_continue"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:text="@string/setup_continue"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_success"
android:textAllCaps="false"
android:enabled="false" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/btn_setup_continue">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_title"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"/>
<TextView
android:id="@+id/setup_welcome_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginBottom="24dp"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_notifications"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_perm_notifications"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:paddingVertical="12dp"
android:layout_marginBottom="8dp"
android:theme="@style/PurpleSwitchTheme"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_termux"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_perm_termux"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:paddingVertical="12dp"
android:layout_marginBottom="8dp"
android:theme="@style/PurpleSwitchTheme"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_storage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_perm_storage"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:paddingVertical="12dp"
android:layout_marginBottom="8dp"
android:theme="@style/PurpleSwitchTheme"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_vpn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_perm_vpn"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:paddingVertical="12dp"
android:layout_marginBottom="8dp"
android:theme="@style/PurpleSwitchTheme"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_perm_battery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_perm_battery"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:paddingVertical="12dp"
android:theme="@style/PurpleSwitchTheme"/>
<Button
android:id="@+id/btn_manage_all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_manage_all_permissions"
android:textAllCaps="false"
android:gravity="start|center_vertical"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
android:paddingVertical="12dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:minHeight="0dp"
android:background="?android:attr/selectableItemBackground"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_termux_custom_permissions"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginBottom="8dp"/>
<Button
android:id="@+id/btn_termux_overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_display_over_other_apps"
android:textAllCaps="false"
style="?android:attr/borderlessButtonStyle"
android:gravity="start|center_vertical"
android:textColor="@color/lightGray66" />
<Button
android:id="@+id/btn_termux_storage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_termux_storage_btn"
android:textAllCaps="false"
style="?android:attr/borderlessButtonStyle"
android:gravity="start|center_vertical"
android:textColor="@color/lightGray66" />
<Button
android:id="@+id/btn_manage_termux"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/setup_manage_termux_permissions"
android:textAllCaps="false"
style="?android:attr/borderlessButtonStyle"
android:gravity="start|center_vertical"
android:textColor="@color/lightGray66" />
</LinearLayout>
</ScrollView>
</RelativeLayout>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dp" >
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true" >
<ImageView
android:id="@+id/icon"
android:layout_width="100px"
android:layout_height="100px"
android:layout_marginLeft="5px"
android:layout_marginRight="20px"
android:layout_marginTop="5px" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="50px"
android:maxEms="14"
android:ellipsize="end"
android:singleLine="true" />
</LinearLayout>
<CheckBox
android:id="@+id/checked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:focusable="false"
android:clickable="false" />
</RelativeLayout>

View File

@ -1,299 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/dash_bg_main">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/dash_text_device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dash_device"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/dash_text_primary"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/dash_module_bg"
android:padding="16dp"
android:layout_marginBottom="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/dash_text_wifi_ip"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/dash_text_primary" />
<TextView
android:id="@+id/dash_text_hotspot_ip"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/dash_text_primary" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/dash_text_uptime"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/dash_text_primary" />
<TextView
android:id="@+id/dash_text_battery"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/dash_text_primary" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/dash_divider"
android:layout_marginBottom="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dash_main_storage"
android:textStyle="bold"
android:textColor="@color/dash_text_primary" />
<TextView
android:id="@+id/dash_text_storage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-- GB / -- GB"
android:textColor="@color/dash_text_secondary"
android:textSize="12sp"/>
</LinearLayout>
<ProgressBar
android:id="@+id/dash_progress_storage"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="12dp"
android:progressDrawable="@drawable/rounded_progress_bar"
android:progressTint="@color/dash_bar_storage"
android:progressBackgroundTint="@color/dash_bar_bg"
android:progress="0"
android:layout_marginBottom="16dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginEnd="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dash_ram_memory"
android:textColor="@color/dash_text_primary"
android:textStyle="bold"
android:maxLines="1" />
<TextView
android:id="@+id/dash_text_ram"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-- / --"
android:textColor="@color/dash_text_secondary"
android:textSize="12sp"
android:gravity="end"
android:layout_marginTop="2dp" />
<ProgressBar
android:id="@+id/dash_progress_ram"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="4dp"
android:progressDrawable="@drawable/rounded_progress_bar"
android:progressTint="@color/dash_bar_ram"
android:progressBackgroundTint="@color/dash_bar_bg"
android:progress="0"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dash_swap_virtual"
android:textColor="@color/dash_text_primary"
android:textStyle="bold"
android:maxLines="1" />
<TextView
android:id="@+id/dash_text_swap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="-- / --"
android:textColor="@color/dash_text_secondary"
android:textSize="12sp"
android:gravity="end"
android:layout_marginTop="2dp" />
<ProgressBar
android:id="@+id/dash_progress_swap"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="4dp"
android:progressDrawable="@drawable/rounded_progress_bar"
android:progressTint="@color/dash_bar_swap"
android:progressBackgroundTint="@color/dash_bar_bg"
android:progress="0"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dash_iiab_system"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/dash_text_primary"
android:layout_marginTop="24dp"
android:layout_marginBottom="8dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/dash_module_bg"
android:padding="16dp"
android:layout_marginBottom="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dash_server_status"
android:textColor="@color/dash_text_primary" />
<TextView
android:id="@+id/dash_badge_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dash_offline"
android:background="@drawable/rounded_button"
android:backgroundTint="#555555"
android:textColor="@color/dash_text_primary"
android:textSize="12sp"
android:textStyle="bold"
android:paddingHorizontal="8dp"
android:paddingVertical="4dp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/dash_divider"
android:layout_marginBottom="12dp"/>
<LinearLayout
android:id="@+id/dash_card_termux_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<View
android:id="@+id/led_termux_state"
android:layout_width="12dp"
android:layout_height="12dp"
android:background="@drawable/led_off"
android:layout_marginEnd="12dp"/>
<TextView
android:id="@+id/text_termux_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dash_termux_searching"
android:textColor="@color/dash_text_primary" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/dash_modules_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dash_installed_modules"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginTop="24dp"
android:layout_marginBottom="8dp"
android:paddingVertical="8dp"
android:paddingHorizontal="0dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"/>
<LinearLayout
android:id="@+id/modules_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="24dp" />
</LinearLayout>
</ScrollView>

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="24dp">
<ImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_auto_towing"
app:tint="?android:attr/textColorPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/deploy_wip_title"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginTop="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/deploy_wip_desc"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginTop="8dp"/>
</LinearLayout>

View File

@ -1,411 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<LinearLayout
android:id="@+id/dashboard_container"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal"
android:layout_marginBottom="16dp"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/dash_module_bg" android:paddingHorizontal="8dp"
android:gravity="center_vertical"
android:baselineAligned="false">
<LinearLayout
android:id="@+id/dash_wifi"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp">
<View
android:layout_width="10dp"
android:id="@+id/led_wifi"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_off" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/wifi"
android:textColor="@color/dash_text_primary" android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/dash_hotspot"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp">
<View
android:id="@+id/led_hotspot"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_off" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hotspot"
android:textColor="@color/dash_text_primary" android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/dash_tunnel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp"
android:visibility="gone">
<View
android:id="@+id/led_tunnel"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_on_green" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tunnel"
android:textColor="@color/dash_text_primary" android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/control"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_marginHorizontal="12dp"
android:text="@string/control_enable"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_vpn_off"
android:textAllCaps="false"/>
<TextView
android:id="@+id/control_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:text="@string/vpn_description"
android:textSize="13sp"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"/>
<Button
android:id="@+id/btnBrowseContent"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_marginHorizontal="12dp" android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"
android:text="@string/browse_content"
android:textSize="21sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_explore_disabled"
android:textAllCaps="false"
android:elevation="4dp"
android:enabled="false" />
<TextView
android:id="@+id/config_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_settings_label"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginTop="24dp"
android:layout_marginBottom="8dp"
android:paddingVertical="8dp"
android:paddingHorizontal="0dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true" />
<LinearLayout
android:id="@+id/config_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:padding="16dp"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/dash_bg_card">
<Button
android:id="@+id/apps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/apps"
android:layout_marginBottom="12dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#673AB7"
android:textColor="#FFFFFF"
android:textAllCaps="false"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/dns_ipv4" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/dns_ipv4" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/dns_ipv6" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/dns_ipv6" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<CheckBox
android:id="@+id/checkbox_maintenance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="@string/maintenance_mode"
android:checked="true" />
<TextView
android:id="@+id/maintenance_warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/maintenance_warning_msg"
android:textSize="11sp"
android:textStyle="italic"
android:textColor="#FF9800"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<Button android:id="@+id/save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/save"
android:layout_marginTop="8dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#555555"
android:textColor="#FFFFFF"
android:textAllCaps="false"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp" android:visibility="gone"/>
<EditText android:id="@+id/socks_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary" android:visibility="gone"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_port" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp" android:visibility="gone"/>
<EditText android:id="@+id/socks_port" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="number" android:textColor="?android:attr/textColorPrimary" android:visibility="gone"/>
<TextView
android:id="@+id/adv_config_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_settings_label"
android:textColor="?android:attr/textColorSecondary"
android:padding="8dp"
android:textSize="13sp"
android:clickable="true"
android:focusable="true"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/advanced_config"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_udp_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_udp_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_user" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_user" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_pass" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_pass" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="textPassword" android:textColor="?android:attr/textColorPrimary"/>
<CheckBox android:id="@+id/udp_in_tcp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/udp_in_tcp" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/ipv4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv4" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/ipv6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv6" android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:visibility="gone">
<CheckBox android:id="@+id/remote_dns" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/remote_dns" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/global" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/global" android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
</LinearLayout>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginBottom="16dp"/>
<LinearLayout
android:id="@+id/deck_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="8dp"
android:background="#00000000"
android:padding="3dp"
android:orientation="horizontal"
android:baselineAligned="false"
android:weightSum="2">
<org.iiab.controller.ProgressButton
android:id="@+id/btn_server_control"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:text="Launch Server"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_success"
android:textAllCaps="false"
app:progressButtonHeight="6dp"
app:progressButtonDuration="@integer/server_cool_off_duration_ms"
app:progressButtonColor="#FF9800" />
<Button
android:id="@+id/watchdog_control"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:text="@string/watchdog_enable"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_watchdog_off"
android:textAllCaps="false"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/watchdog_description"
android:textSize="13sp"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginBottom="0dp"/>
<View android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider_color"
android:layout_marginTop="20dp"
android:layout_marginBottom="0dp"/>
<TextView
android:id="@+id/log_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/connection_log_label"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginTop="24dp" android:layout_marginBottom="8dp"
android:paddingVertical="8dp"
android:paddingHorizontal="0dp"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"/>
<TextView
android:id="@+id/log_warning_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_warning"
android:textSize="11sp"
android:textStyle="italic"
android:padding="4dp"
android:visibility="gone"
android:text="@string/log_warning_rapid_growth" />
<ProgressBar
android:id="@+id/log_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone" />
<TextView
android:id="@+id/connection_log"
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#000000"
android:textColor="#00FF00"
android:fontFamily="monospace"
android:padding="8dp"
android:visibility="gone"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:scrollbarSize="10dp"
android:scrollbarThumbVertical="@drawable/scrollbar_thumb"
android:text="@string/system_ready"
android:textSize="11sp"/>
<TextView
android:id="@+id/log_size_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="?android:attr/textColorSecondary"
android:textSize="10sp"
android:paddingEnd="8dp"
android:visibility="gone"
android:text="Size: 0KB / 10MB" />
<LinearLayout
android:id="@+id/log_actions"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginTop="4dp">
<Button
android:id="@+id/btn_clear_log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/reset_log"
android:textSize="12sp"
android:backgroundTint="@color/btn_danger"
android:textColor="#FFFFFF"
android:textAllCaps="false"
android:layout_marginEnd="4dp"/>
<Button
android:id="@+id/btn_copy_log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/copy_all"
android:textSize="12sp"
android:backgroundTint="@color/btn_success"
android:textColor="#FFFFFF"
android:textAllCaps="false"
android:layout_marginStart="4dp"/>
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="32dp"/>
</LinearLayout>
</ScrollView>

View File

@ -1,95 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="#1A1A1A"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
<ImageView
android:id="@+id/header_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@mipmap/ic_launcher" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="12dp"
android:text="@string/app_name"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_share_qr"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_center_focus_strong"
android:contentDescription="Share via QR"
android:padding="12dp"
android:scaleType="fitCenter"
android:tint="#FFFFFF" />
<ImageButton
android:id="@+id/btn_settings"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_menu_preferences"
android:contentDescription="Settings"
android:padding="12dp"
android:scaleType="fitCenter"
android:tint="#FFFFFF" />
<ImageButton
android:id="@+id/theme_toggle"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_theme_system"
android:contentDescription="Toggle Theme"
android:padding="12dp"
android:scaleType="fitCenter"
android:tint="#FFFFFF" />
</LinearLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill"
app:tabIndicatorColor="#FFFFFF"
app:tabIndicatorHeight="3dp"
app:tabSelectedTextColor="#FFFFFF"
app:tabTextColor="#888888"
app:tabTextAppearance="@style/CustomTabTextStyle"
android:background="#1A1A1A"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/version_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="v0.1.x"
android:textColor="@color/footer_text_color" android:textSize="11sp"
android:padding="8dp"
android:background="#1A1A1A" />
</LinearLayout>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

View File

@ -1,233 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="cancel">Cancelar</string>
<string name="copy_all">Copiar Todo</string>
<string name="fix_action">CORREGIR</string>
<string name="save">Guardar</string>
<string name="advanced_settings_label">Ajustes del Túnel</string>
<string name="configuration_label">Configuración</string>
<string name="connection_log_label">Log de Conexión</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="settings_label">AJUSTES</string>
<string name="tab_deploy">Instalación</string>
<string name="tab_status">Estado</string>
<string name="tab_usage">Uso</string>
<string name="setup_title">Configuración Inicial</string>
<string name="setup_welcome">Bienvenido al asistente de configuración de %1$s.\n\nPara funcionar correctamente, necesitamos los siguientes permisos:</string>
<string name="setup_continue">Continuar</string>
<string name="setup_perm_battery">Desactivar Optimización de Batería</string>
<string name="setup_perm_notifications">Notificaciones Push</string>
<string name="setup_perm_storage">Acceso al almacenamiento local</string>
<string name="setup_perm_termux">Ejecución de Termux</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_manage_all_permissions">Administrar todos los permisos</string>
<string name="setup_display_over_other_apps">Mostrar sobre otras aplicaciones</string>
<string name="setup_manage_termux_permissions">Administrar permisos de Termux</string>
<string name="setup_termux_storage_btn">Archivos y medios (Almacenamiento)</string>
<string name="termux_not_installed">Termux no está instalado.</string>
<string name="termux_not_installed_error">Termux no está instalado o el dispositivo no es compatible.</string>
<string name="notif_perm_denied">Permiso de notificaciones denegado</string>
<string name="notif_perm_granted">Permiso de notificaciones concedido</string>
<string name="revoke_permission_warning">Para revocar permisos, debe hacerlo desde los ajustes del sistema.</string>
<string name="termux_perm_denied">Permiso de Termux denegado</string>
<string name="termux_perm_granted">Permiso de Termux concedido</string>
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_device">Cargando el dispositivo...</string>
<string name="dash_ip">IP: %1$s</string>
<string name="dash_uptime">Activo: %1$s</string>
<string name="dash_battery_format"><b>Batería:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Batería:</b> --%%</string>
<string name="dash_hotspot_format"><b>Hotspot:</b> %1$s</string>
<string name="dash_uptime_format"><b>Activo:</b> %1$s</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_main_storage">Almacenamiento principal</string>
<string name="dash_ram_memory">Memoria RAM</string>
<string name="dash_swap_virtual">Swap (Virtual)</string>
<string name="dash_server_status">Estado del Servidor:</string>
<string name="dash_offline">Desconectado</string>
<string name="dash_online">En línea</string>
<string name="dash_system_state">Estado del Sistema</string>
<string name="dash_iiab_system">Sistema IIAB-oA</string>
<string name="dash_state_debian_only">OS base instalado. Proceda a instalar IIAB.</string>
<string name="dash_state_installer">Instalador encontrado, abra la pestaña de instalación para más información.</string>
<string name="dash_state_none">No se identificó ningún componente, ni siquiera Termux.</string>
<string name="dash_state_offline">IIAB-oA parece estar desconectado, intente lanzarlo.</string>
<string name="dash_state_online">IIAB-oA esta en línea.</string>
<string name="dash_state_termux_only">Termux encontrado, vaya a la pestaña de Instalación para operarlo.</string>
<string name="dash_termux_searching">Buscando instalación...</string>
<string name="dash_installed_modules">Módulos Instalados</string>
<string name="dash_books">Libros</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Mapas</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">Sistema</string>
<string name="apps">Aplicaciones</string>
<string name="browse_content">🚀 Explorar Contenido</string>
<string name="launch_server">🚀 Iniciar Servidor</string>
<string name="stop_server">🛑 Detener Servidor</string>
<string name="server_not_installed_warning">El sistema IIAB-oA no parece estar (completamente) instalado. Por favor, verifique la pestaña de Estado o Instalación para más información.</string>
<string name="server_booting">Iniciando...</string>
<string name="server_shutting_down">Apagando...</string>
<string name="server_timeout_warning">Advertencia: Tiempo de espera agotado en la transición de estado del servidor.</string>
<string name="system_ready">Sistema listo...\n</string>
<string name="hotspot">Hotspot</string>
<string name="tunnel">Túnel</string>
<string name="wifi">Wi-Fi</string>
<string name="qr_error_no_network">Active Wi-Fi o Hotspot para compartir contenido a través de la red.</string>
<string name="qr_error_no_server">Inicie el servidor para compartir contenido a través de la red.</string>
<string name="qr_flip_network">Cambiar Red</string>
<string name="qr_title_hotspot">Red Hotspot</string>
<string name="qr_title_wifi">Red Wi-Fi</string>
<string name="control_disable">Desactivar Safe Pocket Web</string>
<string name="control_enable">Activar Safe Pocket Web</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="global">Global</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="maintenance_mode">Modo Mantenimiento</string>
<string name="maintenance_warning_msg">Desactive Safe Pocket Web para poder modificar</string>
<string name="remote_dns">DNS Remoto</string>
<string name="socks_addr">Dirección Socks:</string>
<string name="socks_pass">Contraseña Socks:</string>
<string name="socks_port">Puerto Socks:</string>
<string name="socks_udp_addr">Dirección UDP Socks:</string>
<string name="socks_user">Usuario Socks:</string>
<string name="udp_in_tcp">Relé UDP sobre TCP</string>
<string name="recovery_channel_name">Recuperación VPN</string>
<string name="recovery_notif_text">Toque para restaurar el entorno seguro inmediatamente.</string>
<string name="recovery_notif_title">Safe Pocket Web Interrumpido</string>
<string name="tproxy_channel_name">socks5</string>
<string name="user_initiated_conn">Conexión iniciada por el usuario</string>
<string name="vpn_description">Habilite URLs amigables. Bloquee las amenazas.</string>
<string name="vpn_permission_granted">Permiso de VPN concedido. Conectando...</string>
<string name="vpn_starting">Iniciando VPN...</string>
<string name="vpn_stopping">Deteniendo VPN...</string>
<string name="watchdog_disable">Desactivar\nWatchdog Maestro</string>
<string name="watchdog_enable">Activar\nWatchdog Maestro</string>
<string name="watchdog_description">Protege Termux del Doze y mantiene el Wi-Fi activo.</string>
<string name="watchdog_channel_desc">Asegura que los servicios permanezcan activos cuando la pantalla está apagada.</string>
<string name="watchdog_channel_name">Servicio IIAB Watchdog</string>
<string name="watchdog_notif_text">Protegiendo el entorno Termux...</string>
<string name="watchdog_notif_title">IIAB Watchdog Activo</string>
<string name="cpu_wakelock_acquired">CPU WakeLock adquirido bajo protección VPN</string>
<string name="cpu_wakelock_released">CPU WakeLock liberado</string>
<string name="error_acquiring_locks">Error al adquirir bloqueos</string>
<string name="syncing_watchdog">Sincronizando estado del Watchdog. Activado: %b</string>
<string name="watchdog_started">Watchdog Iniciado</string>
<string name="watchdog_stopped">Watchdog Detenido</string>
<string name="watchdog_thread_ended">Watchdog Thread: Bucle finalizado</string>
<string name="watchdog_thread_error">Watchdog Thread: Error en el bucle</string>
<string name="watchdog_thread_interrupted">Watchdog Thread: Interrumpido, deteniéndose...</string>
<string name="watchdog_thread_started">Watchdog Thread: Bucle iniciado</string>
<string name="wifi_lock_acquired">Wi-Fi Lock adquirido bajo protección VPN</string>
<string name="wifi_lock_released">Wi-Fi Lock liberado</string>
<string name="critical_os_blocked">CRÍTICO: El SO bloqueó el estímulo a Termux (SecurityException).</string>
<string name="failed_termux_intent">CRÍTICO: Fallo en el Intent de Termux: %s</string>
<string name="force_termux_foreground">Forzar a Termux a pasar a primer plano...</string>
<string name="maintenance_mode_enabled">Modo de mantenimiento activado: Termux tiene acceso directo a Internet</string>
<string name="maintenance_write_failed">Fallo en la escritura de mantenimiento</string>
<string name="permission_denied_log">Permiso denegado: Asegúrese de que el manifiesto tiene RUN_COMMAND y la app no está restringida.</string>
<string name="ping_fail">PING 8085: FALLO (%s)</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="pulse_error_log">Error de Pulso: %s</string>
<string name="pulse_stimulating">Pulso: Estimulando Termux...</string>
<string name="recovery_pulse_received">Pulso de recuperación recibido del sistema. Forzando VPN...</string>
<string name="sent_to_termux">Enviado a Termux: %s</string>
<string name="session_started">SESIÓN DE LATIDO INICIADA</string>
<string name="session_stopped">SESIÓN DE LATIDO DETENIDA</string>
<string name="termux_invocation_error">Error al invocar Termux: %1$s</string>
<string name="termux_pulse_error">[Termux] Error de pulso (exit %1$d): %2$s</string>
<string name="termux_stimulus_ok">[Termux] Estímulo OK (exit 0)</string>
<string name="termux_stuck_warning">¿Termux no abre? Habilite Watchdog Maestro para forzar que obtenga el foco.</string>
<string name="unexpected_error_termux">Error inesperado enviando intent a Termux</string>
<string name="end_of_history">--- Fin del Historial ---</string>
<string name="error_reading_history">Error al leer el historial: %s</string>
<string name="failed_write_blackbox">Fallo al escribir en BlackBox</string>
<string name="loading_history">--- Cargando Historial ---</string>
<string name="log_cleared_toast">Log borrado</string>
<string name="log_copied_toast">Log copiado al portapapeles</string>
<string name="log_reset_confirm_msg">Esto borrará permanentemente todos los logs de conexión guardados. Esta acción no se puede deshacer.</string>
<string name="log_reset_confirm_title">¿Reiniciar historial de log?</string>
<string name="failed_reset_log">Fallo al reiniciar el log: %s</string>
<string name="log_reset_log">Log reiniciado</string>
<string name="log_reset_user">Log reiniciado por el usuario</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_format">Tamaño: %1$s / 10MB</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="log_warning_rapid_growth">El archivo de log está creciendo demasiado rápido, verifique si algo está fallando</string>
<string name="no_blackbox_found">--- No se encontró el archivo BlackBox ---</string>
<string name="auth_required_subtitle">Autentíquese para desactivar el entorno seguro</string>
<string name="auth_required_title">Autenticación requerida</string>
<string name="auth_success_disconnect">Autenticación exitosa. Desconectando...</string>
<string name="security_required_msg">Debe configurar un PIN, Patrón o Huella digital en su dispositivo antes de activar el entorno seguro.</string>
<string name="security_required_title">Seguridad Requerida</string>
<string name="unlock_watchdog_subtitle">Se requiere autenticación para detener la protección de Termux</string>
<string name="unlock_watchdog_title">Desbloquear Watchdog Maestro</string>
<string name="battery_opt_denied">Para que la app funcione al 100%, desactive la optimización de batería.</string>
<string name="battery_opt_msg">Para que el Watchdog funcione de manera confiable, desactive las optimizaciones de batería para esta aplicación.</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme detectado: Asegúrese de activar \'Permitir actividad en segundo plano\' en los ajustes de esta aplicación.</string>
<string name="battery_opt_title">Optimización de Batería</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi detectado: Establezca el ahorro de batería a \'Sin restricciones\' en los ajustes.</string>
<string name="go_to_settings">Ir a Ajustes</string>
<string name="app_started">Aplicación Iniciada</string>
<string name="deploy_wip_desc">El módulo Termux y el instalador de entorno estarán disponibles aquí pronto.</string>
<string name="deploy_wip_title">WIP - En construcción</string>
<string name="battery_custom">"Batería: "</string>
<string name="battery_no_value">Batería: --%</string>
<string name="hotspot_fdash">Hotspot: --</string>
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="saved_toast">Guardado</string>
<string name="settings_saved">Ajustes Guardados</string>
<string name="uptime_no_value">Tiempo de actividad: --</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="setup_termux_custom_permissions">Ajuste de permisos Termux</string>
</resources>

View File

@ -1,231 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="cancel">Annuler</string>
<string name="copy_all">Tout copier</string>
<string name="fix_action">CORRIGER</string>
<string name="save">Enregistrer</string>
<string name="advanced_settings_label">Paramètres du tunnel</string>
<string name="configuration_label">Configuration</string>
<string name="connection_log_label">Journal de connexion</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="settings_label">PARAMÈTRES</string>
<string name="tab_deploy">Installation</string>
<string name="tab_status">Statut</string>
<string name="tab_usage">Utilisation</string>
<string name="setup_title">Configuration initiale</string>
<string name="setup_welcome">Bienvenue dans l\'assistant de configuration de %1$s.\n\nPour fonctionner correctement, nous avons besoin des autorisations suivantes :</string>
<string name="setup_continue">Continuer</string>
<string name="setup_perm_battery">Désactiver l\'optimisation de la batterie</string>
<string name="setup_perm_notifications">Notifications Push</string>
<string name="setup_perm_storage">Accès au stockage local</string>
<string name="setup_perm_termux">Exécution de Termux</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_termux_storage_btn">Fichiers et médias (Stockage)</string>
<string name="termux_not_installed">Termux n\'est pas installé.</string>
<string name="termux_not_installed_error">Termux n\'est pas installé ou l\'appareil n\'est pas pris en charge.</string>
<string name="notif_perm_denied">Autorisation de notification refusée</string>
<string name="notif_perm_granted">Autorisation de notification accordée</string>
<string name="revoke_permission_warning">Pour révoquer des autorisations, vous devez le faire depuis les paramètres système.</string>
<string name="termux_perm_denied">Autorisation Termux refusée</string>
<string name="termux_perm_granted">Autorisation Termux accordée</string>
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_device">Chargement de l\'appareil...</string>
<string name="dash_ip">IP: %1$s</string>
<string name="dash_uptime">Temps de fonctionnement: %1$s</string>
<string name="dash_battery_format"><b>Batterie:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Batterie:</b> --%%</string>
<string name="dash_hotspot_format"><b>Point d\'accès:</b> %1$s</string>
<string name="dash_uptime_format"><b>Temps de fonctionnement:</b> %1$s</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_main_storage">Stockage principal</string>
<string name="dash_ram_memory">Mémoire RAM</string>
<string name="dash_swap_virtual">Swap (virtuel)</string>
<string name="dash_server_status">Statut du serveur:</string>
<string name="dash_offline">Hors ligne</string>
<string name="dash_online">En ligne</string>
<string name="dash_system_state">État du système</string>
<string name="dash_iiab_system">Système IIAB-oA</string>
<string name="dash_state_debian_only">OS de base installé. Procédez à l\'installation de IIAB.</string>
<string name="dash_state_installer">Installateur trouvé, ouvrez l\'onglet installation pour plus d\'informations.</string>
<string name="dash_state_none">Aucun composant identifié, même pas Termux.</string>
<string name="dash_state_offline">IIAB-oA semble hors ligne, essayez de le lancer.</string>
<string name="dash_state_online">IIAB-oA est en ligne.</string>
<string name="dash_state_termux_only">Termux trouvé, allez dans l\'onglet Installation pour le gérer.</string>
<string name="dash_termux_searching">Recherche d\'installation...</string>
<string name="dash_installed_modules">Modules installés</string>
<string name="dash_books">Livres</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Cartes</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">Système</string>
<string name="apps">Applications</string>
<string name="browse_content">🚀 Explorer le contenu</string>
<string name="launch_server">🚀 Lancer le serveur</string>
<string name="stop_server">🛑 Arrêter le serveur</string>
<string name="server_not_installed_warning">Le système IIAB-oA ne semble pas être (entièrement) installé. Veuillez vérifier l\'onglet Statut ou Installation pour plus d\'informations.</string>
<string name="server_booting">Démarrage...</string>
<string name="server_shutting_down">Arrêt en cours...</string>
<string name="server_timeout_warning">Avertissement: Le délai de transition de l\'état du serveur a expiré.</string>
<string name="system_ready">Système prêt...\n</string>
<string name="hotspot">Point d\'accès</string>
<string name="tunnel">Tunnel</string>
<string name="wifi">Wi-Fi</string>
<string name="qr_error_no_network">Activez le Wi-Fi ou le point d\'accès pour partager du contenu sur le réseau.</string>
<string name="qr_error_no_server">Lancez le serveur pour partager du contenu sur le réseau.</string>
<string name="qr_flip_network">Changer de réseau</string>
<string name="qr_title_hotspot">Réseau point d\'accès</string>
<string name="qr_title_wifi">Réseau Wi-Fi</string>
<string name="control_disable">Désactiver Safe Pocket Web</string>
<string name="control_enable">Activer Safe Pocket Web</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="global">Global</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="maintenance_mode">Mode de maintenance</string>
<string name="maintenance_warning_msg">Désactivez Safe Pocket Web pour pouvoir modifier</string>
<string name="remote_dns">DNS distant</string>
<string name="socks_addr">Adresse Socks:</string>
<string name="socks_pass">Mot de passe Socks:</string>
<string name="socks_port">Port Socks:</string>
<string name="socks_udp_addr">Adresse UDP Socks:</string>
<string name="socks_user">Nom d\'utilisateur Socks:</string>
<string name="udp_in_tcp">Relais UDP sur TCP</string>
<string name="recovery_channel_name">Récupération VPN</string>
<string name="recovery_notif_text">Appuyez pour restaurer immédiatement l\'environnement sécurisé.</string>
<string name="recovery_notif_title">Safe Pocket Web interrompu</string>
<string name="tproxy_channel_name">socks5</string>
<string name="user_initiated_conn">Connexion initiée par l\'utilisateur</string>
<string name="vpn_description">Activez des URL conviviales. Bloquez les menaces.</string>
<string name="vpn_permission_granted">Autorisation VPN accordée. Connexion...</string>
<string name="vpn_starting">Démarrage VPN...</string>
<string name="vpn_stopping">Arrêt VPN...</string>
<string name="watchdog_disable">Désactiver\nle Watchdog maître</string>
<string name="watchdog_enable">Activer\nle Watchdog maître</string>
<string name="watchdog_description">Protège Termux du mode Doze et maintient le Wi-Fi actif.</string>
<string name="watchdog_channel_desc">Garantit que les services restent actifs lorsque l\'écran est éteint.</string>
<string name="watchdog_channel_name">Service IIAB Watchdog</string>
<string name="watchdog_notif_text">Protection de l\'environnement Termux...</string>
<string name="watchdog_notif_title">IIAB Watchdog actif</string>
<string name="cpu_wakelock_acquired">CPU WakeLock acquis sous protection VPN</string>
<string name="cpu_wakelock_released">CPU WakeLock libéré</string>
<string name="error_acquiring_locks">Erreur lors de l\'acquisition des verrous</string>
<string name="syncing_watchdog">Synchronisation de l\'état du Watchdog. Activé: %b</string>
<string name="watchdog_started">Watchdog démarré</string>
<string name="watchdog_stopped">Watchdog arrêté</string>
<string name="watchdog_thread_ended">Watchdog Thread: Boucle terminée</string>
<string name="watchdog_thread_error">Watchdog Thread: Erreur dans la boucle</string>
<string name="watchdog_thread_interrupted">Watchdog Thread: Interrompu, arrêt en cours...</string>
<string name="watchdog_thread_started">Watchdog Thread: Boucle démarrée</string>
<string name="wifi_lock_acquired">Wi-Fi Lock acquis sous protection VPN</string>
<string name="wifi_lock_released">Wi-Fi Lock libéré</string>
<string name="critical_os_blocked">CRITIQUE: Le système d\'exploitation a bloqué la stimulation de Termux (SecurityException).</string>
<string name="failed_termux_intent">CRITIQUE: Échec de l\'intention Termux: %s</string>
<string name="force_termux_foreground">Forcer Termux au premier plan...</string>
<string name="maintenance_mode_enabled">Mode de maintenance activé: Termux a un accès direct à Internet</string>
<string name="maintenance_write_failed">Échec de l\'écriture de maintenance</string>
<string name="permission_denied_log">Autorisation refusée: Assurez-vous que le manifeste contient RUN_COMMAND et que l\'application n\'est pas restreinte.</string>
<string name="ping_fail">PING 8085: ÉCHEC (%s)</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="pulse_error_log">Erreur de Pulse: %s</string>
<string name="pulse_stimulating">Pulse: Stimulation de Termux...</string>
<string name="recovery_pulse_received">Pulse de récupération reçu du système. VPN en cours d\'application...</string>
<string name="sent_to_termux">Envoyé à Termux: %s</string>
<string name="session_started">SESSION DE HEARTBEAT DÉMARRÉE</string>
<string name="session_stopped">SESSION DE HEARTBEAT ARRÊTÉE</string>
<string name="termux_invocation_error">Erreur lors de l\'invocation de Termux: %1$s</string>
<string name="termux_pulse_error">[Termux] Erreur de Pulse (exit %1$d): %2$s</string>
<string name="termux_stimulus_ok">[Termux] Stimulus OK (exit 0)</string>
<string name="termux_stuck_warning">Termux ne s\'ouvre pas? Activez le Watchdog maître pour le forcer à prendre le focus.</string>
<string name="unexpected_error_termux">Erreur inattendue lors de l\'envoi de l\'intention vers Termux</string>
<string name="end_of_history">--- Fin de l\'historique ---</string>
<string name="error_reading_history">Erreur lors de la lecture de l\'historique: %s</string>
<string name="failed_write_blackbox">Échec de l\'écriture dans BlackBox</string>
<string name="loading_history">--- Chargement de l\'historique ---</string>
<string name="log_cleared_toast">Journal effacé</string>
<string name="log_copied_toast">Journal copié dans le presse-papier</string>
<string name="log_reset_confirm_msg">Cela supprimera définitivement tous les journaux de connexion enregistrés. Cette action ne peut pas être annulée.</string>
<string name="log_reset_confirm_title">Réinitialiser l\'historique des journaux?</string>
<string name="failed_reset_log">Échec de la réinitialisation du journal: %s</string>
<string name="log_reset_log">Journal réinitialisé</string>
<string name="log_reset_user">Journal réinitialisé par l\'utilisateur</string>
<string name="log_size_bytes">%d o</string>
<string name="log_size_format">Taille: %1$s / 10 Mo</string>
<string name="log_size_kb">%.1f Ko</string>
<string name="log_size_mb">%.2f Mo</string>
<string name="log_warning_rapid_growth">Le fichier de journalisation croît trop rapidement, vérifiez si quelque chose échoue</string>
<string name="no_blackbox_found">--- Aucun fichier BlackBox trouvé ---</string>
<string name="auth_required_subtitle">Authentifiez-vous pour désactiver l\'environnement sécurisé</string>
<string name="auth_required_title">Authentification requise</string>
<string name="auth_success_disconnect">Authentification réussie. Déconnexion...</string>
<string name="security_required_msg">Vous devez définir un code PIN, un schéma ou une empreinte digitale sur votre appareil avant d\'activer l\'environnement sécurisé.</string>
<string name="security_required_title">Sécurité requise</string>
<string name="unlock_watchdog_subtitle">Authentification requise pour arrêter la protection Termux</string>
<string name="unlock_watchdog_title">Déverrouiller le Watchdog maître</string>
<string name="battery_opt_denied">Pour que l\'application fonctionne à 100%, veuillez désactiver l\'optimisation de la batterie.</string>
<string name="battery_opt_msg">Pour que le Watchdog fonctionne de manière fiable, veuillez désactiver les optimisations de batterie pour cette application.</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme détecté: Veuillez vous assurer d\'activer \'Autoriser l\'activité en arrière-plan\' dans les paramètres de cette application.</string>
<string name="battery_opt_title">Optimisation de la batterie</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi détecté: Veuillez régler l\'économie de batterie sur \'Aucune restriction\' dans les paramètres.</string>
<string name="go_to_settings">Aller aux paramètres</string>
<string name="app_started">Application démarrée</string>
<string name="deploy_wip_desc">Le module Termux et l\'installateur d\'environnement seront disponibles ici prochainement.</string>
<string name="deploy_wip_title">WIP - En construction</string>
<string name="battery_custom">"Batterie: "</string>
<string name="battery_no_value">Batterie: --%</string>
<string name="hotspot_fdash">Point d\'accès: --</string>
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="saved_toast">Enregistré</string>
<string name="settings_saved">Paramètres enregistrés</string>
<string name="uptime_no_value">Temps de fonctionnement: --</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="setup_display_over_other_apps">Display over other apps</string>
<string name="setup_manage_all_permissions">Manage All Permissions</string>
<string name="setup_manage_termux_permissions">Manage Termux permissions</string>
<string name="setup_termux_custom_permissions">Termux custom permissions</string>
</resources>

View File

@ -1,231 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="cancel">रद्द करें</string>
<string name="copy_all">सभी कॉपी करें</string>
<string name="fix_action">ठीक करें</string>
<string name="save">सहेजें</string>
<string name="advanced_settings_label">टनल सेटिंग्स</string>
<string name="configuration_label">कॉन्फ़िगरेशन</string>
<string name="connection_log_label">कनेक्शन लॉग</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="settings_label">सेटिंग्स</string>
<string name="tab_deploy">इंस्टालेशन</string>
<string name="tab_status">स्थिति</string>
<string name="tab_usage">उपयोग</string>
<string name="setup_title">प्रारंभिक सेटअप</string>
<string name="setup_welcome">%1$s सेटअप विज़ार्ड में आपका स्वागत है।\n\nठीक से काम करने के लिए, हमें निम्नलिखित अनुमतियों की आवश्यकता है:</string>
<string name="setup_continue">जारी रखें</string>
<string name="setup_perm_battery">बैटरी अनुकूलन अक्षम करें</string>
<string name="setup_perm_notifications">पुश सूचनाएं</string>
<string name="setup_perm_storage">स्थानीय स्टोरेज एक्सेस</string>
<string name="setup_perm_termux">Termux निष्पादन</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_termux_storage_btn">फ़ाइलें और मीडिया (स्टोरेज)</string>
<string name="termux_not_installed">Termux इंस्टॉल नहीं है।</string>
<string name="termux_not_installed_error">Termux इंस्टॉल नहीं है या डिवाइस समर्थित नहीं है।</string>
<string name="notif_perm_denied">सूचना अनुमति अस्वीकार</string>
<string name="notif_perm_granted">सूचना अनुमति दी गई</string>
<string name="revoke_permission_warning">अनुमतियां रद्द करने के लिए, आपको सिस्टम सेटिंग्स में जाना होगा।</string>
<string name="termux_perm_denied">Termux अनुमति अस्वीकार</string>
<string name="termux_perm_granted">Termux अनुमति दी गई</string>
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_device">डिवाइस लोड हो रहा है...</string>
<string name="dash_ip">IP: %1$s</string>
<string name="dash_uptime">अपटाइम: %1$s</string>
<string name="dash_battery_format"><b>बैटरी:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>बैटरी:</b> --%%</string>
<string name="dash_hotspot_format"><b>हॉटस्पॉट:</b> %1$s</string>
<string name="dash_uptime_format"><b>अपटाइम:</b> %1$s</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_main_storage">मुख्य स्टोरेज</string>
<string name="dash_ram_memory">RAM मेमोरी</string>
<string name="dash_swap_virtual">स्वैप (वर्चुअल)</string>
<string name="dash_server_status">सर्वर स्थिति:</string>
<string name="dash_offline">ऑफ़लाइन</string>
<string name="dash_online">ऑनलाइन</string>
<string name="dash_system_state">सिस्टम स्थिति</string>
<string name="dash_iiab_system">सिस्टम IIAB-oA</string>
<string name="dash_state_debian_only">बेस OS इंस्टॉल है। IIAB इंस्टॉल करना जारी रखें।</string>
<string name="dash_state_installer">इंस्टॉलर मिला, अधिक जानकारी के लिए इंस्टालेशन टैब खोलें।</string>
<string name="dash_state_none">कोई घटक नहीं मिला, यहाँ तक कि Termux भी नहीं।</string>
<string name="dash_state_offline">IIAB-oA ऑफ़लाइन लग रहा है, इसे लॉन्च करने का प्रयास करें।</string>
<string name="dash_state_online">IIAB-oA ऑनलाइन है।</string>
<string name="dash_state_termux_only">Termux मिला, इसे प्रबंधित करने के लिए इंस्टालेशन टैब पर जाएं।</string>
<string name="dash_termux_searching">इंस्टालेशन खोज रहा है...</string>
<string name="dash_installed_modules">इंस्टॉल किए गए मॉड्यूल</string>
<string name="dash_books">पुस्तकें</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">मानचित्र</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">सिस्टम</string>
<string name="apps">ऐप्स</string>
<string name="browse_content">🚀 सामग्री देखें</string>
<string name="launch_server">🚀 सर्वर लॉन्च करें</string>
<string name="stop_server">🛑 सर्वर बंद करें</string>
<string name="server_not_installed_warning">IIAB-oA सिस्टम (पूरी तरह से) इंस्टॉल नहीं लग रहा है। कृपया अधिक जानकारी के लिए स्थिति या इंस्टालेशन टैब देखें।</string>
<string name="server_booting">बूट हो रहा है...</string>
<string name="server_shutting_down">बंद हो रहा है...</string>
<string name="server_timeout_warning">चेतावनी: सर्वर स्थिति परिवर्तन का समय समाप्त हो गया।</string>
<string name="system_ready">सिस्टम तैयार...\n</string>
<string name="hotspot">हॉटस्पॉट</string>
<string name="tunnel">टनल</string>
<string name="wifi">Wi-Fi</string>
<string name="qr_error_no_network">नेटवर्क पर सामग्री साझा करने के लिए Wi-Fi या हॉटस्पॉट सक्षम करें।</string>
<string name="qr_error_no_server">नेटवर्क पर सामग्री साझा करने के लिए सर्वर लॉन्च करें।</string>
<string name="qr_flip_network">नेटवर्क स्विच करें</string>
<string name="qr_title_hotspot">हॉटस्पॉट नेटवर्क</string>
<string name="qr_title_wifi">Wi-Fi नेटवर्क</string>
<string name="control_disable">Safe Pocket Web अक्षम करें</string>
<string name="control_enable">Safe Pocket Web सक्षम करें</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="global">ग्लोबल</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="maintenance_mode">रखरखाव मोड</string>
<string name="maintenance_warning_msg">संशोधन करने के लिए Safe Pocket Web अक्षम करें</string>
<string name="remote_dns">रिमोट DNS</string>
<string name="socks_addr">Socks पता:</string>
<string name="socks_pass">Socks पासवर्ड:</string>
<string name="socks_port">Socks पोर्ट:</string>
<string name="socks_udp_addr">Socks UDP पता:</string>
<string name="socks_user">Socks उपयोगकर्ता नाम:</string>
<string name="udp_in_tcp">TCP पर UDP रिले</string>
<string name="recovery_channel_name">VPN रिकवरी</string>
<string name="recovery_notif_text">सुरक्षित वातावरण को तुरंत बहाल करने के लिए टैप करें।</string>
<string name="recovery_notif_title">Safe Pocket Web बाधित</string>
<string name="tproxy_channel_name">socks5</string>
<string name="user_initiated_conn">उपयोगकर्ता द्वारा शुरू किया गया कनेक्शन</string>
<string name="vpn_description">अनुकूल URL सक्षम करें। खतरों को ब्लॉक करें।</string>
<string name="vpn_permission_granted">VPN अनुमति दी गई। कनेक्ट हो रहा है...</string>
<string name="vpn_starting">VPN शुरू हो रहा है...</string>
<string name="vpn_stopping">VPN बंद हो रहा है...</string>
<string name="watchdog_disable">मास्टर वॉचडॉग\nअक्षम करें</string>
<string name="watchdog_enable">मास्टर वॉचडॉग\nसक्षम करें</string>
<string name="watchdog_description">Termux को Doze मोड से बचाता है और Wi-Fi को सक्रिय रखता है।</string>
<string name="watchdog_channel_desc">यह सुनिश्चित करता है कि स्क्रीन बंद होने पर भी सेवाएं सक्रिय रहें।</string>
<string name="watchdog_channel_name">IIAB वॉचडॉग सेवा</string>
<string name="watchdog_notif_text">Termux वातावरण की सुरक्षा...</string>
<string name="watchdog_notif_title">IIAB वॉचडॉग सक्रिय</string>
<string name="cpu_wakelock_acquired">VPN सुरक्षा के तहत CPU WakeLock प्राप्त</string>
<string name="cpu_wakelock_released">CPU WakeLock जारी</string>
<string name="error_acquiring_locks">लॉक प्राप्त करने में त्रुटि</string>
<string name="syncing_watchdog">वॉचडॉग स्थिति सिंक हो रही है। सक्षम: %b</string>
<string name="watchdog_started">वॉचडॉग शुरू</string>
<string name="watchdog_stopped">वॉचडॉग बंद</string>
<string name="watchdog_thread_ended">वॉचडॉग थ्रेड: लूप समाप्त</string>
<string name="watchdog_thread_error">वॉचडॉग थ्रेड: लूप में त्रुटि</string>
<string name="watchdog_thread_interrupted">वॉचडॉग थ्रेड: बाधित, बंद हो रहा है...</string>
<string name="watchdog_thread_started">वॉचडॉग थ्रेड: लूप शुरू हुआ</string>
<string name="wifi_lock_acquired">VPN सुरक्षा के तहत Wi-Fi Lock प्राप्त</string>
<string name="wifi_lock_released">Wi-Fi Lock जारी</string>
<string name="critical_os_blocked">क्रिटिकल: OS ने Termux उत्तेजना को ब्लॉक कर दिया (SecurityException)।</string>
<string name="failed_termux_intent">क्रिटिकल: Termux इंटेंट विफल: %s</string>
<string name="force_termux_foreground">Termux को फॉरग्राउंड में मजबूर किया जा रहा है...</string>
<string name="maintenance_mode_enabled">रखरखाव मोड सक्षम: Termux के पास सीधा इंटरनेट एक्सेस है</string>
<string name="maintenance_write_failed">रखरखाव लेखन विफल</string>
<string name="permission_denied_log">अनुमति अस्वीकार: सुनिश्चित करें कि मैनिफ़ेस्ट में RUN_COMMAND है और ऐप प्रतिबंधित नहीं है।</string>
<string name="ping_fail">PING 8085: विफल (%s)</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="pulse_error_log">पल्स त्रुटि: %s</string>
<string name="pulse_stimulating">पल्स: Termux को उत्तेजित करना...</string>
<string name="recovery_pulse_received">सिस्टम से रिकवरी पल्स प्राप्त। VPN लागू किया जा रहा है...</string>
<string name="sent_to_termux">Termux को भेजा गया: %s</string>
<string name="session_started">हार्टबीट सत्र शुरू हुआ</string>
<string name="session_stopped">हार्टबीट सत्र बंद हुआ</string>
<string name="termux_invocation_error">Termux को बुलाने में त्रुटि: %1$s</string>
<string name="termux_pulse_error">[Termux] पल्स त्रुटि (exit %1$d): %2$s</string>
<string name="termux_stimulus_ok">[Termux] स्टिमुलस OK (exit 0)</string>
<string name="termux_stuck_warning">Termux नहीं खुल रहा है? फोकस पाने के लिए मास्टर वॉचडॉग सक्षम करें।</string>
<string name="unexpected_error_termux">Termux पर इंटेंट भेजने में अनपेक्षित त्रुटि</string>
<string name="end_of_history">--- इतिहास का अंत ---</string>
<string name="error_reading_history">इतिहास पढ़ने में त्रुटि: %s</string>
<string name="failed_write_blackbox">BlackBox में लिखने में विफल</string>
<string name="loading_history">--- इतिहास लोड हो रहा है ---</string>
<string name="log_cleared_toast">लॉग साफ़ किया गया</string>
<string name="log_copied_toast">लॉग क्लिपबोर्ड पर कॉपी किया गया</string>
<string name="log_reset_confirm_msg">यह सभी संग्रहीत कनेक्शन लॉग को स्थायी रूप से हटा देगा। यह कार्रवाई पूर्ववत नहीं की जा सकती।</string>
<string name="log_reset_confirm_title">लॉग इतिहास रीसेट करें?</string>
<string name="failed_reset_log">लॉग रीसेट करने में विफल: %s</string>
<string name="log_reset_log">लॉग रीसेट किया गया</string>
<string name="log_reset_user">उपयोगकर्ता द्वारा लॉग रीसेट</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_format">आकार: %1$s / 10MB</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="log_warning_rapid_growth">लॉग फ़ाइल बहुत तेज़ी से बढ़ रही है, आपको जांचना चाहिए कि क्या कुछ विफल हो रहा है</string>
<string name="no_blackbox_found">--- कोई BlackBox फ़ाइल नहीं मिली ---</string>
<string name="auth_required_subtitle">सुरक्षित वातावरण को अक्षम करने के लिए प्रमाणित करें</string>
<string name="auth_required_title">प्रमाणीकरण आवश्यक है</string>
<string name="auth_success_disconnect">प्रमाणीकरण सफल। डिस्कनेक्ट हो रहा है...</string>
<string name="security_required_msg">सुरक्षित वातावरण को सक्षम करने से पहले आपको अपने डिवाइस पर PIN, पैटर्न या फ़िंगरप्रिंट सेट करना होगा।</string>
<string name="security_required_title">सुरक्षा आवश्यक</string>
<string name="unlock_watchdog_subtitle">Termux सुरक्षा को रोकने के लिए प्रमाणीकरण आवश्यक है</string>
<string name="unlock_watchdog_title">मास्टर वॉचडॉग अनलॉक करें</string>
<string name="battery_opt_denied">ऐप को 100% काम करने के लिए, कृपया बैटरी अनुकूलन को अक्षम करें।</string>
<string name="battery_opt_msg">वॉचडॉग के विश्वसनीय रूप से काम करने के लिए, कृपया इस ऐप के लिए बैटरी अनुकूलन को अक्षम करें।</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme पहचाना गया: कृपया सुनिश्चित करें कि आप इस ऐप की सेटिंग्स में \'Allow background activity\' सक्षम करें।</string>
<string name="battery_opt_title">बैटरी अनुकूलन</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi पहचाना गया: कृपया सेटिंग्स में बैटरी सेवर को \'No restrictions\' पर सेट करें।</string>
<string name="go_to_settings">सेटिंग्स पर जाएं</string>
<string name="app_started">एप्लिकेशन शुरू हुआ</string>
<string name="deploy_wip_desc">Termux मॉड्यूल और वातावरण इंस्टॉलर जल्द ही यहां उपलब्ध होंगे।</string>
<string name="deploy_wip_title">WIP - निर्माणाधीन</string>
<string name="battery_custom">"Battery: "</string>
<string name="battery_no_value">Battery: --%</string>
<string name="hotspot_fdash">हॉटस्पॉट: --</string>
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="saved_toast">सहेजा गया</string>
<string name="settings_saved">सेटिंग्स सहेजी गईं</string>
<string name="uptime_no_value">अपटाइम: --</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="setup_display_over_other_apps">Display over other apps</string>
<string name="setup_manage_all_permissions">Manage All Permissions</string>
<string name="setup_manage_termux_permissions">Manage Termux permissions</string>
<string name="setup_termux_custom_permissions">Termux custom permissions</string>
</resources>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="dash_bg_main">#121212</color>
<color name="dash_bg_card">#242424</color>
<color name="dash_text_primary">#FAFAFA</color>
<color name="dash_text_secondary">#BDBDBD</color>
<color name="dash_module_bg">#242424</color>
<color name="dash_module_text">#FFFFFF</color>
<color name="dash_divider">#333333</color>
<color name="dash_warning">#FFB300</color>
<color name="dash_status_online">#4CAF50</color>
<color name="footer_text_color">#888888</color>
</resources>

View File

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.IIABController" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">#000000</item>
<item name="colorPrimaryDark">#000000</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@color/background_dark</item>
<item name="android:textColorPrimary">@color/white</item>
<item name="sectionBackground">@color/section_body_bg_dark</item>
<item name="sectionHeaderBackground">@color/black</item>
</style>
</resources>

View File

@ -1,231 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="cancel">Cancelar</string>
<string name="copy_all">Copiar Tudo</string>
<string name="fix_action">CORRIGIR</string>
<string name="save">Salvar</string>
<string name="advanced_settings_label">Configurações do Túnel</string>
<string name="configuration_label">Configuração</string>
<string name="connection_log_label">Log de Conexão</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="settings_label">CONFIGURAÇÕES</string>
<string name="tab_deploy">Instalação</string>
<string name="tab_status">Status</string>
<string name="tab_usage">Uso</string>
<string name="setup_title">Configuração Inicial</string>
<string name="setup_welcome">Bem-vindo ao assistente de configuração do %1$s.\n\nPara funcionar corretamente, precisamos das seguintes permissões:</string>
<string name="setup_continue">Continuar</string>
<string name="setup_perm_battery">Desativar Otimização de Bateria</string>
<string name="setup_perm_notifications">Notificações Push</string>
<string name="setup_perm_storage">Acesso ao armazenamento local</string>
<string name="setup_perm_termux">Execução do Termux</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_termux_storage_btn">Arquivos e mídia (Armazenamento)</string>
<string name="termux_not_installed">O Termux não está instalado.</string>
<string name="termux_not_installed_error">O Termux não está instalado ou o dispositivo não é compatível.</string>
<string name="notif_perm_denied">Permissão de notificação negada</string>
<string name="notif_perm_granted">Permissão de notificação concedida</string>
<string name="revoke_permission_warning">Para revogar permissões, você deve fazê-lo nas configurações do sistema.</string>
<string name="termux_perm_denied">Permissão do Termux negada</string>
<string name="termux_perm_granted">Permissão do Termux concedida</string>
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_device">Carregando o dispositivo...</string>
<string name="dash_ip">IP: %1$s</string>
<string name="dash_uptime">Tempo de atividade: %1$s</string>
<string name="dash_battery_format"><b>Bateria:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Bateria:</b> --%%</string>
<string name="dash_hotspot_format"><b>Hotspot:</b> %1$s</string>
<string name="dash_uptime_format"><b>Tempo de atividade:</b> %1$s</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_main_storage">Armazenamento principal</string>
<string name="dash_ram_memory">Memória RAM</string>
<string name="dash_swap_virtual">Swap (Virtual)</string>
<string name="dash_server_status">Status do Servidor:</string>
<string name="dash_offline">Offline</string>
<string name="dash_online">Online</string>
<string name="dash_system_state">Estado do Sistema</string>
<string name="dash_iiab_system">Sistema IIAB-oA</string>
<string name="dash_state_debian_only">SO base instalado. Prossiga com a instalação do IIAB.</string>
<string name="dash_state_installer">Instalador encontrado, abra a aba de instalação para mais informações.</string>
<string name="dash_state_none">Nenhum componente identificado, nem mesmo o Termux.</string>
<string name="dash_state_offline">O IIAB-oA parece estar offline, tente iniciá-lo.</string>
<string name="dash_state_online">IIAB-oA está online.</string>
<string name="dash_state_termux_only">Termux encontrado, vá para a aba Instalação para gerenciá-lo.</string>
<string name="dash_termux_searching">Buscando instalação...</string>
<string name="dash_installed_modules">Módulos Instalados</string>
<string name="dash_books">Livros</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Mapas</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">Sistema</string>
<string name="apps">Aplicativos</string>
<string name="browse_content">🚀 Explorar Conteúdo</string>
<string name="launch_server">🚀 Iniciar Servidor</string>
<string name="stop_server">🛑 Parar Servidor</string>
<string name="server_not_installed_warning">O sistema IIAB-oA não parece estar (totalmente) instalado. Verifique a aba Status ou Instalação para mais informações.</string>
<string name="server_booting">Inicializando...</string>
<string name="server_shutting_down">Desligando...</string>
<string name="server_timeout_warning">Aviso: Tempo limite de transição de estado do servidor excedido.</string>
<string name="system_ready">Sistema pronto...\n</string>
<string name="hotspot">Hotspot</string>
<string name="tunnel">Túnel</string>
<string name="wifi">Wi-Fi</string>
<string name="qr_error_no_network">Ative o Wi-Fi ou Hotspot para compartilhar conteúdo pela rede.</string>
<string name="qr_error_no_server">Inicie o servidor para compartilhar conteúdo pela rede.</string>
<string name="qr_flip_network">Trocar Rede</string>
<string name="qr_title_hotspot">Rede Hotspot</string>
<string name="qr_title_wifi">Rede Wi-Fi</string>
<string name="control_disable">Desativar Safe Pocket Web</string>
<string name="control_enable">Ativar Safe Pocket Web</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="global">Global</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="maintenance_mode">Modo de Manutenção</string>
<string name="maintenance_warning_msg">Desative o Safe Pocket Web para poder modificar</string>
<string name="remote_dns">DNS Remoto</string>
<string name="socks_addr">Endereço Socks:</string>
<string name="socks_pass">Senha Socks:</string>
<string name="socks_port">Porta Socks:</string>
<string name="socks_udp_addr">Endereço UDP Socks:</string>
<string name="socks_user">Usuário Socks:</string>
<string name="udp_in_tcp">Relé UDP sobre TCP</string>
<string name="recovery_channel_name">Recuperação de VPN</string>
<string name="recovery_notif_text">Toque para restaurar o ambiente seguro imediatamente.</string>
<string name="recovery_notif_title">Safe Pocket Web Interrompido</string>
<string name="tproxy_channel_name">socks5</string>
<string name="user_initiated_conn">Conexão iniciada pelo usuário</string>
<string name="vpn_description">Ative URLs amigáveis. Bloqueie as ameaças.</string>
<string name="vpn_permission_granted">Permissão de VPN concedida. Conectando...</string>
<string name="vpn_starting">Iniciando VPN...</string>
<string name="vpn_stopping">Parando VPN...</string>
<string name="watchdog_disable">Desativar\nWatchdog Mestre</string>
<string name="watchdog_enable">Ativar\nWatchdog Mestre</string>
<string name="watchdog_description">Protege o Termux do modo Doze e mantém o Wi-Fi ativo.</string>
<string name="watchdog_channel_desc">Garante que os serviços permaneçam ativos quando a tela estiver desligada.</string>
<string name="watchdog_channel_name">Serviço IIAB Watchdog</string>
<string name="watchdog_notif_text">Protegendo o ambiente Termux...</string>
<string name="watchdog_notif_title">IIAB Watchdog Ativo</string>
<string name="cpu_wakelock_acquired">CPU WakeLock adquirido sob proteção VPN</string>
<string name="cpu_wakelock_released">CPU WakeLock liberado</string>
<string name="error_acquiring_locks">Erro ao adquirir bloqueios</string>
<string name="syncing_watchdog">Sincronizando estado do Watchdog. Ativado: %b</string>
<string name="watchdog_started">Watchdog Iniciado</string>
<string name="watchdog_stopped">Watchdog Parado</string>
<string name="watchdog_thread_ended">Watchdog Thread: Loop encerrado</string>
<string name="watchdog_thread_error">Watchdog Thread: Erro no loop</string>
<string name="watchdog_thread_interrupted">Watchdog Thread: Interrompido, parando...</string>
<string name="watchdog_thread_started">Watchdog Thread: Loop iniciado</string>
<string name="wifi_lock_acquired">Wi-Fi Lock adquirido sob proteção VPN</string>
<string name="wifi_lock_released">Wi-Fi Lock liberado</string>
<string name="critical_os_blocked">CRÍTICO: O SO bloqueou o estímulo do Termux (SecurityException).</string>
<string name="failed_termux_intent">CRÍTICO: Falha no Intent do Termux: %s</string>
<string name="force_termux_foreground">Forçando o Termux para o primeiro plano...</string>
<string name="maintenance_mode_enabled">Modo de manutenção ativado: Termux tem acesso direto à Internet</string>
<string name="maintenance_write_failed">Falha na escrita de manutenção</string>
<string name="permission_denied_log">Permissão Negada: Certifique-se de que o manifesto tem RUN_COMMAND e o aplicativo não está restrito.</string>
<string name="ping_fail">PING 8085: FALHA (%s)</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="pulse_error_log">Erro de Pulso: %s</string>
<string name="pulse_stimulating">Pulso: Estimulando o Termux...</string>
<string name="recovery_pulse_received">Pulso de recuperação recebido do sistema. Forçando VPN...</string>
<string name="sent_to_termux">Enviado para o Termux: %s</string>
<string name="session_started">SESSÃO DE BATIMENTO CARDÍACO INICIADA</string>
<string name="session_stopped">SESSÃO DE BATIMENTO CARDÍACO PARADA</string>
<string name="termux_invocation_error">Erro ao invocar Termux: %1$s</string>
<string name="termux_pulse_error">[Termux] Erro de Pulso (exit %1$d): %2$s</string>
<string name="termux_stimulus_ok">[Termux] Estímulo OK (exit 0)</string>
<string name="termux_stuck_warning">Termux não abre? Ative o Watchdog Mestre para forçá-lo a ganhar foco.</string>
<string name="unexpected_error_termux">Erro inesperado ao enviar intent para o Termux</string>
<string name="end_of_history">--- Fim do Histórico ---</string>
<string name="error_reading_history">Erro ao ler histórico: %s</string>
<string name="failed_write_blackbox">Falha ao escrever no BlackBox</string>
<string name="loading_history">--- Carregando Histórico ---</string>
<string name="log_cleared_toast">Log limpo</string>
<string name="log_copied_toast">Log copiado para a área de transferência</string>
<string name="log_reset_confirm_msg">Isso apagará permanentemente todos os logs de conexão armazenados. Esta ação não pode ser desfeita.</string>
<string name="log_reset_confirm_title">Redefinir Histórico de Log?</string>
<string name="failed_reset_log">Falha ao redefinir log: %s</string>
<string name="log_reset_log">Log redefinido</string>
<string name="log_reset_user">Log redefinido pelo usuário</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_format">Tamanho: %1$s / 10MB</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="log_warning_rapid_growth">O arquivo de log está crescendo muito rapidamente, verifique se algo está falhando</string>
<string name="no_blackbox_found">--- Nenhum arquivo BlackBox encontrado ---</string>
<string name="auth_required_subtitle">Autentique-se para desativar o ambiente seguro</string>
<string name="auth_required_title">Autenticação necessária</string>
<string name="auth_success_disconnect">Autenticação bem-sucedida. Desconectando...</string>
<string name="security_required_msg">Você deve definir um PIN, Padrão ou Impressão Digital no seu dispositivo antes de ativar o ambiente seguro.</string>
<string name="security_required_title">Segurança Necessária</string>
<string name="unlock_watchdog_subtitle">Autenticação necessária para parar a proteção do Termux</string>
<string name="unlock_watchdog_title">Desbloquear Watchdog Mestre</string>
<string name="battery_opt_denied">Para que o app funcione 100%, desative a otimização de bateria.</string>
<string name="battery_opt_msg">Para que o Watchdog funcione de forma confiável, desative as otimizações de bateria para este aplicativo.</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme detectado: Certifique-se de ativar \'Permitir atividade em segundo plano\' nas configurações deste aplicativo.</string>
<string name="battery_opt_title">Otimização de Bateria</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi detectado: Defina a economia de bateria para \'Sem restrições\' nas configurações.</string>
<string name="go_to_settings">Ir para Configurações</string>
<string name="app_started">Aplicativo Iniciado</string>
<string name="deploy_wip_desc">O módulo Termux e o instalador de ambiente estarão disponíveis aqui em breve.</string>
<string name="deploy_wip_title">WIP - Em Construção</string>
<string name="battery_custom">"Bateria: "</string>
<string name="battery_no_value">Bateria: --%</string>
<string name="hotspot_fdash">Hotspot: --</string>
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="saved_toast">Salvo</string>
<string name="settings_saved">Configurações salvas</string>
<string name="uptime_no_value">Tempo de atividade: --</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="setup_display_over_other_apps">Display over other apps</string>
<string name="setup_manage_all_permissions">Manage All Permissions</string>
<string name="setup_manage_termux_permissions">Manage Termux permissions</string>
<string name="setup_termux_custom_permissions">Termux custom permissions</string>
</resources>

View File

@ -1,231 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="cancel">Отмена</string>
<string name="copy_all">Скопировать все</string>
<string name="fix_action">ИСПРАВИТЬ</string>
<string name="save">Сохранить</string>
<string name="advanced_settings_label">Настройки туннеля</string>
<string name="configuration_label">Конфигурация</string>
<string name="connection_log_label">Журнал подключений</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="settings_label">НАСТРОЙКИ</string>
<string name="tab_deploy">Установка</string>
<string name="tab_status">Статус</string>
<string name="tab_usage">Использование</string>
<string name="setup_title">Начальная настройка</string>
<string name="setup_welcome">Добро пожаловать в мастер настройки %1$s.\n\nДля правильной работы нам нужны следующие разрешения:</string>
<string name="setup_continue">Продолжить</string>
<string name="setup_perm_battery">Отключить оптимизацию батареи</string>
<string name="setup_perm_notifications">Push-уведомления</string>
<string name="setup_perm_storage">Доступ к локальному хранилищу</string>
<string name="setup_perm_termux">Выполнение Termux</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_termux_storage_btn">Файлы и медиа (Хранилище)</string>
<string name="termux_not_installed">Termux не установлен.</string>
<string name="termux_not_installed_error">Termux не установлен или устройство не поддерживается.</string>
<string name="notif_perm_denied">Разрешение на уведомления отклонено</string>
<string name="notif_perm_granted">Разрешение на уведомления предоставлено</string>
<string name="revoke_permission_warning">Чтобы отозвать разрешения, это нужно сделать в настройках системы.</string>
<string name="termux_perm_denied">Разрешение Termux отклонено</string>
<string name="termux_perm_granted">Разрешение Termux предоставлено</string>
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_device">Загрузка устройства...</string>
<string name="dash_ip">IP: %1$s</string>
<string name="dash_uptime">Время работы: %1$s</string>
<string name="dash_battery_format"><b>Батарея:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Батарея:</b> --%%</string>
<string name="dash_hotspot_format"><b>Точка доступа:</b> %1$s</string>
<string name="dash_uptime_format"><b>Время работы:</b> %1$s</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_main_storage">Основная память</string>
<string name="dash_ram_memory">ОЗУ</string>
<string name="dash_swap_virtual">Swap (Виртуальная)</string>
<string name="dash_server_status">Статус сервера:</string>
<string name="dash_offline">Оффлайн</string>
<string name="dash_online">В сети</string>
<string name="dash_system_state">Состояние системы</string>
<string name="dash_iiab_system">Система IIAB-oA</string>
<string name="dash_state_debian_only">ОС установлена. Перейдите к установке IIAB.</string>
<string name="dash_state_installer">Установщик найден, откройте вкладку установки для получения подробной информации.</string>
<string name="dash_state_none">Компоненты не найдены, даже Termux.</string>
<string name="dash_state_offline">IIAB-oA оффлайн, попробуйте запустить его.</string>
<string name="dash_state_online">IIAB-oA доступен онлайн.</string>
<string name="dash_state_termux_only">Termux найден, перейдите на вкладку Установка, чтобы управлять им.</string>
<string name="dash_termux_searching">Поиск установки...</string>
<string name="dash_installed_modules">Установленные модули</string>
<string name="dash_books">Книги</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Карты</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">Система</string>
<string name="apps">Приложения</string>
<string name="browse_content">🚀 Исследовать контент</string>
<string name="launch_server">🚀 Запустить сервер</string>
<string name="stop_server">🛑 Остановить сервер</string>
<string name="server_not_installed_warning">Система IIAB-oA (полностью) не установлена. Проверьте вкладку Статус или Установка для получения подробной информации.</string>
<string name="server_booting">Загрузка...</string>
<string name="server_shutting_down">Выключение...</string>
<string name="server_timeout_warning">Предупреждение: Время ожидания перехода состояния сервера истекло.</string>
<string name="system_ready">Система готова...\n</string>
<string name="hotspot">Точка доступа</string>
<string name="tunnel">Туннель</string>
<string name="wifi">Wi-Fi</string>
<string name="qr_error_no_network">Включите Wi-Fi или точку доступа, чтобы поделиться контентом по сети.</string>
<string name="qr_error_no_server">Запустите сервер, чтобы поделиться контентом по сети.</string>
<string name="qr_flip_network">Переключить сеть</string>
<string name="qr_title_hotspot">Сеть точки доступа</string>
<string name="qr_title_wifi">Сеть Wi-Fi</string>
<string name="control_disable">Выключить Safe Pocket Web</string>
<string name="control_enable">Включить Safe Pocket Web</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="global">Глобально</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="maintenance_mode">Режим обслуживания</string>
<string name="maintenance_warning_msg">Отключите Safe Pocket Web для внесения изменений</string>
<string name="remote_dns">Удаленный DNS</string>
<string name="socks_addr">Адрес Socks:</string>
<string name="socks_pass">Пароль Socks:</string>
<string name="socks_port">Порт Socks:</string>
<string name="socks_udp_addr">UDP адрес Socks:</string>
<string name="socks_user">Имя пользователя Socks:</string>
<string name="udp_in_tcp">UDP ретрансляция через TCP</string>
<string name="recovery_channel_name">Восстановление VPN</string>
<string name="recovery_notif_text">Нажмите, чтобы немедленно восстановить безопасное окружение.</string>
<string name="recovery_notif_title">Safe Pocket Web прерван</string>
<string name="tproxy_channel_name">socks5</string>
<string name="user_initiated_conn">Соединение инициировано пользователем</string>
<string name="vpn_description">Включить дружественные URL. Блокировать угрозы.</string>
<string name="vpn_permission_granted">Разрешение VPN получено. Подключение...</string>
<string name="vpn_starting">Запуск VPN...</string>
<string name="vpn_stopping">Остановка VPN...</string>
<string name="watchdog_disable">Выключить\nМастер Watchdog</string>
<string name="watchdog_enable">Включить\nМастер Watchdog</string>
<string name="watchdog_description">Защищает Termux от режима Doze и поддерживает Wi-Fi активным.</string>
<string name="watchdog_channel_desc">Гарантирует, что службы остаются активными при выключенном экране.</string>
<string name="watchdog_channel_name">Служба IIAB Watchdog</string>
<string name="watchdog_notif_text">Защита окружения Termux...</string>
<string name="watchdog_notif_title">IIAB Watchdog активен</string>
<string name="cpu_wakelock_acquired">CPU WakeLock получен под защитой VPN</string>
<string name="cpu_wakelock_released">CPU WakeLock освобожден</string>
<string name="error_acquiring_locks">Ошибка получения блокировок</string>
<string name="syncing_watchdog">Синхронизация состояния Watchdog. Включено: %b</string>
<string name="watchdog_started">Watchdog запущен</string>
<string name="watchdog_stopped">Watchdog остановлен</string>
<string name="watchdog_thread_ended">Watchdog Thread: Цикл завершен</string>
<string name="watchdog_thread_error">Watchdog Thread: Ошибка в цикле</string>
<string name="watchdog_thread_interrupted">Watchdog Thread: Прервано, остановка...</string>
<string name="watchdog_thread_started">Watchdog Thread: Цикл запущен</string>
<string name="wifi_lock_acquired">Wi-Fi Lock получен под защитой VPN</string>
<string name="wifi_lock_released">Wi-Fi Lock освобожден</string>
<string name="critical_os_blocked">КРИТИЧЕСКАЯ ОШИБКА: ОС заблокировала стимуляцию Termux (SecurityException).</string>
<string name="failed_termux_intent">КРИТИЧЕСКАЯ ОШИБКА: Ошибка Intent Termux: %s</string>
<string name="force_termux_foreground">Принудительно перевести Termux на передний план...</string>
<string name="maintenance_mode_enabled">Режим обслуживания включен: Termux имеет прямой доступ в Интернет</string>
<string name="maintenance_write_failed">Ошибка записи обслуживания</string>
<string name="permission_denied_log">В доступе отказано: убедитесь, что в манифесте есть RUN_COMMAND и приложение не ограничено.</string>
<string name="ping_fail">PING 8085: ОШИБКА (%s)</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="pulse_error_log">Ошибка пульса: %s</string>
<string name="pulse_stimulating">Пульс: Стимуляция Termux...</string>
<string name="recovery_pulse_received">Пульс восстановления получен от системы. Принудительный VPN...</string>
<string name="sent_to_termux">Отправлено в Termux: %s</string>
<string name="session_started">СЕАНС СЕРДЦЕБИЕНИЯ ЗАПУЩЕН</string>
<string name="session_stopped">СЕАНС СЕРДЦЕБИЕНИЯ ОСТАНОВЛЕН</string>
<string name="termux_invocation_error">Ошибка вызова Termux: %1$s</string>
<string name="termux_pulse_error">[Termux] Ошибка пульса (exit %1$d): %2$s</string>
<string name="termux_stimulus_ok">[Termux] Стимул OK (exit 0)</string>
<string name="termux_stuck_warning">Termux не открывается? Включите Мастер Watchdog, чтобы принудительно вывести его на передний план.</string>
<string name="unexpected_error_termux">Непредвиденная ошибка при отправке intent в Termux</string>
<string name="end_of_history">--- Конец истории ---</string>
<string name="error_reading_history">Ошибка чтения истории: %s</string>
<string name="failed_write_blackbox">Ошибка записи в BlackBox</string>
<string name="loading_history">--- Загрузка истории ---</string>
<string name="log_cleared_toast">Журнал очищен</string>
<string name="log_copied_toast">Журнал скопирован в буфер обмена</string>
<string name="log_reset_confirm_msg">Это безвозвратно удалит все сохраненные журналы подключений. Это действие нельзя отменить.</string>
<string name="log_reset_confirm_title">Сбросить историю журнала?</string>
<string name="failed_reset_log">Ошибка сброса журнала: %s</string>
<string name="log_reset_log">Журнал сброшен</string>
<string name="log_reset_user">Журнал сброшен пользователем</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_format">Размер: %1$s / 10MB</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="log_warning_rapid_growth">Файл журнала растет слишком быстро, возможно, стоит проверить, нет ли ошибки</string>
<string name="no_blackbox_found">--- Файл BlackBox не найден ---</string>
<string name="auth_required_subtitle">Пройдите аутентификацию, чтобы отключить безопасное окружение</string>
<string name="auth_required_title">Требуется аутентификация</string>
<string name="auth_success_disconnect">Аутентификация успешна. Отключение...</string>
<string name="security_required_msg">Перед активацией безопасного окружения необходимо установить PIN-код, графический ключ или отпечаток пальца на устройстве.</string>
<string name="security_required_title">Требуется безопасность</string>
<string name="unlock_watchdog_subtitle">Требуется аутентификация для остановки защиты Termux</string>
<string name="unlock_watchdog_title">Разблокировать Мастер Watchdog</string>
<string name="battery_opt_denied">Для 100% работы приложения, пожалуйста, отключите оптимизацию батареи.</string>
<string name="battery_opt_msg">Для надежной работы Watchdog, пожалуйста, отключите оптимизацию батареи для этого приложения.</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme обнаружен: Пожалуйста, убедитесь, что вы включили "Разрешить фоновую активность" в настройках этого приложения.</string>
<string name="battery_opt_title">Оптимизация батареи</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi обнаружен: Пожалуйста, установите экономию заряда батареи на "Без ограничений" в настройках.</string>
<string name="go_to_settings">Перейти к настройкам</string>
<string name="app_started">Приложение запущено</string>
<string name="deploy_wip_desc">Модуль Termux и установщик окружения скоро будут доступны здесь.</string>
<string name="deploy_wip_title">WIP - В разработке</string>
<string name="battery_custom">"Батарея: "</string>
<string name="battery_no_value">Батарея: --%</string>
<string name="hotspot_fdash">Точка доступа: --</string>
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="saved_toast">Сохранено</string>
<string name="settings_saved">Настройки сохранены</string>
<string name="uptime_no_value">Время работы: --</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="setup_display_over_other_apps">Display over other apps</string>
<string name="setup_manage_all_permissions">Manage All Permissions</string>
<string name="setup_manage_termux_permissions">Manage Termux permissions</string>
<string name="setup_termux_custom_permissions">Termux custom permissions</string>
</resources>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="sectionBackground" format="color" />
<attr name="sectionHeaderBackground" format="color" />
<declare-styleable name="ProgressButton">
<attr name="progressButtonColor" format="color" />
<attr name="progressButtonBackgroundColor" format="color" />
<attr name="progressButtonHeight" format="dimension" />
<attr name="progressButtonDuration" format="integer" />
</declare-styleable>
</resources>

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#1A1A1A</color>
<color name="colorPrimaryDark">#000000</color>
<color name="colorAccent">#2E7D32</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="lightGray66">#AAAAAA</color>
<color name="background_dark">#121212</color>
<!-- Status Colors (Fixed for readability) -->
<color name="btn_watchdog_on">#D32F2F</color> <!-- Material Red -->
<color name="btn_watchdog_off">#1976D2</color> <!-- Material Blue -->
<color name="btn_vpn_on">#C62828</color> <!-- Dark Red -->
<color name="btn_vpn_off">#2E7D32</color> <!-- Material Green -->
<color name="btn_explore_ready">#F57C00</color>
<color name="btn_explore_disabled">#9E9E9E</color>
<color name="btn_vpn_on_dim">#EF9A9A</color>
<color name="btn_vpn_off_dim">#A5D6A7</color>
<!-- Sections -->
<color name="section_header_bg">#333333</color>
<color name="section_body_bg_light">#F5F5F5</color>
<color name="section_body_bg_dark">#1A1A1A</color>
<color name="text_warning">#FF9800</color>
<color name="text_muted">#888888</color>
<color name="divider_color">#444444</color>
<color name="btn_danger">#D32F2F</color>
<color name="btn_success">#388E3C</color>
<color name="bar_storage">#4DB6AC</color>
<color name="bar_ram">#FFB300</color>
<color name="bar_swap">#7986CB</color>
<color name="bar_background">#333333</color>
<!-- Landing -->
<color name="dash_bar_storage">#4DB6AC</color>
<color name="dash_bar_ram">#FFB300</color>
<color name="dash_bar_swap">#7986CB</color>
<color name="dash_badge_online">#2E7D32</color>
<color name="dash_bar_bg">#333333</color>
<color name="dash_bg_main">#F4F5F7</color>
<color name="dash_bg_card">#FFFFFF</color>
<color name="dash_text_primary">#202124</color>
<color name="dash_text_secondary">#5F6368</color>
<color name="dash_module_bg">#E8EAED</color>
<color name="dash_module_text">#202124</color>
<color name="dash_divider">#E0E0E0</color>
<color name="dash_warning">#E65100</color>
<color name="dash_status_online">#2E7D32</color>
<color name="footer_text_color">#FFFFFF</color>
</resources>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="server_cool_off_duration_ms">60000</integer>
<integer name="server_snackbar_delay_ms">20000</integer>
</resources>

View File

@ -1,233 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="cancel">Cancel</string>
<string name="copy_all">Copy All</string>
<string name="fix_action">FIX</string>
<string name="save">Save</string>
<string name="advanced_settings_label">Tunnel Settings</string>
<string name="configuration_label">Configuration</string>
<string name="connection_log_label">Connection Log</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="settings_label">SETTINGS</string>
<string name="tab_deploy">Installation</string>
<string name="tab_status">Status</string>
<string name="tab_usage">Usage</string>
<string name="setup_title">Initial Setup</string>
<string name="setup_welcome">Welcome to the %1$s setup wizard.\n\nIn order to work properly, we need the following permissions:</string>
<string name="setup_continue">Continue</string>
<string name="setup_perm_battery">Disable Battery Optimization</string>
<string name="setup_perm_notifications">Push Notifications</string>
<string name="setup_perm_storage">Local Storage Access</string>
<string name="setup_perm_termux">Termux Execution</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_termux_storage_btn">Files and media (Storage)</string>
<string name="termux_not_installed">Termux is not installed.</string>
<string name="termux_not_installed_error">Termux is not installed or device not supported.</string>
<string name="notif_perm_denied">Notification permission denied</string>
<string name="notif_perm_granted">Notification permission granted</string>
<string name="revoke_permission_warning">To revoke permissions, you must do it from system settings.</string>
<string name="termux_perm_denied">Termux permission denied</string>
<string name="termux_perm_granted">Termux permission granted</string>
<string name="dash_title">IIAB-oA Controller</string>
<string name="dash_subtitle_localhost">localhost</string>
<string name="dash_device">Loading device...</string>
<string name="dash_ip">IP: %1$s</string>
<string name="dash_uptime">Uptime: %1$s</string>
<string name="dash_battery_format"><b>Battery:</b> %1$d%%</string>
<string name="dash_battery_no_value"><b>Battery:</b> --%%</string>
<string name="dash_hotspot_format"><b>Hotspot:</b> %1$s</string>
<string name="dash_uptime_format"><b>Uptime:</b> %1$s</string>
<string name="dash_wifi_format"><b>Wi-Fi:</b> %1$s</string>
<string name="dash_main_storage">Main Storage</string>
<string name="dash_ram_memory">RAM Memory</string>
<string name="dash_swap_virtual">Swap (Virtual)</string>
<string name="dash_server_status">Server Status:</string>
<string name="dash_offline">Offline</string>
<string name="dash_online">Online</string>
<string name="dash_system_state">System State</string>
<string name="dash_iiab_system">IIAB-oA System</string>
<string name="dash_state_debian_only">Base OS installed. Proceed to install IIAB.</string>
<string name="dash_state_installer">Installer found, open the installation tab for more info.</string>
<string name="dash_state_none">No component identified, not even Termux.</string>
<string name="dash_state_offline">IIAB-oA seems offline, try launching it.</string>
<string name="dash_state_online">IIAB-oA is online.</string>
<string name="dash_state_termux_only">Termux found, go to Installation tab to manage it.</string>
<string name="dash_termux_searching">Searching for installation...</string>
<string name="dash_installed_modules">Installed Modules</string>
<string name="dash_books">Books</string>
<string name="dash_kiwix">Kiwix</string>
<string name="dash_kolibri">Kolibri</string>
<string name="dash_maps">Maps</string>
<string name="dash_matomo">Matomo</string>
<string name="dash_system">System</string>
<string name="dash_code">Code</string>
<string name="apps">Apps</string>
<string name="browse_content">🚀 Explore Content</string>
<string name="launch_server">🚀 Launch Server</string>
<string name="stop_server">🛑 Stop Server</string>
<string name="server_not_installed_warning">The IIAB-oA system does not seem to be (fully) installed. Please check the Status or Installation tab for more info.</string>
<string name="server_booting">Booting...</string>
<string name="server_shutting_down">Shutting down...</string>
<string name="server_timeout_warning">Warning: Server state transition timed out.</string>
<string name="system_ready">System ready...\n</string>
<string name="hotspot">Hotspot</string>
<string name="tunnel">Tunnel</string>
<string name="wifi">Wi-Fi</string>
<string name="qr_error_no_network">Enable Wi-Fi or Hotspot to share content over the network.</string>
<string name="qr_error_no_server">Launch the server to share content over the network.</string>
<string name="qr_flip_network">Switch Network</string>
<string name="qr_title_hotspot">Hotspot Network</string>
<string name="qr_title_wifi">Wi-Fi Network</string>
<string name="control_disable">Disable Safe Pocket Web</string>
<string name="control_enable">Enable Safe Pocket Web</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="global">Global</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="maintenance_mode">Maintenance Mode</string>
<string name="maintenance_warning_msg">Disable Safe Pocket Web in order to modify</string>
<string name="remote_dns">Remote DNS</string>
<string name="socks_addr">Socks Address:</string>
<string name="socks_pass">Socks Password:</string>
<string name="socks_port">Socks Port:</string>
<string name="socks_udp_addr">Socks UDP Address:</string>
<string name="socks_user">Socks Username:</string>
<string name="udp_in_tcp">UDP relay over TCP</string>
<string name="recovery_channel_name">VPN Recovery</string>
<string name="recovery_notif_text">Tap to restore secure environment immediately.</string>
<string name="recovery_notif_title">Safe Pocket Web Interrupted</string>
<string name="tproxy_channel_name">socks5</string>
<string name="user_initiated_conn">User initiated connection</string>
<string name="vpn_description">Enable friendly URLs. Lock out the threats.</string>
<string name="vpn_permission_granted">VPN Permission Granted. Connecting...</string>
<string name="vpn_starting">VPN Starting...</string>
<string name="vpn_stopping">VPN Stopping...</string>
<string name="watchdog_disable">Disable\nMaster Watchdog</string>
<string name="watchdog_enable">Enable\nMaster Watchdog</string>
<string name="watchdog_description">Protects Termux from Doze mode and keeps Wi-Fi active.</string>
<string name="watchdog_channel_desc">Ensures services remain active when screen is off.</string>
<string name="watchdog_channel_name">IIAB Watchdog Service</string>
<string name="watchdog_notif_text">Protecting Termux environment...</string>
<string name="watchdog_notif_title">IIAB Watchdog Active</string>
<string name="cpu_wakelock_acquired">CPU WakeLock acquired under VPN shield</string>
<string name="cpu_wakelock_released">CPU WakeLock released</string>
<string name="error_acquiring_locks">Error acquiring locks</string>
<string name="syncing_watchdog">Syncing Watchdog state. Enabled: %b</string>
<string name="watchdog_started">Watchdog Started</string>
<string name="watchdog_stopped">Watchdog Stopped</string>
<string name="watchdog_thread_ended">Watchdog Thread: Loop ended</string>
<string name="watchdog_thread_error">Watchdog Thread: Error in loop</string>
<string name="watchdog_thread_interrupted">Watchdog Thread: Interrupted, stopping...</string>
<string name="watchdog_thread_started">Watchdog Thread: Started loop</string>
<string name="wifi_lock_acquired">Wi-Fi Lock acquired under VPN shield</string>
<string name="wifi_lock_released">Wi-Fi Lock released</string>
<string name="critical_os_blocked">CRITICAL: OS blocked Termux stimulus (SecurityException).</string>
<string name="failed_termux_intent">CRITICAL: Failed Termux Intent: %s</string>
<string name="force_termux_foreground">Forcing Termux to the foreground...</string>
<string name="maintenance_mode_enabled">Maintenance mode enabled: Termux has direct Internet access</string>
<string name="maintenance_write_failed">Maintenance write failed</string>
<string name="permission_denied_log">Permission Denied: Ensure manifest has RUN_COMMAND and app is not restricted.</string>
<string name="ping_fail">PING 8085: FAIL (%s)</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="pulse_error_log">Pulse Error: %s</string>
<string name="pulse_stimulating">Pulse: Stimulating Termux...</string>
<string name="recovery_pulse_received">Recovery Pulse Received from System. Enforcing VPN...</string>
<string name="sent_to_termux">Sent to Termux: %s</string>
<string name="session_started">HEARTBEAT SESSION STARTED</string>
<string name="session_stopped">HEARTBEAT SESSION STOPPED</string>
<string name="termux_invocation_error">Error invoking Termux: %1$s</string>
<string name="termux_pulse_error">[Termux] Pulse Error (exit %1$d): %2$s</string>
<string name="termux_stimulus_ok">[Termux] Stimulus OK (exit 0)</string>
<string name="termux_stuck_warning">Termux not opening? Enable Master Watchdog to force it to gain focus.</string>
<string name="unexpected_error_termux">Unexpected error sending intent to Termux</string>
<string name="end_of_history">--- End of History ---</string>
<string name="error_reading_history">Error reading history: %s</string>
<string name="failed_write_blackbox">Failed to write to BlackBox</string>
<string name="loading_history">--- Loading History ---</string>
<string name="log_cleared_toast">Log cleared</string>
<string name="log_copied_toast">Log copied to clipboard</string>
<string name="log_reset_confirm_msg">This will permanently delete all stored connection logs. This action cannot be undone.</string>
<string name="log_reset_confirm_title">Reset Log History?</string>
<string name="failed_reset_log">Failed to reset log: %s</string>
<string name="log_reset_log">Log reset</string>
<string name="log_reset_user">Log reset by user</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_format">Size: %1$s / 10MB</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="log_warning_rapid_growth">The logging file is growing too rapidly, you might want to check if something is failing</string>
<string name="no_blackbox_found">--- No BlackBox file found ---</string>
<string name="reset_log">Reset Log</string>
<string name="auth_required_subtitle">Authenticate to disable the secure environment</string>
<string name="auth_required_title">Authentication required</string>
<string name="auth_success_disconnect">Authentication Success. Disconnecting...</string>
<string name="security_required_msg">You must set up a PIN, Pattern, or Fingerprint on your device before enabling the secure environment.</string>
<string name="security_required_title">Security Required</string>
<string name="unlock_watchdog_subtitle">Authentication required to stop Termux protection</string>
<string name="unlock_watchdog_title">Unlock Master Watchdog</string>
<string name="battery_opt_denied">For the app to work 100%, please disable battery optimization.</string>
<string name="battery_opt_msg">For the Watchdog to work reliably, please disable battery optimizations for this app.</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme detected: Please ensure you also enable \'Allow background activity\' in this app\'s settings.</string>
<string name="battery_opt_title">Battery Optimization</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi detected: Please set battery saver to \'No restrictions\' in settings.</string>
<string name="go_to_settings">Go to Settings</string>
<string name="app_started">Application Started</string>
<string name="deploy_wip_desc">The Termux module and environment installer will be available here soon.</string>
<string name="deploy_wip_title">WIP - Under Construction</string>
<string name="battery_custom">"Battery: "</string>
<string name="battery_no_value">Battery: --%</string>
<string name="hotspot_fdash">Hotspot: --</string>
<string name="pref_file_internal">IIAB_Internal</string>
<string name="pref_key_setup_complete">setup_complete</string>
<string name="saved_toast">Saved</string>
<string name="settings_saved">Settings Saved</string>
<string name="uptime_no_value">Uptime: --</string>
<string name="version_footer_fallback">IIAB-oA · 2026 · Controller v0.1.xbeta</string>
<string name="version_footer_format">IIAB-oA · 2026 · Controller %1$s</string>
<string name="wi_fi_fdash">Wi-Fi: --</string>
<string name="setup_display_over_other_apps">Display over other apps</string>
<string name="setup_manage_all_permissions">Manage All Permissions</string>
<string name="setup_manage_termux_permissions">Manage Termux permissions</string>
<string name="setup_termux_custom_permissions">Termux custom permissions</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More