Compare commits
No commits in common. "main" and "improve_arm64_mp" have entirely different histories.
main
...
improve_ar
|
|
@ -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,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,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,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 28
|
||||
versionName "v0.1.32beta"
|
||||
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": 28,
|
||||
"versionName": "v0.1.32beta",
|
||||
"outputFile": "org.iiab.controller-v0.1.32beta-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File",
|
||||
"baselineProfiles": [
|
||||
{
|
||||
"minApi": 28,
|
||||
"maxApi": 30,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/1/org.iiab.controller-v0.1.32beta-release.dm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"minApi": 31,
|
||||
"maxApi": 2147483647,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/0/org.iiab.controller-v0.1.32beta-release.dm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"minSdkVersionForDexing": 24
|
||||
}
|
||||
|
|
@ -1,96 +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" />
|
||||
</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">
|
||||
<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" />
|
||||
</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,225 +0,0 @@
|
|||
/*
|
||||
============================================================================
|
||||
Name : AppListActivity.java
|
||||
Author : hev <r@hev.cc>
|
||||
Copyright : Copyright (c) 2025 xyz
|
||||
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,84 +0,0 @@
|
|||
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,70 +0,0 @@
|
|||
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,99 +0,0 @@
|
|||
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;
|
||||
|
||||
// We pass a Callback so the Dashboard can tell MainActivity to start/stop the VPN
|
||||
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 (No wrench icons needed!)
|
||||
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) {
|
||||
// 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,222 +0,0 @@
|
|||
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,84 +0,0 @@
|
|||
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,238 +0,0 @@
|
|||
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,230 +0,0 @@
|
|||
/*
|
||||
============================================================================
|
||||
Name : Preferences.java
|
||||
Author : hev <r@hev.cc>
|
||||
Copyright : Copyright (c) 2023 xyz
|
||||
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,147 +0,0 @@
|
|||
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,184 +0,0 @@
|
|||
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,40 +0,0 @@
|
|||
/*
|
||||
============================================================================
|
||||
Name : ServiceReceiver.java
|
||||
Author : hev <r@hev.cc>
|
||||
Copyright : Copyright (c) 2023 xyz
|
||||
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,245 +0,0 @@
|
|||
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.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, switchVpn, switchBattery;
|
||||
private Button btnContinue;
|
||||
private Button btnManageAll;
|
||||
private Button btnTermuxOverlay;
|
||||
private Button btnManageTermux;
|
||||
|
||||
private ActivityResultLauncher<String> requestPermissionLauncher;
|
||||
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);
|
||||
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);
|
||||
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 -> {
|
||||
// Save flag so we don't show this screen again
|
||||
SharedPreferences prefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean("setup_complete", true).apply();
|
||||
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupLaunchers() {
|
||||
requestPermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
isGranted -> 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);
|
||||
});
|
||||
|
||||
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 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 vpn = hasVpnPermission();
|
||||
boolean battery = hasBatteryPermission();
|
||||
|
||||
switchNotif.setChecked(notif);
|
||||
switchTermux.setChecked(termux);
|
||||
switchVpn.setChecked(vpn);
|
||||
switchBattery.setChecked(battery);
|
||||
|
||||
boolean allGranted = termux && 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,341 +0,0 @@
|
|||
/*
|
||||
============================================================================
|
||||
Name : TProxyService.java
|
||||
Author : hev <r@hev.cc>
|
||||
Copyright : Copyright (c) 2024 xyz
|
||||
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,33 +0,0 @@
|
|||
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,68 +0,0 @@
|
|||
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,146 +0,0 @@
|
|||
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="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,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,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,144 +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_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="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="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="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_manage_termux"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="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,508 +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">
|
||||
|
||||
<!-- Custom Header/Toolbar -->
|
||||
<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="10dp"
|
||||
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="10dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:tint="#FFFFFF" />
|
||||
|
||||
<!-- Triple Toggle Theme ImageButton -->
|
||||
<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="10dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:tint="#FFFFFF" />
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<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="#1A1A1A"
|
||||
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="Wi-Fi"
|
||||
android:textColor="#FFFFFF"
|
||||
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="Hotspot"
|
||||
android:textColor="#FFFFFF"
|
||||
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="Tunnel"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- VPN Control Section -->
|
||||
<Button
|
||||
android:id="@+id/control"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
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:text="@string/vpn_description"
|
||||
android:textSize="13sp"
|
||||
android:gravity="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<!-- Local WebView -->
|
||||
<Button
|
||||
android:id="@+id/btnBrowseContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
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:padding="12dp"
|
||||
android:background="?attr/sectionHeaderBackground"
|
||||
android:textColor="#FFFFFF"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_marginTop="20dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/config_layout"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/sectionBackground">
|
||||
|
||||
<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="Maintenance Mode"
|
||||
android:checked="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/maintenance_warning"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Disable Safe Pocket Web in order to modify"
|
||||
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>
|
||||
|
||||
<!-- HR above Watchdog -->
|
||||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Watchdog Control Section -->
|
||||
<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="16dp"/>
|
||||
|
||||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginVertical="20dp"/>
|
||||
|
||||
<!-- Log Section (Collapsible) -->
|
||||
<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:padding="10dp"
|
||||
android:background="?attr/sectionHeaderBackground"
|
||||
android:textColor="#FFFFFF"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
|
||||
<!-- Log Warnings (Growth rate warning) -->
|
||||
<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" />
|
||||
|
||||
<!-- Loading Indicator for Logs -->
|
||||
<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"/>
|
||||
|
||||
<!-- Log Size Indicator -->
|
||||
<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" />
|
||||
|
||||
<!-- Log Actions Bar -->
|
||||
<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>
|
||||
|
||||
<!-- Version Footer -->
|
||||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginTop="32dp" android:layout_marginBottom="8dp"/>
|
||||
<TextView
|
||||
android:id="@+id/version_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="v0.1.17alpha"
|
||||
android:textColor="@color/text_muted"
|
||||
android:textSize="10sp"
|
||||
android:paddingBottom="16dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</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,155 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- General App -->
|
||||
<string name="app_name">IIAB-oA Controller</string>
|
||||
<string name="default_version">v0.1.x</string>
|
||||
<string name="save">Guardar</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="saved_toast">Guardado</string>
|
||||
<string name="settings_saved">Ajustes Guardados</string>
|
||||
<string name="fix_action">CORREGIR</string>
|
||||
<string name="configuration_label">Configuración</string>
|
||||
<string name="advanced_settings_label">Ajustes del Túnel</string>
|
||||
<string name="connection_log_label">Log de Conexión</string>
|
||||
<string name="settings_label">AJUSTES</string>
|
||||
|
||||
<!-- SetupActivity -->
|
||||
<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_perm_notifications">Notificaciones Push</string>
|
||||
<string name="setup_perm_termux">Ejecución de Termux</string>
|
||||
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
|
||||
<string name="setup_perm_battery">Desactivar Optimización de Batería</string>
|
||||
<string name="setup_continue">Continuar</string>
|
||||
<string name="revoke_permission_warning">Para revocar permisos, debe hacerlo desde los ajustes del sistema.</string>
|
||||
<string name="termux_not_installed_error">Termux no está instalado o el dispositivo no es compatible.</string>
|
||||
<string name="termux_not_installed">Termux no está instalado.</string>
|
||||
|
||||
<!-- VPN / Socks -->
|
||||
<string name="control_enable">Activar Safe Pocket Web</string>
|
||||
<string name="control_disable">Desactivar Safe Pocket Web</string>
|
||||
<string name="vpn_description">Habilite URLs amigables. Bloquee las amenazas.</string>
|
||||
<string name="socks_addr">Dirección Socks:</string>
|
||||
<string name="socks_udp_addr">Dirección UDP Socks:</string>
|
||||
<string name="socks_port">Puerto Socks:</string>
|
||||
<string name="socks_user">Usuario Socks:</string>
|
||||
<string name="socks_pass">Contraseña Socks:</string>
|
||||
<string name="dns_ipv4">DNS IPv4:</string>
|
||||
<string name="dns_ipv6">DNS IPv6:</string>
|
||||
<string name="udp_in_tcp">Relé UDP sobre TCP</string>
|
||||
<string name="remote_dns">DNS Remoto</string>
|
||||
<string name="ipv4">IPv4</string>
|
||||
<string name="ipv6">IPv6</string>
|
||||
<string name="global">Global</string>
|
||||
<string name="apps">Aplicaciones</string>
|
||||
<string name="vpn_stopping">Deteniendo VPN...</string>
|
||||
<string name="vpn_starting">Iniciando VPN...</string>
|
||||
<string name="user_initiated_conn">Conexión iniciada por el usuario</string>
|
||||
<string name="vpn_permission_granted">Permiso de VPN concedido. Conectando...</string>
|
||||
<string name="tproxy_channel_name">socks5</string>
|
||||
|
||||
<!-- WatchdogService / IIABWatchdog -->
|
||||
<string name="watchdog_enable">Activar\nWatchdog Maestro</string>
|
||||
<string name="watchdog_disable">Desactivar\nWatchdog Maestro</string>
|
||||
<string name="watchdog_description">Protege Termux del modo Doze y mantiene el Wi-Fi activo.</string>
|
||||
<string name="watchdog_stopped">Watchdog Detenido</string>
|
||||
<string name="watchdog_started">Watchdog Iniciado</string>
|
||||
<string name="watchdog_channel_name">Servicio IIAB Watchdog</string>
|
||||
<string name="watchdog_channel_desc">Asegura que los servicios permanezcan activos cuando la pantalla está apagada.</string>
|
||||
<string name="watchdog_notif_title">IIAB Watchdog Activo</string>
|
||||
<string name="watchdog_notif_text">Protegiendo el entorno Termux...</string>
|
||||
<string name="syncing_watchdog">Sincronizando estado del Watchdog. Activado: %b</string>
|
||||
<string name="watchdog_thread_started">Watchdog Thread: Bucle iniciado</string>
|
||||
<string name="watchdog_thread_interrupted">Watchdog Thread: Interrumpido, deteniéndose...</string>
|
||||
<string name="watchdog_thread_error">Watchdog Thread: Error en el bucle</string>
|
||||
<string name="watchdog_thread_ended">Watchdog Thread: Bucle finalizado</string>
|
||||
<string name="cpu_wakelock_acquired">CPU WakeLock adquirido bajo protección VPN</string>
|
||||
<string name="wifi_lock_acquired">Wi-Fi Lock adquirido bajo protección VPN</string>
|
||||
<string name="error_acquiring_locks">Error al adquirir bloqueos</string>
|
||||
<string name="cpu_wakelock_released">CPU WakeLock liberado</string>
|
||||
<string name="wifi_lock_released">Wi-Fi Lock liberado</string>
|
||||
|
||||
<!-- Pulse / Heartbeat -->
|
||||
<string name="pulse_stimulating">Pulso: Estimulando Termux...</string>
|
||||
<string name="critical_os_blocked">CRÍTICO: El SO bloqueó el estímulo a Termux (SecurityException).</string>
|
||||
<string name="ping_ok">PING 8085: OK</string>
|
||||
<string name="ping_fail">PING 8085: FALLO (%s)</string>
|
||||
<string name="session_started">SESIÓN DE LATIDO INICIADA</string>
|
||||
<string name="session_stopped">SESIÓN DE LATIDO DETENIDA</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="unexpected_error_termux">Error inesperado enviando intent a Termux</string>
|
||||
<string name="pulse_error_log">Error de Pulso: %s</string>
|
||||
<string name="maintenance_write_failed">Fallo en la escritura de mantenimiento</string>
|
||||
<string name="failed_write_blackbox">Fallo al escribir en BlackBox</string>
|
||||
<string name="recovery_pulse_received">Pulso de recuperación recibido del sistema. Forzando VPN...</string>
|
||||
|
||||
<!-- TermuxCallbackReceiver / Operations -->
|
||||
<string name="termux_stimulus_ok">[Termux] Estímulo OK (exit 0)</string>
|
||||
<string name="termux_pulse_error">[Termux] Error de pulso (exit %1$d): %2$s</string>
|
||||
<string name="server_timeout_warning">Advertencia: Tiempo de espera agotado en la transición de estado del servidor.</string>
|
||||
<string name="server_booting">Iniciando...</string>
|
||||
<string name="server_shutting_down">Apagando...</string>
|
||||
<string name="failed_termux_intent">CRÍTICO: Fallo en el Intent de Termux: %s</string>
|
||||
<string name="sent_to_termux">Enviado a Termux: %s</string>
|
||||
<string name="maintenance_mode_enabled">Modo de mantenimiento activado: Termux tiene acceso directo a Internet</string>
|
||||
<string name="stop_server">🛑 Detener Servidor</string>
|
||||
<string name="launch_server">🚀 Iniciar Servidor</string>
|
||||
<string name="termux_perm_granted">Permiso de Termux concedido</string>
|
||||
<string name="termux_perm_denied">Permiso de Termux denegado</string>
|
||||
<string name="notif_perm_granted">Permiso de notificaciones concedido</string>
|
||||
<string name="notif_perm_denied">Permiso de notificaciones denegado</string>
|
||||
|
||||
<!-- Logs -->
|
||||
<string name="log_reset_confirm_title">¿Reiniciar historial de log?</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_warning_rapid_growth">El archivo de log está creciendo demasiado rápido, verifique si algo está fallando</string>
|
||||
<string name="reset_log">Reiniciar Log</string>
|
||||
<string name="copy_all">Copiar Todo</string>
|
||||
<string name="log_reset_log">Log reiniciado</string>
|
||||
<string name="log_reset_user">Log reiniciado por el usuario</string>
|
||||
<string name="log_copied_toast">Log copiado al portapapeles</string>
|
||||
<string name="log_cleared_toast">Log borrado</string>
|
||||
<string name="failed_reset_log">Fallo al reiniciar el log: %s</string>
|
||||
<string name="log_size_format">Tamaño: %1$s / 10MB</string>
|
||||
<string name="log_size_bytes">%d B</string>
|
||||
<string name="log_size_kb">%.1f KB</string>
|
||||
<string name="log_size_mb">%.2f MB</string>
|
||||
<string name="no_blackbox_found">--- No se encontró el archivo BlackBox ---</string>
|
||||
<string name="loading_history">--- Cargando Historial ---</string>
|
||||
<string name="error_reading_history">Error al leer el historial: %s</string>
|
||||
<string name="end_of_history">--- Fin del Historial ---</string>
|
||||
|
||||
<!-- Battery Optimizations -->
|
||||
<string name="battery_opt_title">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="go_to_settings">Ir a Ajustes</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_xiaomi_extra">\n\nXiaomi detectado: Establezca el ahorro de batería a \'Sin restricciones\' en los ajustes.</string>
|
||||
<string name="battery_opt_denied">Para que la app funcione al 100%, desactive la optimización de batería.</string>
|
||||
|
||||
<!-- UI / Misc -->
|
||||
<string name="browse_content">🚀 Explorar Contenido</string>
|
||||
<string name="system_ready">Sistema listo...\n</string>
|
||||
<string name="app_started">Aplicación Iniciada</string>
|
||||
<string name="label_separator_down">▼ %s</string>
|
||||
<string name="label_separator_up">▶ %s</string>
|
||||
<string name="qr_error_no_server">Inicie el servidor para compartir contenido a través de la red.</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_title_wifi">Red Wi-Fi</string>
|
||||
<string name="qr_title_hotspot">Red Hotspot</string>
|
||||
<string name="qr_flip_network">Cambiar Red</string>
|
||||
|
||||
<!-- Authentication / Security -->
|
||||
<string name="unlock_watchdog_title">Desbloquear Watchdog Maestro</string>
|
||||
<string name="unlock_watchdog_subtitle">Se requiere autenticación para detener la protección de Termux</string>
|
||||
<string name="auth_success_disconnect">Autenticación exitosa. Desconectando...</string>
|
||||
<string name="auth_required_title">Autenticación requerida</string>
|
||||
<string name="auth_required_subtitle">Autentíquese para desactivar el entorno seguro</string>
|
||||
<string name="security_required_title">Seguridad Requerida</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>
|
||||
|
||||
<!-- VPN Recovery Service -->
|
||||
<string name="recovery_channel_name">Recuperación VPN</string>
|
||||
<string name="recovery_notif_title">Safe Pocket Web Interrumpido</string>
|
||||
<string name="recovery_notif_text">Toque para restaurar el entorno seguro inmediatamente.</string>
|
||||
</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,155 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- General App -->
|
||||
<string name="app_name">IIAB-oA Controller</string>
|
||||
<string name="default_version">v0.1.x</string>
|
||||
<string name="save">Сохранить</string>
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="saved_toast">Сохранено</string>
|
||||
<string name="settings_saved">Настройки сохранены</string>
|
||||
<string name="fix_action">ИСПРАВИТЬ</string>
|
||||
<string name="configuration_label">Конфигурация</string>
|
||||
<string name="advanced_settings_label">Настройки туннеля</string>
|
||||
<string name="connection_log_label">Журнал подключений</string>
|
||||
<string name="settings_label">НАСТРОЙКИ</string>
|
||||
|
||||
<!-- SetupActivity -->
|
||||
<string name="setup_title">Начальная настройка</string>
|
||||
<string name="setup_welcome">Добро пожаловать в мастер настройки %1$s.\n\nДля правильной работы нам нужны следующие разрешения:</string>
|
||||
<string name="setup_perm_notifications">Push-уведомления</string>
|
||||
<string name="setup_perm_termux">Выполнение Termux</string>
|
||||
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
|
||||
<string name="setup_perm_battery">Отключить оптимизацию батареи</string>
|
||||
<string name="setup_continue">Продолжить</string>
|
||||
<string name="revoke_permission_warning">Чтобы отозвать разрешения, это нужно сделать в настройках системы.</string>
|
||||
<string name="termux_not_installed_error">Termux не установлен или устройство не поддерживается.</string>
|
||||
<string name="termux_not_installed">Termux не установлен.</string>
|
||||
|
||||
<!-- VPN / Socks -->
|
||||
<string name="control_enable">Включить Safe Pocket Web</string>
|
||||
<string name="control_disable">Выключить Safe Pocket Web</string>
|
||||
<string name="vpn_description">Включить дружественные URL. Блокировать угрозы.</string>
|
||||
<string name="socks_addr">Адрес Socks:</string>
|
||||
<string name="socks_udp_addr">UDP адрес Socks:</string>
|
||||
<string name="socks_port">Порт Socks:</string>
|
||||
<string name="socks_user">Имя пользователя Socks:</string>
|
||||
<string name="socks_pass">Пароль Socks:</string>
|
||||
<string name="dns_ipv4">DNS IPv4:</string>
|
||||
<string name="dns_ipv6">DNS IPv6:</string>
|
||||
<string name="udp_in_tcp">UDP ретрансляция через TCP</string>
|
||||
<string name="remote_dns">Удаленный DNS</string>
|
||||
<string name="ipv4">IPv4</string>
|
||||
<string name="ipv6">IPv6</string>
|
||||
<string name="global">Глобально</string>
|
||||
<string name="apps">Приложения</string>
|
||||
<string name="vpn_stopping">Остановка VPN...</string>
|
||||
<string name="vpn_starting">Запуск VPN...</string>
|
||||
<string name="user_initiated_conn">Соединение инициировано пользователем</string>
|
||||
<string name="vpn_permission_granted">Разрешение VPN получено. Подключение...</string>
|
||||
<string name="tproxy_channel_name">socks5</string>
|
||||
|
||||
<!-- WatchdogService / IIABWatchdog -->
|
||||
<string name="watchdog_enable">Включить\nМастер Watchdog</string>
|
||||
<string name="watchdog_disable">Выключить\nМастер Watchdog</string>
|
||||
<string name="watchdog_description">Защищает Termux от режима Doze и поддерживает Wi-Fi активным.</string>
|
||||
<string name="watchdog_stopped">Watchdog остановлен</string>
|
||||
<string name="watchdog_started">Watchdog запущен</string>
|
||||
<string name="watchdog_channel_name">Служба IIAB Watchdog</string>
|
||||
<string name="watchdog_channel_desc">Гарантирует, что службы остаются активными при выключенном экране.</string>
|
||||
<string name="watchdog_notif_title">IIAB Watchdog активен</string>
|
||||
<string name="watchdog_notif_text">Защита окружения Termux...</string>
|
||||
<string name="syncing_watchdog">Синхронизация состояния Watchdog. Включено: %b</string>
|
||||
<string name="watchdog_thread_started">Watchdog Thread: Цикл запущен</string>
|
||||
<string name="watchdog_thread_interrupted">Watchdog Thread: Прервано, остановка...</string>
|
||||
<string name="watchdog_thread_error">Watchdog Thread: Ошибка в цикле</string>
|
||||
<string name="watchdog_thread_ended">Watchdog Thread: Цикл завершен</string>
|
||||
<string name="cpu_wakelock_acquired">CPU WakeLock получен под защитой VPN</string>
|
||||
<string name="wifi_lock_acquired">Wi-Fi Lock получен под защитой VPN</string>
|
||||
<string name="error_acquiring_locks">Ошибка получения блокировок</string>
|
||||
<string name="cpu_wakelock_released">CPU WakeLock освобожден</string>
|
||||
<string name="wifi_lock_released">Wi-Fi Lock освобожден</string>
|
||||
|
||||
<!-- Pulse / Heartbeat -->
|
||||
<string name="pulse_stimulating">Пульс: Стимуляция Termux...</string>
|
||||
<string name="critical_os_blocked">КРИТИЧЕСКАЯ ОШИБКА: ОС заблокировала стимуляцию Termux (SecurityException).</string>
|
||||
<string name="ping_ok">PING 8085: OK</string>
|
||||
<string name="ping_fail">PING 8085: ОШИБКА (%s)</string>
|
||||
<string name="session_started">СЕАНС СЕРДЦЕБИЕНИЯ ЗАПУЩЕН</string>
|
||||
<string name="session_stopped">СЕАНС СЕРДЦЕБИЕНИЯ ОСТАНОВЛЕН</string>
|
||||
<string name="permission_denied_log">В доступе отказано: убедитесь, что в манифесте есть RUN_COMMAND и приложение не ограничено.</string>
|
||||
<string name="unexpected_error_termux">Непредвиденная ошибка при отправке intent в Termux</string>
|
||||
<string name="pulse_error_log">Ошибка пульса: %s</string>
|
||||
<string name="maintenance_write_failed">Ошибка записи обслуживания</string>
|
||||
<string name="failed_write_blackbox">Ошибка записи в BlackBox</string>
|
||||
<string name="recovery_pulse_received">Пульс восстановления получен от системы. Принудительный VPN...</string>
|
||||
|
||||
<!-- TermuxCallbackReceiver / Operations -->
|
||||
<string name="termux_stimulus_ok">[Termux] Стимул OK (exit 0)</string>
|
||||
<string name="termux_pulse_error">[Termux] Ошибка пульса (exit %1$d): %2$s</string>
|
||||
<string name="server_timeout_warning">Предупреждение: Время ожидания перехода состояния сервера истекло.</string>
|
||||
<string name="server_booting">Загрузка...</string>
|
||||
<string name="server_shutting_down">Выключение...</string>
|
||||
<string name="failed_termux_intent">КРИТИЧЕСКАЯ ОШИБКА: Ошибка Intent Termux: %s</string>
|
||||
<string name="sent_to_termux">Отправлено в Termux: %s</string>
|
||||
<string name="maintenance_mode_enabled">Режим обслуживания включен: Termux имеет прямой доступ в Интернет</string>
|
||||
<string name="stop_server">🛑 Остановить сервер</string>
|
||||
<string name="launch_server">🚀 Запустить сервер</string>
|
||||
<string name="termux_perm_granted">Разрешение Termux предоставлено</string>
|
||||
<string name="termux_perm_denied">Разрешение Termux отклонено</string>
|
||||
<string name="notif_perm_granted">Разрешение на уведомления предоставлено</string>
|
||||
<string name="notif_perm_denied">Разрешение на уведомления отклонено</string>
|
||||
|
||||
<!-- Logs -->
|
||||
<string name="log_reset_confirm_title">Сбросить историю журнала?</string>
|
||||
<string name="log_reset_confirm_msg">Это безвозвратно удалит все сохраненные журналы подключений. Это действие нельзя отменить.</string>
|
||||
<string name="log_warning_rapid_growth">Файл журнала растет слишком быстро, возможно, стоит проверить, нет ли ошибки</string>
|
||||
<string name="reset_log">Сбросить журнал</string>
|
||||
<string name="copy_all">Скопировать все</string>
|
||||
<string name="log_reset_log">Журнал сброшен</string>
|
||||
<string name="log_reset_user">Журнал сброшен пользователем</string>
|
||||
<string name="log_copied_toast">Журнал скопирован в буфер обмена</string>
|
||||
<string name="log_cleared_toast">Журнал очищен</string>
|
||||
<string name="failed_reset_log">Ошибка сброса журнала: %s</string>
|
||||
<string name="log_size_format">Размер: %1$s / 10MB</string>
|
||||
<string name="log_size_bytes">%d B</string>
|
||||
<string name="log_size_kb">%.1f KB</string>
|
||||
<string name="log_size_mb">%.2f MB</string>
|
||||
<string name="no_blackbox_found">--- Файл BlackBox не найден ---</string>
|
||||
<string name="loading_history">--- Загрузка истории ---</string>
|
||||
<string name="error_reading_history">Ошибка чтения истории: %s</string>
|
||||
<string name="end_of_history">--- Конец истории ---</string>
|
||||
|
||||
<!-- Battery Optimizations -->
|
||||
<string name="battery_opt_title">Оптимизация батареи</string>
|
||||
<string name="battery_opt_msg">Для надежной работы Watchdog, пожалуйста, отключите оптимизацию батареи для этого приложения.</string>
|
||||
<string name="go_to_settings">Перейти к настройкам</string>
|
||||
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme обнаружен: Пожалуйста, убедитесь, что вы включили "Разрешить фоновую активность" в настройках этого приложения.</string>
|
||||
<string name="battery_opt_xiaomi_extra">\n\nXiaomi обнаружен: Пожалуйста, установите экономию заряда батареи на "Без ограничений" в настройках.</string>
|
||||
<string name="battery_opt_denied">Для 100% работы приложения, пожалуйста, отключите оптимизацию батареи.</string>
|
||||
|
||||
<!-- UI / Misc -->
|
||||
<string name="browse_content">🚀 Исследовать контент</string>
|
||||
<string name="system_ready">Система готова...\n</string>
|
||||
<string name="app_started">Приложение запущено</string>
|
||||
<string name="label_separator_down">▼ %s</string>
|
||||
<string name="label_separator_up">▶ %s</string>
|
||||
<string name="qr_error_no_server">Запустите сервер, чтобы поделиться контентом по сети.</string>
|
||||
<string name="qr_error_no_network">Включите Wi-Fi или точку доступа, чтобы поделиться контентом по сети.</string>
|
||||
<string name="qr_title_wifi">Сеть Wi-Fi</string>
|
||||
<string name="qr_title_hotspot">Сеть точки доступа</string>
|
||||
<string name="qr_flip_network">Переключить сеть</string>
|
||||
|
||||
<!-- Authentication / Security -->
|
||||
<string name="unlock_watchdog_title">Разблокировать Мастер Watchdog</string>
|
||||
<string name="unlock_watchdog_subtitle">Требуется аутентификация для остановки защиты Termux</string>
|
||||
<string name="auth_success_disconnect">Аутентификация успешна. Отключение...</string>
|
||||
<string name="auth_required_title">Требуется аутентификация</string>
|
||||
<string name="auth_required_subtitle">Пройдите аутентификацию, чтобы отключить безопасное окружение</string>
|
||||
<string name="security_required_title">Требуется безопасность</string>
|
||||
<string name="security_required_msg">Перед активацией безопасного окружения необходимо установить PIN-код, графический ключ или отпечаток пальца на устройстве.</string>
|
||||
|
||||
<!-- VPN Recovery Service -->
|
||||
<string name="recovery_channel_name">Восстановление VPN</string>
|
||||
<string name="recovery_notif_title">Safe Pocket Web прерван</string>
|
||||
<string name="recovery_notif_text">Нажмите, чтобы немедленно восстановить безопасное окружение.</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,31 +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>
|
||||
</resources>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="server_cool_off_duration_ms">60000</integer>
|
||||
</resources>
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- General App -->
|
||||
<string name="app_name">IIAB-oA Controller</string>
|
||||
<string name="default_version">v0.1.x</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="saved_toast">Saved</string>
|
||||
<string name="settings_saved">Settings Saved</string>
|
||||
<string name="fix_action">FIX</string>
|
||||
<string name="configuration_label">Configuration</string>
|
||||
<string name="advanced_settings_label">Tunnel Settings</string>
|
||||
<string name="connection_log_label">Connection Log</string>
|
||||
<string name="settings_label">SETTINGS</string>
|
||||
|
||||
<!-- SetupActivity -->
|
||||
<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_perm_notifications">Push Notifications</string>
|
||||
<string name="setup_perm_termux">Termux Execution</string>
|
||||
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
|
||||
<string name="setup_perm_battery">Disable Battery Optimization</string>
|
||||
<string name="setup_continue">Continue</string>
|
||||
<string name="revoke_permission_warning">To revoke permissions, you must do it from system settings.</string>
|
||||
<string name="termux_not_installed_error">Termux is not installed or device not supported.</string>
|
||||
<string name="termux_not_installed">Termux is not installed.</string>
|
||||
|
||||
<!-- VPN / Socks -->
|
||||
<string name="control_enable">Enable Safe Pocket Web</string>
|
||||
<string name="control_disable">Disable Safe Pocket Web</string>
|
||||
<string name="vpn_description">Enable friendly URLs. Lock out the threats.</string>
|
||||
<string name="socks_addr">Socks Address:</string>
|
||||
<string name="socks_udp_addr">Socks UDP Address:</string>
|
||||
<string name="socks_port">Socks Port:</string>
|
||||
<string name="socks_user">Socks Username:</string>
|
||||
<string name="socks_pass">Socks Password:</string>
|
||||
<string name="dns_ipv4">DNS IPv4:</string>
|
||||
<string name="dns_ipv6">DNS IPv6:</string>
|
||||
<string name="udp_in_tcp">UDP relay over TCP</string>
|
||||
<string name="remote_dns">Remote DNS</string>
|
||||
<string name="ipv4">IPv4</string>
|
||||
<string name="ipv6">IPv6</string>
|
||||
<string name="global">Global</string>
|
||||
<string name="apps">Apps</string>
|
||||
<string name="vpn_stopping">VPN Stopping...</string>
|
||||
<string name="vpn_starting">VPN Starting...</string>
|
||||
<string name="user_initiated_conn">User initiated connection</string>
|
||||
<string name="vpn_permission_granted">VPN Permission Granted. Connecting...</string>
|
||||
<string name="tproxy_channel_name">socks5</string>
|
||||
|
||||
<!-- WatchdogService / IIABWatchdog -->
|
||||
<string name="watchdog_enable">Enable\nMaster Watchdog</string>
|
||||
<string name="watchdog_disable">Disable\nMaster Watchdog</string>
|
||||
<string name="watchdog_description">Protects Termux from Doze mode and keeps Wi-Fi active.</string>
|
||||
<string name="watchdog_stopped">Watchdog Stopped</string>
|
||||
<string name="watchdog_started">Watchdog Started</string>
|
||||
<string name="watchdog_channel_name">IIAB Watchdog Service</string>
|
||||
<string name="watchdog_channel_desc">Ensures services remain active when screen is off.</string>
|
||||
<string name="watchdog_notif_title">IIAB Watchdog Active</string>
|
||||
<string name="watchdog_notif_text">Protecting Termux environment...</string>
|
||||
<string name="syncing_watchdog">Syncing Watchdog state. Enabled: %b</string>
|
||||
<string name="watchdog_thread_started">Watchdog Thread: Started loop</string>
|
||||
<string name="watchdog_thread_interrupted">Watchdog Thread: Interrupted, stopping...</string>
|
||||
<string name="watchdog_thread_error">Watchdog Thread: Error in loop</string>
|
||||
<string name="watchdog_thread_ended">Watchdog Thread: Loop ended</string>
|
||||
<string name="cpu_wakelock_acquired">CPU WakeLock acquired under VPN shield</string>
|
||||
<string name="wifi_lock_acquired">Wi-Fi Lock acquired under VPN shield</string>
|
||||
<string name="error_acquiring_locks">Error acquiring locks</string>
|
||||
<string name="cpu_wakelock_released">CPU WakeLock released</string>
|
||||
<string name="wifi_lock_released">Wi-Fi Lock released</string>
|
||||
|
||||
<!-- Pulse / Heartbeat -->
|
||||
<string name="pulse_stimulating">Pulse: Stimulating Termux...</string>
|
||||
<string name="critical_os_blocked">CRITICAL: OS blocked Termux stimulus (SecurityException).</string>
|
||||
<string name="ping_ok">PING 8085: OK</string>
|
||||
<string name="ping_fail">PING 8085: FAIL (%s)</string>
|
||||
<string name="session_started">HEARTBEAT SESSION STARTED</string>
|
||||
<string name="session_stopped">HEARTBEAT SESSION STOPPED</string>
|
||||
<string name="permission_denied_log">Permission Denied: Ensure manifest has RUN_COMMAND and app is not restricted.</string>
|
||||
<string name="unexpected_error_termux">Unexpected error sending intent to Termux</string>
|
||||
<string name="pulse_error_log">Pulse Error: %s</string>
|
||||
<string name="maintenance_write_failed">Maintenance write failed</string>
|
||||
<string name="failed_write_blackbox">Failed to write to BlackBox</string>
|
||||
<string name="recovery_pulse_received">Recovery Pulse Received from System. Enforcing VPN...</string>
|
||||
|
||||
<!-- TermuxCallbackReceiver / Operations -->
|
||||
<string name="termux_stimulus_ok">[Termux] Stimulus OK (exit 0)</string>
|
||||
<string name="termux_pulse_error">[Termux] Pulse Error (exit %1$d): %2$s</string>
|
||||
<string name="server_timeout_warning">Warning: Server state transition timed out.</string>
|
||||
<string name="server_booting">Booting...</string>
|
||||
<string name="server_shutting_down">Shutting down...</string>
|
||||
<string name="failed_termux_intent">CRITICAL: Failed Termux Intent: %s</string>
|
||||
<string name="sent_to_termux">Sent to Termux: %s</string>
|
||||
<string name="maintenance_mode_enabled">Maintenance mode enabled: Termux has direct Internet access</string>
|
||||
<string name="stop_server">🛑 Stop Server</string>
|
||||
<string name="launch_server">🚀 Launch Server</string>
|
||||
<string name="termux_perm_granted">Termux permission granted</string>
|
||||
<string name="termux_perm_denied">Termux permission denied</string>
|
||||
<string name="notif_perm_granted">Notification permission granted</string>
|
||||
<string name="notif_perm_denied">Notification permission denied</string>
|
||||
|
||||
<!-- Logs -->
|
||||
<string name="log_reset_confirm_title">Reset Log History?</string>
|
||||
<string name="log_reset_confirm_msg">This will permanently delete all stored connection logs. This action cannot be undone.</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="reset_log">Reset Log</string>
|
||||
<string name="copy_all">Copy All</string>
|
||||
<string name="log_reset_log">Log reset</string>
|
||||
<string name="log_reset_user">Log reset by user</string>
|
||||
<string name="log_copied_toast">Log copied to clipboard</string>
|
||||
<string name="log_cleared_toast">Log cleared</string>
|
||||
<string name="failed_reset_log">Failed to reset log: %s</string>
|
||||
<string name="log_size_format">Size: %1$s / 10MB</string>
|
||||
<string name="log_size_bytes">%d B</string>
|
||||
<string name="log_size_kb">%.1f KB</string>
|
||||
<string name="log_size_mb">%.2f MB</string>
|
||||
<string name="no_blackbox_found">--- No BlackBox file found ---</string>
|
||||
<string name="loading_history">--- Loading History ---</string>
|
||||
<string name="error_reading_history">Error reading history: %s</string>
|
||||
<string name="end_of_history">--- End of History ---</string>
|
||||
|
||||
<!-- Battery Optimizations -->
|
||||
<string name="battery_opt_title">Battery Optimization</string>
|
||||
<string name="battery_opt_msg">For the Watchdog to work reliably, please disable battery optimizations for this app.</string>
|
||||
<string name="go_to_settings">Go to Settings</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_xiaomi_extra">\n\nXiaomi detected: Please set battery saver to \'No restrictions\' in settings.</string>
|
||||
<string name="battery_opt_denied">For the app to work 100%, please disable battery optimization.</string>
|
||||
|
||||
<!-- UI / Misc -->
|
||||
<string name="browse_content">🚀 Explore Content</string>
|
||||
<string name="system_ready">System ready...\n</string>
|
||||
<string name="app_started">Application Started</string>
|
||||
<string name="label_separator_down">▼ %s</string>
|
||||
<string name="label_separator_up">▶ %s</string>
|
||||
<string name="qr_error_no_server">Launch the server to share content over the network.</string>
|
||||
<string name="qr_error_no_network">Enable Wi-Fi or Hotspot to share content over the network.</string>
|
||||
<string name="qr_title_wifi">Wi-Fi Network</string>
|
||||
<string name="qr_title_hotspot">Hotspot Network</string>
|
||||
<string name="qr_flip_network">Switch Network</string>
|
||||
|
||||
<!-- Authentication / Security -->
|
||||
<string name="unlock_watchdog_title">Unlock Master Watchdog</string>
|
||||
<string name="unlock_watchdog_subtitle">Authentication required to stop Termux protection</string>
|
||||
<string name="auth_success_disconnect">Authentication Success. Disconnecting...</string>
|
||||
<string name="auth_required_title">Authentication required</string>
|
||||
<string name="auth_required_subtitle">Authenticate to disable the secure environment</string>
|
||||
<string name="security_required_title">Security Required</string>
|
||||
<string name="security_required_msg">You must set up a PIN, Pattern, or Fingerprint on your device before enabling the secure environment.</string>
|
||||
|
||||
<!-- VPN Recovery Service -->
|
||||
<string name="recovery_channel_name">VPN Recovery</string>
|
||||
<string name="recovery_notif_title">Safe Pocket Web Interrupted</string>
|
||||
<string name="recovery_notif_text">Tap to restore secure environment immediately.</string>
|
||||
</resources>
|
||||
|
|
@ -1,23 +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>
|
||||
</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
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
|
@ -1 +0,0 @@
|
|||
include ':app'
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
storeFile=debug.keystore
|
||||
keyAlias=androiddebugkey
|
||||
storePassword=android
|
||||
keyPassword=android
|
||||
|
|
@ -7,7 +7,6 @@
|
|||
# --clean Stop+delete+purge target VMs (by BASE-<number>, regardless of COUNT)
|
||||
# Distro selection:
|
||||
# --debian-13 Debian 13 only (sets IMAGE=$DEBIAN13_IMAGE_URL and BASE=deb13)
|
||||
# --ubuntu-release VER Use specific Ubuntu release (e.g., 24.04, 26.04); sets IMAGE and BASE dynamically
|
||||
# --both-distros Run Ubuntu + Debian 13 in parallel: COUNT=N => 2N VMs (default order: interleaved)
|
||||
# --first-ubuntu (with --both-distros) order: all Ubuntu first, then all Debian
|
||||
# --first-debian (with --both-distros) order: all Debian first, then all Ubuntu
|
||||
|
|
@ -47,8 +46,8 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|||
# Source: Debian cloud images live under cloud.debian.org/images/cloud/ ('genericcloud' includes cloud-init).
|
||||
DEBIAN13_IMAGE_URL="${DEBIAN13_IMAGE_URL:-https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-${DPKG_ARCH}.qcow2}"
|
||||
|
||||
IMAGE="${IMAGE:-26.04}"
|
||||
BASE="${BASE:-ubu2604}"
|
||||
IMAGE="${IMAGE:-24.04}"
|
||||
BASE="${BASE:-ubu2404}"
|
||||
# cloud-init controls
|
||||
CLOUD_INIT_FILE="${CLOUD_INIT_FILE:-}"
|
||||
RPIOS_CLOUD_INIT_FILE="${RPIOS_CLOUD_INIT_FILE:-$SCRIPT_DIR/cloud-init-rpios-like-arm64.yaml}"
|
||||
|
|
@ -101,7 +100,6 @@ Aliases:
|
|||
--test-pr N (same as --run-pr N)
|
||||
Image shortcuts:
|
||||
--debian-13 Use Debian 13 cloud image; sets IMAGE=\$DEBIAN13_IMAGE_URL and BASE=deb13
|
||||
--ubuntu-release VER Use specific Ubuntu release (e.g., 24.04, 26.04); sets IMAGE and BASE dynamically
|
||||
--both-distros Run both Ubuntu + Debian 13 (COUNT=N => N + N VMs)
|
||||
--first-ubuntu With --both-distros: run all Ubuntu first, then Debian
|
||||
--first-debian With --both-distros: run all Debian first, then Ubuntu
|
||||
|
|
@ -134,12 +132,6 @@ while [[ $# -gt 0 ]]; do
|
|||
[[ $# -lt 2 ]] && { echo "[ERROR] --cloud-init needs a file path"; exit 2; }
|
||||
CLOUD_INIT_FILE="$2"; shift 2 ;;
|
||||
|
||||
--ubuntu-release)
|
||||
[[ $# -lt 2 ]] && { echo "[ERROR] --ubuntu-release needs a version (e.g. 24.04 or 26.04)"; exit 2; }
|
||||
IMAGE="$2"
|
||||
BASE="ubu${2//./}"
|
||||
shift 2 ;;
|
||||
|
||||
--debian-13)
|
||||
DEBIAN13_ONLY=1
|
||||
IMAGE="$DEBIAN13_IMAGE_URL"
|
||||
|
|
@ -239,9 +231,7 @@ else
|
|||
DEB_BASE="deb13"
|
||||
fi
|
||||
|
||||
LOG_MONTH="$(date +%Y%m)"
|
||||
LOG_RUN="$(date +%Y%m%d_%H%M%S)"
|
||||
LOGROOT="${LOGROOT:-iiab_multipass_runs_${LOG_MONTH}/${LOG_RUN}}"
|
||||
LOGROOT="${LOGROOT:-iiab_multipass_runs_$(date +%Y%m%d)}"
|
||||
mkdir -p "$LOGROOT"
|
||||
echo "[INFO] Logging files stored at: $LOGROOT"
|
||||
|
||||
|
|
@ -356,12 +346,15 @@ clean_targets() {
|
|||
local list
|
||||
list="$(multipass list 2>/dev/null | awk 'NR>1 {print $1}' || true)"
|
||||
|
||||
# Regex modified to catch any Ubuntu base (ubu followed by 4 digits) or the Debian base
|
||||
if [[ "$BOTH_DISTROS" == "1" ]]; then
|
||||
local ubu_re deb_re
|
||||
ubu_re="$(re_escape "$UBU_BASE")"
|
||||
deb_re="$(re_escape "$DEB_BASE")"
|
||||
printf '%s\n' "$list" | grep -E "^(ubu[0-9]{4}|${deb_re})-[0-9]+$" || true
|
||||
printf '%s\n' "$list" | grep -E "^(${ubu_re}|${deb_re})-[0-9]+$" || true
|
||||
else
|
||||
printf '%s\n' "$list" | grep -E "^(ubu[0-9]{4}|deb13|rpios-d13)-[0-9]+$" || true
|
||||
local base_re
|
||||
base_re="$(re_escape "$BASE")"
|
||||
printf '%s\n' "$list" | grep -E "^${base_re}-[0-9]+$" || true
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,212 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# gen_simple_inplace.sh
|
||||
# Moves wheels from a pool to their corresponding PEP 503 directory and generates the index.html files.
|
||||
# Standardizes web permissions (755 dir, 644 files)
|
||||
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
FINAL_REPO=""
|
||||
W_POOL=""
|
||||
ONLY_PKG=""
|
||||
NO_TOP=0
|
||||
DO_VERIFY=0
|
||||
VERIFY_ONLY=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--final-repo|--simple-dir) FINAL_REPO="${2:-}"; shift 2 ;;
|
||||
--w-pool) W_POOL="${2:-}"; shift 2 ;;
|
||||
--pkg) ONLY_PKG="${2:-}"; shift 2 ;;
|
||||
--no-top) NO_TOP=1; shift ;;
|
||||
--verify) DO_VERIFY=1; shift ;;
|
||||
--verify-only) VERIFY_ONLY=1; DO_VERIFY=1; shift ;;
|
||||
-h|--help)
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./simple_builder.sh --final-repo ~/simple/ --w-pool ~/wheel_pool/
|
||||
./simple_builder.sh --final-repo ~/simple/ --pkg cffi
|
||||
./simple_builder.sh --final-repo ~/simple/ --verify
|
||||
|
||||
Options:
|
||||
--final-repo Path to the repository's root directory (e.g., ~/simple/)
|
||||
--w-pool Path to the folder containing new wheels to accommodate (e.g., ~/wheel_pool/)
|
||||
--pkg Update/Verify only a specific package
|
||||
--no-top Do not regenerate the main /simple/index.html file
|
||||
--verify Verify that the href attributes exist and the SHA256 attributes match
|
||||
--verify-only Only verify (does not regenerate any index.html files or move wheels)
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
*) die "Unknown arg: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$FINAL_REPO" ] || die "--final-repo is required"
|
||||
[ -d "$FINAL_REPO" ] || die "It is not a valid directory: $FINAL_REPO"
|
||||
|
||||
# Strict PEP 503 normalization
|
||||
normalize_pkg_name() {
|
||||
echo "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[-_.]+/-/g'
|
||||
}
|
||||
|
||||
# 1. Process the Pool of Wheels
|
||||
if [ "$VERIFY_ONLY" -eq 0 ] && [ -n "$W_POOL" ]; then
|
||||
[ -d "$W_POOL" ] || die "The pool is not a valid directory: $W_POOL"
|
||||
echo "=> Scanning pool of wheels in: $W_POOL"
|
||||
while IFS= read -r -d '' whl; do
|
||||
filename="$(basename "$whl")"
|
||||
|
||||
# The distribution name is everything that comes before the first hyphen (-)
|
||||
raw_dist="${filename%%-*}"
|
||||
norm_pkg="$(normalize_pkg_name "$raw_dist")"
|
||||
dest_dir="$FINAL_REPO/$norm_pkg"
|
||||
|
||||
# Create the folder and ensure its web permissions
|
||||
mkdir -p "$dest_dir"
|
||||
chmod 755 "$dest_dir"
|
||||
|
||||
echo " -> Moving: $filename to the directory /$norm_pkg/"
|
||||
mv -f "$whl" "$dest_dir/"
|
||||
|
||||
# Ensure that the newly moved file has web permissions
|
||||
chmod 644 "$dest_dir/$filename"
|
||||
|
||||
done < <(find "$W_POOL" -maxdepth 1 -type f -name '*.whl' -print0)
|
||||
echo "=> Moving complete."
|
||||
fi
|
||||
|
||||
verify_pkg_index() {
|
||||
local pkgdir="$1"
|
||||
local pkgname="$2"
|
||||
local idx="$pkgdir/index.html"
|
||||
local errs=0
|
||||
|
||||
if [ ! -f "$idx" ]; then
|
||||
echo "VERIFY FAIL [$pkgname]: index.html is missing" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
while IFS= read -r href; do
|
||||
[ -n "$href" ] || continue
|
||||
local file="${href%%#sha256=*}"
|
||||
local want="${href##*#sha256=}"
|
||||
|
||||
if [ ! -f "$pkgdir/$file" ]; then
|
||||
echo "VERIFY FAIL [$pkgname]: file is missing: $file" >&2
|
||||
errs=$((errs+1))
|
||||
continue
|
||||
fi
|
||||
|
||||
local got
|
||||
got="$(sha256sum "$pkgdir/$file" | awk '{print $1}')"
|
||||
if [ "$got" != "$want" ]; then
|
||||
echo "VERIFY FAIL [$pkgname]: sha256 mismatch for $file" >&2
|
||||
echo " expected: $want" >&2
|
||||
echo " got: $got" >&2
|
||||
errs=$((errs+1))
|
||||
fi
|
||||
done < <(
|
||||
grep -oE 'href="[^"]+"' "$idx" \
|
||||
| sed -E 's/^href="(.*)"$/\1/' \
|
||||
| grep -E '#sha256=' || true
|
||||
)
|
||||
|
||||
if [ "$errs" -gt 0 ]; then
|
||||
echo "VERIFY FAIL [$pkgname]: $errs error(s)" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "VERIFY OK [$pkgname]"
|
||||
return 0
|
||||
}
|
||||
|
||||
write_pkg_index() {
|
||||
local pkgdir="$1"
|
||||
local pkgname="$2"
|
||||
local idx="$pkgdir/index.html"
|
||||
|
||||
# Ensure directory and existing wheels permissions prior to this script
|
||||
chmod 755 "$pkgdir"
|
||||
find "$pkgdir" -maxdepth 1 -type f -name '*.whl' -exec chmod 644 {} +
|
||||
|
||||
mapfile -t files < <(find "$pkgdir" -maxdepth 1 -type f -name '*.whl' -printf '%f\n' | sort)
|
||||
[ "${#files[@]}" -gt 0 ] || return 0
|
||||
|
||||
{
|
||||
echo "<!doctype html>"
|
||||
echo "<html><head><meta charset=\"utf-8\"><title>${pkgname}</title></head><body>"
|
||||
for bn in "${files[@]}"; do
|
||||
sha="$(sha256sum "$pkgdir/$bn" | awk '{print $1}')"
|
||||
printf '<a href="%s#sha256=%s">%s</a><br/>\n' "$bn" "$sha" "$bn"
|
||||
done
|
||||
echo "</body></html>"
|
||||
} > "$idx"
|
||||
|
||||
# Ensure generated index permissions
|
||||
chmod 644 "$idx"
|
||||
}
|
||||
|
||||
# Determine package directories
|
||||
pkg_dirs=()
|
||||
if [ -n "$ONLY_PKG" ]; then
|
||||
ONLY_PKG="$(normalize_pkg_name "$ONLY_PKG")"
|
||||
[ -d "$FINAL_REPO/$ONLY_PKG" ] || die "Package directory not found: $FINAL_REPO/$ONLY_PKG"
|
||||
pkg_dirs+=("$FINAL_REPO/$ONLY_PKG")
|
||||
else
|
||||
while IFS= read -r d; do
|
||||
pkg_dirs+=("$d")
|
||||
done < <(find "$FINAL_REPO" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | sort)
|
||||
fi
|
||||
|
||||
if [ "$VERIFY_ONLY" -eq 0 ]; then
|
||||
echo "=> Generating indexes (index.html) and standardizing permissions..."
|
||||
|
||||
# Ensure root directory permissions
|
||||
chmod 755 "$FINAL_REPO"
|
||||
|
||||
for d in "${pkg_dirs[@]}"; do
|
||||
pkg="$(basename "$d")"
|
||||
write_pkg_index "$d" "$pkg"
|
||||
done
|
||||
|
||||
# Main Index
|
||||
if [ "$NO_TOP" -eq 0 ] && [ -z "$ONLY_PKG" ]; then
|
||||
top="$FINAL_REPO/index.html"
|
||||
{
|
||||
echo "<!doctype html>"
|
||||
echo "<html><head><meta charset=\"utf-8\"><title>Simple Index</title></head><body>"
|
||||
for d in "${pkg_dirs[@]}"; do
|
||||
pkg="$(basename "$d")"
|
||||
if find "$d" -maxdepth 1 -type f -name '*.whl' | grep -q .; then
|
||||
printf '<a href="./%s/">%s</a><br/>\n' "$pkg" "$pkg"
|
||||
fi
|
||||
done
|
||||
echo "</body></html>"
|
||||
} > "$top"
|
||||
|
||||
# Main index permissions
|
||||
chmod 644 "$top"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DO_VERIFY" -eq 1 ]; then
|
||||
echo "=> Verifying integrity..."
|
||||
vfail=0
|
||||
for d in "${pkg_dirs[@]}"; do
|
||||
pkg="$(basename "$d")"
|
||||
if ! verify_pkg_index "$d" "$pkg"; then
|
||||
vfail=1
|
||||
fi
|
||||
done
|
||||
if [ "$vfail" -ne 0 ]; then
|
||||
die "Verification failed for one or more packages."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$VERIFY_ONLY" -eq 1 ]; then
|
||||
echo "=> OK: indexes verified in: $FINAL_REPO"
|
||||
else
|
||||
echo "=> OK: process finished successfully in: $FINAL_REPO"
|
||||
fi
|
||||