Compare commits

..

No commits in common. "main" and "add_scripts" have entirely different histories.

107 changed files with 716 additions and 8731 deletions

View File

@ -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 "$@"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.IIABController" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@color/white</item>
<item name="android:textColorPrimary">@color/black</item>
<item name="sectionBackground">@color/section_body_bg_light</item>
<item name="sectionHeaderBackground">@color/section_header_bg</item>
</style>
<style name="Theme.TransparentQR" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
<style name="PurpleSwitchTheme" parent="">
<item name="colorControlActivated">#8A2BE2</item>
</style>
<style name="CustomTabTextStyle" parent="TextAppearance.Design.Tab">
<item name="android:textStyle">bold</item>
<item name="android:textSize">13sp</item>
<item name="textAllCaps">true</item>
</style>
</resources>

View File

@ -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()
}
}

Binary file not shown.

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 KiB

View File

@ -1 +0,0 @@
A simple and lightweight VPN over socks5 proxy (tun2socks)

View File

@ -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>

View File

@ -1 +0,0 @@
Простое и лёгкое VPN поверх Socks5 прокси (tun2socks)

View File

@ -1,2 +0,0 @@
android.enableJetifier=true
android.useAndroidX=true

Binary file not shown.

View File

@ -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

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