Compare commits
No commits in common. "main" and "multipass_fix1" have entirely different histories.
main
...
multipass_
|
|
@ -0,0 +1,435 @@
|
|||
#!/data/data/com.termux/files/usr/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# IIAB on Android - Termux bootstrap (standalone)
|
||||
#
|
||||
# Requirements:
|
||||
# - Termux installed
|
||||
# - Network connectivity
|
||||
# - Shizuku + rish (Android 12/13 only) to raise Phantom Process Killer (PPK) limit
|
||||
#
|
||||
# Default behavior (idempotent):
|
||||
# - One-time Termux repo mirror selection (prompted once; no TTY checks)
|
||||
# - Acquire wakelock when possible (Termux:API)
|
||||
# - Prepare Termux baseline packages (noninteractive, avoids dpkg conffile prompts)
|
||||
# - Ensure proot-distro + Debian exists (install by default)
|
||||
# - Last step: On Android 12/13 only, re-apply PPK fix via rish (if available)
|
||||
# - Self-check summary at the end
|
||||
#
|
||||
# Flags:
|
||||
# - --ppk-only Run only PPK fix + self-check
|
||||
# - --reset-debian Reset (reinstall) Debian (clean environment), then proceed normally
|
||||
# - --cleanup-rish-export After installing rish into PATH, delete ~/rish and ~/rish*.dex (off by default)
|
||||
|
||||
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; }
|
||||
|
||||
STATE_DIR="${HOME}/.iiab-android"
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
PPK_ONLY=0
|
||||
RESET_DEBIAN=0
|
||||
CLEANUP_RISH_EXPORT=0
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--ppk-only) PPK_ONLY=1 ;;
|
||||
--reset-debian|--clean-debian) RESET_DEBIAN=1 ;;
|
||||
--cleanup-rish-export) CLEANUP_RISH_EXPORT=1 ;;
|
||||
-h|--help)
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./0_termux-setup.sh
|
||||
Default run (idempotent): Termux baseline + Debian bootstrap + PPK fix (Android 12/13) + self-check
|
||||
|
||||
./0_termux-setup.sh --ppk-only
|
||||
Run ONLY the last step: PPK fix (Android 12/13) + self-check
|
||||
|
||||
./0_termux-setup.sh --reset-debian
|
||||
Reset (reinstall) Debian in proot-distro (clean environment), then proceed normally
|
||||
|
||||
./0_termux-setup.sh --cleanup-rish-export
|
||||
After installing rish into PATH, delete ~/rish and ~/rish*.dex (off by default)
|
||||
|
||||
Notes:
|
||||
- This script will NOT delete rish exports unless --cleanup-rish-export is used.
|
||||
- PPK fix is re-applied to 256 on every run (Android 12/13) when possible.
|
||||
- Repo selection is prompted once (skipped for --ppk-only).
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
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)"
|
||||
|
||||
# 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[@]}" "$@"; }
|
||||
|
||||
# -------------------------
|
||||
# Wakelock (Termux:API)
|
||||
# -------------------------
|
||||
WAKELOCK_HELD=0
|
||||
|
||||
acquire_wakelock() {
|
||||
# This helps prevent the device from sleeping during long installs.
|
||||
if have termux-wake-lock; then
|
||||
termux-wake-lock || true
|
||||
WAKELOCK_HELD=1
|
||||
ok "Wakelock acquired (termux-wake-lock)."
|
||||
else
|
||||
warn "termux-wake-lock not available. (Install Termux:API: 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
|
||||
}
|
||||
|
||||
trap 'release_wakelock' EXIT
|
||||
|
||||
# -------------------------
|
||||
# Step 0: One-time repo selector (no TTY checks)
|
||||
# -------------------------
|
||||
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
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# Step 1: Termux baseline packages (idempotent)
|
||||
# -------------------------
|
||||
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 \
|
||||
curl \
|
||||
ca-certificates \
|
||||
coreutils \
|
||||
grep \
|
||||
sed \
|
||||
openssh \
|
||||
proot \
|
||||
proot-distro || true
|
||||
|
||||
ok "Termux baseline ready."
|
||||
date > "$stamp"
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# Debian helpers (robust)
|
||||
# -------------------------
|
||||
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 is not available. Attempting to install it..."
|
||||
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 2: Ensure Debian exists (default), optionally reset it
|
||||
# -------------------------
|
||||
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)..."
|
||||
# Prefer reset (if available), fallback to remove+install
|
||||
if proot-distro help 2>/dev/null | grep -qE '\breset\b'; then
|
||||
proot-distro reset debian
|
||||
else
|
||||
if debian_exists; then proot-distro remove debian; fi
|
||||
proot_install_debian_safe
|
||||
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
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Installing minimal tools inside Debian (noninteractive)..."
|
||||
proot-distro login debian -- bash -lc '
|
||||
set -e
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -y
|
||||
apt-get -y \
|
||||
-o Dpkg::Options::=--force-confdef \
|
||||
-o Dpkg::Options::=--force-confold \
|
||||
install ca-certificates curl coreutils
|
||||
' || true
|
||||
|
||||
ok "Debian bootstrap complete."
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# rish helpers
|
||||
# -------------------------
|
||||
android_major_12_to_13() {
|
||||
case "${ANDROID_SDK:-}" in
|
||||
31|32) echo "12" ;; # Android 12 / 12L
|
||||
33) echo "13" ;; # Android 13
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
rish_export_available_in_home() {
|
||||
[[ -f "$HOME/rish" ]] && ls -1 "$HOME"/rish*.dex >/dev/null 2>&1
|
||||
}
|
||||
|
||||
install_rish_to_path_if_available() {
|
||||
if ! rish_export_available_in_home; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local dex PREFIX_BIN
|
||||
dex="$(ls -1 "$HOME"/rish*.dex 2>/dev/null | head -n1 || true)"
|
||||
PREFIX_BIN="/data/data/com.termux/files/usr/bin"
|
||||
|
||||
install -m 0755 "$HOME/rish" "$PREFIX_BIN/rish"
|
||||
install -m 0644 "$dex" "$PREFIX_BIN/$(basename "$dex")"
|
||||
# Typical adjustment required by rish
|
||||
sed -i 's/PKG/com.termux/g' "$PREFIX_BIN/rish" || true
|
||||
|
||||
if [[ "$CLEANUP_RISH_EXPORT" -eq 1 ]]; then
|
||||
warn "Cleanup requested: removing rish exports from HOME (~/rish, ~/rish*.dex)."
|
||||
rm -f "$HOME/rish" "$HOME"/rish*.dex || true
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
run_rish() {
|
||||
local out
|
||||
out="$(rish -c "$1" 2>&1)" || {
|
||||
printf "%s\n" "$out"
|
||||
return 1
|
||||
}
|
||||
printf "%s\n" "$out"
|
||||
return 0
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# LAST STEP: Android 12/13 PPK fix via rish
|
||||
# -------------------------
|
||||
step_ppk_fix_android_12_13() {
|
||||
local major rel sdk
|
||||
major="$(android_major_12_to_13)"
|
||||
rel="${ANDROID_REL:-?}"
|
||||
sdk="${ANDROID_SDK:-?}"
|
||||
|
||||
if [[ -z "$major" ]]; then
|
||||
ok "Android release=$rel sdk=$sdk -> PPK fix not applicable (only Android 12/13)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Android $major (release=$rel sdk=$sdk) -> PPK fix target: max_phantom_processes=256"
|
||||
|
||||
if ! have rish; then
|
||||
install_rish_to_path_if_available || true
|
||||
fi
|
||||
|
||||
if ! have rish; then
|
||||
warn_red "PPK fix could not be applied: rish is not available."
|
||||
warn_red "Start Shizuku, then export 'rish' and the matching .dex into Termux (SAF)."
|
||||
warn_red "Continuing without changing PPK."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Current phantom setting:"
|
||||
local cur
|
||||
cur="$(run_rish "dumpsys activity settings | grep -i phantom || true" || true)"
|
||||
if echo "$cur" | grep -qi "Server is not running"; then
|
||||
warn_red "Shizuku/rish server is not running (or not authorized)."
|
||||
warn_red "Open Shizuku -> start the service -> authorize rish, then re-run:"
|
||||
warn_red " ./0_termux-setup.sh --ppk-only"
|
||||
return 0
|
||||
fi
|
||||
printf "%s\n" "$cur"
|
||||
|
||||
local target=256
|
||||
log "Applying: device_config put activity_manager max_phantom_processes $target"
|
||||
local apply
|
||||
apply="$(run_rish "device_config put activity_manager max_phantom_processes $target" || true)"
|
||||
if echo "$apply" | grep -qi "Server is not running"; then
|
||||
warn_red "Shizuku/rish server is not running (or not authorized)."
|
||||
warn_red "Open Shizuku -> start the service -> authorize rish, then re-run:"
|
||||
warn_red " ./0_termux-setup.sh --ppk-only"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Final phantom setting:"
|
||||
run_rish "dumpsys activity settings | grep -i phantom || true" || true
|
||||
|
||||
ok "PPK fix applied (or re-applied)."
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# Self-check summary
|
||||
# -------------------------
|
||||
self_check() {
|
||||
local rel sdk major
|
||||
rel="${ANDROID_REL:-?}"
|
||||
sdk="${ANDROID_SDK:-?}"
|
||||
major="$(android_major_12_to_13)"
|
||||
|
||||
log "Self-check summary:"
|
||||
log " Android release=$rel sdk=$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 rish_export_available_in_home; then
|
||||
ok " rish export: present in HOME (~/rish, ~/rish*.dex)"
|
||||
else
|
||||
warn " rish export: not found in HOME"
|
||||
fi
|
||||
|
||||
if have rish; then
|
||||
ok " rish: installed in PATH"
|
||||
log " rish -c id:"
|
||||
run_rish "id" | sed 's/^/ /' || true
|
||||
if [[ -n "$major" ]]; then
|
||||
log " phantom setting (via rish):"
|
||||
run_rish "dumpsys activity settings | grep -i phantom || true" | sed 's/^/ /' || true
|
||||
fi
|
||||
else
|
||||
warn " rish: not installed in PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# Main
|
||||
# -------------------------
|
||||
main() {
|
||||
acquire_wakelock
|
||||
step_termux_repo_select_once
|
||||
step_termux_base
|
||||
|
||||
if [[ "$PPK_ONLY" -eq 1 ]]; then
|
||||
step_ppk_fix_android_12_13
|
||||
self_check
|
||||
ok "Done (--ppk-only)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
step_debian_bootstrap_default
|
||||
|
||||
# IMPORTANT: keep this last so users can re-run it quickly.
|
||||
step_ppk_fix_android_12_13
|
||||
|
||||
self_check
|
||||
|
||||
ok "0_termux-setup.sh completed."
|
||||
log "Tip: re-run only the PPK fix: ./0_termux-setup.sh --ppk-only"
|
||||
log "Tip: clean Debian environment: ./0_termux-setup.sh --reset-debian"
|
||||
ok "You can proceed with: proot-distro login debian"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
@ -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 "$@"
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
local.properties
|
||||
|
||||
.gradle/
|
||||
build/
|
||||
*/build/
|
||||
app/release/
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
.externalNativeBuild/
|
||||
*/.externalNativeBuild/
|
||||
.cxx/
|
||||
*/.cxx/
|
||||
|
||||
.DS_Store
|
||||
|
|
@ -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
|
||||
|
|
@ -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*
|
||||
|
|
@ -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.
|
||||
|
|
@ -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'
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 |
|
Before Width: | Height: | Size: 26 KiB |
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 870 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.IIABController" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
|
||||
<item name="android:windowBackground">@color/white</item>
|
||||
<item name="android:textColorPrimary">@color/black</item>
|
||||
|
||||
<item name="sectionBackground">@color/section_body_bg_light</item>
|
||||
<item name="sectionHeaderBackground">@color/section_header_bg</item>
|
||||
</style>
|
||||
<style name="Theme.TransparentQR" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:backgroundDimEnabled">false</item>
|
||||
</style>
|
||||
<style name="PurpleSwitchTheme" parent="">
|
||||
<item name="colorControlActivated">#8A2BE2</item>
|
||||
</style>
|
||||
<style name="CustomTabTextStyle" parent="TextAppearance.Design.Tab">
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textSize">13sp</item>
|
||||
<item name="textAllCaps">true</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* The buildscript block is where you configure the repositories and
|
||||
* dependencies for Gradle itself--meaning, you should not include dependencies
|
||||
* for your modules here. For example, this block includes the Android plugin for
|
||||
* Gradle as a dependency because it provides the additional instructions Gradle
|
||||
* needs to build Android app modules.
|
||||
*/
|
||||
|
||||
buildscript {
|
||||
|
||||
/**
|
||||
* The repositories block configures the repositories Gradle uses to
|
||||
* search or download the dependencies. Gradle pre-configures support for remote
|
||||
* repositories such as JCenter, Maven Central, and Ivy. You can also use local
|
||||
* repositories or define your own remote repositories. The code below defines
|
||||
* JCenter as the repository Gradle should use to look for its dependencies.
|
||||
*/
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
/**
|
||||
* The dependencies block configures the dependencies Gradle needs to use
|
||||
* to build your project. The following line adds Android plugin for Gradle
|
||||
* version 2.3.1 as a classpath dependency.
|
||||
*/
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.4.1'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The allprojects block is where you configure the repositories and
|
||||
* dependencies used by all modules in your project, such as third-party plugins
|
||||
* or libraries. Dependencies that are not required by all the modules in the
|
||||
* project should be configured in module-level build.gradle files. For new
|
||||
* projects, Android Studio configures JCenter as the default repository, but it
|
||||
* does not configure any dependencies.
|
||||
*/
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
A simple and lightweight VPN over socks5 proxy for Android. It is based on a high-performance and low-overhead tun2socks.
|
||||
|
||||
<h2>Features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Redirect TCP connections.</li>
|
||||
<li>Redirect UDP packets. (Fullcone NAT, UDP in UDP/TCP)</li>
|
||||
<li>Simple username/password authentication.</li>
|
||||
<li>Specifying DNS addresses.</li>
|
||||
<li>IPv4/IPv6 dual stack.</li>
|
||||
<li>Global/per-App modes.</li>
|
||||
</ul>
|
||||
|
Before Width: | Height: | Size: 443 KiB |
|
|
@ -1 +0,0 @@
|
|||
A simple and lightweight VPN over socks5 proxy (tun2socks)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
Простое и лёгкое VPN-решение поверх Socks5 прокси для Android. Основано на высокопроизводительном и малозатратном tun2socks.
|
||||
|
||||
<h2>Особенности</h2>
|
||||
|
||||
<ul>
|
||||
<li>Перенаправление TCP-соединений.</li>
|
||||
<li>Перенаправление UDP-пакетов. (Fullcone NAT, UDP внутри UDP/TCP)</li>
|
||||
<li>Простая аутентификация по имени пользователя и паролю.</li>
|
||||
<li>Указание адресов DNS.</li>
|
||||
<li>Поддержка двойного стека IPv4/IPv6.</li>
|
||||
<li>Глобальный режим и режим для отдельных приложений.</li>
|
||||
</ul>
|
||||
|
|
@ -1 +0,0 @@
|
|||
Простое и лёгкое VPN поверх Socks5 прокси (tun2socks)
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||