Compare commits

..

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

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

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": 28,
"versionName": "v0.1.32beta",
"outputFile": "org.iiab.controller-v0.1.32beta-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/org.iiab.controller-v0.1.32beta-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/org.iiab.controller-v0.1.32beta-release.dm"
]
}
],
"minSdkVersionForDexing": 24
}

View File

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

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

View File

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

View File

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

View File

@ -1,99 +0,0 @@
package org.iiab.controller;
import android.app.Activity;
import android.content.Intent;
import android.provider.Settings;
import android.transition.AutoTransition;
import android.transition.TransitionManager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
public class DashboardManager {
private final Activity activity;
private final LinearLayout dashboardContainer;
private final View dashWifi, dashHotspot, dashTunnel;
private final View ledWifi, ledHotspot, ledTunnel;
private final View standaloneEspwButton;
private final View standaloneEspwDescription;
// We pass a Callback so the Dashboard can tell MainActivity to start/stop the VPN
public interface DashboardActionCallback {
void onToggleEspwRequested();
}
public DashboardManager(Activity activity, View rootView, DashboardActionCallback callback) {
this.activity = activity;
// Bind all the views
dashboardContainer = (LinearLayout) rootView.findViewById(R.id.dashboard_container);
dashWifi = rootView.findViewById(R.id.dash_wifi);
dashHotspot = rootView.findViewById(R.id.dash_hotspot);
dashTunnel = rootView.findViewById(R.id.dash_tunnel);
ledWifi = rootView.findViewById(R.id.led_wifi);
ledHotspot = rootView.findViewById(R.id.led_hotspot);
ledTunnel = rootView.findViewById(R.id.led_tunnel);
standaloneEspwButton = rootView.findViewById(R.id.control);
standaloneEspwDescription = rootView.findViewById(R.id.control_description);
setupListeners(callback);
}
private void setupListeners(DashboardActionCallback callback) {
// Single tap opens Settings directly (No wrench icons needed!)
dashWifi.setOnClickListener(v -> activity.startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)));
dashHotspot.setOnClickListener(v -> {
try {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
activity.startActivity(intent);
} catch (Exception e) {
activity.startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
}
});
// The Tunnel/ESPW toggle logic
View.OnClickListener toggleEspw = v -> callback.onToggleEspwRequested();
standaloneEspwButton.setOnClickListener(toggleEspw);
dashTunnel.setOnClickListener(toggleEspw);
}
// Updates the LED graphics based on actual OS connectivity states
public void updateConnectivityLeds(boolean isWifiOn, boolean isHotspotOn) {
ledWifi.setBackgroundResource(isWifiOn ? R.drawable.led_on_green : R.drawable.led_off);
ledHotspot.setBackgroundResource(isHotspotOn ? R.drawable.led_on_green : R.drawable.led_off);
}
// The Magic Morphing Animation!
public void setTunnelState(boolean isTunnelActive, boolean isDegraded) {
// Tells Android to smoothly animate any layout changes we make next
TransitionManager.beginDelayedTransition((ViewGroup) dashboardContainer.getParent(), new AutoTransition().setDuration(300));
if (isTunnelActive) {
// Morph into 33% / 33% / 33% Dashboard mode
standaloneEspwButton.setVisibility(View.GONE);
standaloneEspwDescription.setVisibility(View.GONE);
dashTunnel.setVisibility(View.VISIBLE);
ledTunnel.setBackgroundResource(isDegraded ? R.drawable.led_on_orange : R.drawable.led_on_green);
// Force recalculate
dashboardContainer.setWeightSum(3f);
} else {
// Morph back into 50% / 50% mode
dashTunnel.setVisibility(View.GONE);
standaloneEspwButton.setVisibility(View.VISIBLE);
standaloneEspwDescription.setVisibility(View.VISIBLE);
// The LED turns off implicitly since the whole dash_tunnel hides, but we can enforce it:
ledTunnel.setBackgroundResource(R.drawable.led_off);
// Force recalculate
dashboardContainer.setWeightSum(2f);
}
// Force recalculate
dashboardContainer.requestLayout();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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,508 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground">
<!-- Custom Header/Toolbar -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="#1A1A1A"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp">
<ImageView
android:id="@+id/header_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@mipmap/ic_launcher" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="12dp"
android:text="@string/app_name"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_share_qr"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_center_focus_strong"
android:contentDescription="Share via QR"
android:padding="10dp"
android:scaleType="fitCenter"
android:tint="#FFFFFF" />
<ImageButton
android:id="@+id/btn_settings"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_menu_preferences"
android:contentDescription="Settings"
android:padding="10dp"
android:scaleType="fitCenter"
android:tint="#FFFFFF" />
<!-- Triple Toggle Theme ImageButton -->
<ImageButton
android:id="@+id/theme_toggle"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_theme_system"
android:contentDescription="Toggle Theme"
android:padding="10dp"
android:scaleType="fitCenter"
android:tint="#FFFFFF" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<LinearLayout
android:id="@+id/dashboard_container"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal"
android:layout_marginBottom="16dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#1A1A1A"
android:paddingHorizontal="8dp"
android:gravity="center_vertical"
android:baselineAligned="false">
<LinearLayout
android:id="@+id/dash_wifi"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp">
<View
android:layout_width="10dp"
android:id="@+id/led_wifi"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_off" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wi-Fi"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/dash_hotspot"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp">
<View
android:id="@+id/led_hotspot"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_off" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hotspot"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/dash_tunnel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:padding="12dp"
android:visibility="gone">
<View
android:id="@+id/led_tunnel"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_marginEnd="8dp"
android:background="@drawable/led_on_green" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tunnel"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<!-- VPN Control Section -->
<Button
android:id="@+id/control"
android:layout_width="match_parent"
android:layout_height="90dp"
android:text="@string/control_enable"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_vpn_off"
android:textAllCaps="false"/>
<TextView
android:id="@+id/control_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/vpn_description"
android:textSize="13sp"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"/>
<!-- Local WebView -->
<Button
android:id="@+id/btnBrowseContent"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="12dp"
android:text="@string/browse_content"
android:textSize="21sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_explore_disabled"
android:textAllCaps="false"
android:elevation="4dp"
android:enabled="false" />
<TextView
android:id="@+id/config_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_settings_label"
android:textStyle="bold"
android:padding="12dp"
android:background="?attr/sectionHeaderBackground"
android:textColor="#FFFFFF"
android:clickable="true"
android:focusable="true"
android:layout_marginTop="20dp" />
<LinearLayout
android:id="@+id/config_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:padding="12dp"
android:background="?attr/sectionBackground">
<Button
android:id="@+id/apps"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/apps"
android:layout_marginBottom="12dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#673AB7"
android:textColor="#FFFFFF"
android:textAllCaps="false"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dns_ipv4"
android:textColor="?android:attr/textColorSecondary"
android:textSize="11sp"/>
<EditText android:id="@+id/dns_ipv4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dns_ipv6"
android:textColor="?android:attr/textColorSecondary"
android:textSize="11sp"/>
<EditText android:id="@+id/dns_ipv6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="?android:attr/textColorPrimary"/>
<CheckBox
android:id="@+id/checkbox_maintenance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="Maintenance Mode"
android:checked="true" />
<TextView
android:id="@+id/maintenance_warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Disable Safe Pocket Web in order to modify"
android:textSize="11sp"
android:textStyle="italic"
android:textColor="#FF9800"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<Button android:id="@+id/save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/save"
android:layout_marginTop="8dp"
android:background="@drawable/rounded_button"
android:backgroundTint="#555555"
android:textColor="#FFFFFF"
android:textAllCaps="false"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp" android:visibility="gone"/>
<EditText android:id="@+id/socks_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary" android:visibility="gone"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_port" android:textColor="?android:attr/textColorSecondary" android:textSize="12sp" android:visibility="gone"/>
<EditText android:id="@+id/socks_port" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="number" android:textColor="?android:attr/textColorPrimary" android:visibility="gone"/>
<TextView
android:id="@+id/adv_config_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/advanced_settings_label"
android:textColor="?android:attr/textColorSecondary"
android:padding="8dp"
android:textSize="13sp"
android:clickable="true"
android:focusable="true"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/advanced_config"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_udp_addr" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_udp_addr" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_user" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_user" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:textColor="?android:attr/textColorPrimary"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/socks_pass" android:textColor="?android:attr/textColorSecondary" android:textSize="11sp"/>
<EditText android:id="@+id/socks_pass" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:inputType="textPassword" android:textColor="?android:attr/textColorPrimary"/>
<CheckBox android:id="@+id/udp_in_tcp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/udp_in_tcp" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/ipv4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv4" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/ipv6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/ipv6" android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:visibility="gone">
<CheckBox android:id="@+id/remote_dns" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/remote_dns" android:textColor="?android:attr/textColorSecondary"/>
<CheckBox android:id="@+id/global" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/global" android:textColor="?android:attr/textColorSecondary"/>
</LinearLayout>
</LinearLayout>
<!-- HR above Watchdog -->
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginBottom="16dp"/>
<!-- Watchdog Control Section -->
<LinearLayout
android:id="@+id/deck_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginBottom="8dp"
android:background="#00000000"
android:padding="3dp"
android:orientation="horizontal"
android:baselineAligned="false"
android:weightSum="2">
<org.iiab.controller.ProgressButton
android:id="@+id/btn_server_control"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:text="Launch Server"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_success"
android:textAllCaps="false"
app:progressButtonHeight="6dp"
app:progressButtonDuration="@integer/server_cool_off_duration_ms"
app:progressButtonColor="#FF9800" />
<Button
android:id="@+id/watchdog_control"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:text="@string/watchdog_enable"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="@drawable/rounded_button"
android:backgroundTint="@color/btn_watchdog_off"
android:textAllCaps="false"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/watchdog_description"
android:textSize="13sp"
android:gravity="center"
android:textColor="?android:attr/textColorSecondary"
android:layout_marginBottom="16dp"/>
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginVertical="20dp"/>
<!-- Log Section (Collapsible) -->
<TextView
android:id="@+id/log_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/connection_log_label"
android:textStyle="bold"
android:padding="10dp"
android:background="?attr/sectionHeaderBackground"
android:textColor="#FFFFFF"
android:clickable="true"
android:focusable="true"/>
<!-- Log Warnings (Growth rate warning) -->
<TextView
android:id="@+id/log_warning_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_warning"
android:textSize="11sp"
android:textStyle="italic"
android:padding="4dp"
android:visibility="gone"
android:text="@string/log_warning_rapid_growth" />
<!-- Loading Indicator for Logs -->
<ProgressBar
android:id="@+id/log_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone" />
<TextView
android:id="@+id/connection_log"
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#000000"
android:textColor="#00FF00"
android:fontFamily="monospace"
android:padding="8dp"
android:visibility="gone"
android:scrollbars="vertical"
android:fadeScrollbars="false"
android:scrollbarSize="10dp"
android:scrollbarThumbVertical="@drawable/scrollbar_thumb"
android:text="@string/system_ready"
android:textSize="11sp"/>
<!-- Log Size Indicator -->
<TextView
android:id="@+id/log_size_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="?android:attr/textColorSecondary"
android:textSize="10sp"
android:paddingEnd="8dp"
android:visibility="gone"
android:text="Size: 0KB / 10MB" />
<!-- Log Actions Bar -->
<LinearLayout
android:id="@+id/log_actions"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginTop="4dp">
<Button
android:id="@+id/btn_clear_log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/reset_log"
android:textSize="12sp"
android:backgroundTint="@color/btn_danger"
android:textColor="#FFFFFF"
android:textAllCaps="false"
android:layout_marginEnd="4dp"/>
<Button
android:id="@+id/btn_copy_log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/copy_all"
android:textSize="12sp"
android:backgroundTint="@color/btn_success"
android:textColor="#FFFFFF"
android:textAllCaps="false"
android:layout_marginStart="4dp"/>
</LinearLayout>
<!-- Version Footer -->
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/divider_color" android:layout_marginTop="32dp" android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/version_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="v0.1.17alpha"
android:textColor="@color/text_muted"
android:textSize="10sp"
android:paddingBottom="16dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>

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,155 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General App -->
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="save">Guardar</string>
<string name="cancel">Cancelar</string>
<string name="saved_toast">Guardado</string>
<string name="settings_saved">Ajustes Guardados</string>
<string name="fix_action">CORREGIR</string>
<string name="configuration_label">Configuración</string>
<string name="advanced_settings_label">Ajustes del Túnel</string>
<string name="connection_log_label">Log de Conexión</string>
<string name="settings_label">AJUSTES</string>
<!-- SetupActivity -->
<string name="setup_title">Configuración Inicial</string>
<string name="setup_welcome">Bienvenido al asistente de configuración de %1$s.\n\nPara funcionar correctamente, necesitamos los siguientes permisos:</string>
<string name="setup_perm_notifications">Notificaciones Push</string>
<string name="setup_perm_termux">Ejecución de Termux</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_perm_battery">Desactivar Optimización de Batería</string>
<string name="setup_continue">Continuar</string>
<string name="revoke_permission_warning">Para revocar permisos, debe hacerlo desde los ajustes del sistema.</string>
<string name="termux_not_installed_error">Termux no está instalado o el dispositivo no es compatible.</string>
<string name="termux_not_installed">Termux no está instalado.</string>
<!-- VPN / Socks -->
<string name="control_enable">Activar Safe Pocket Web</string>
<string name="control_disable">Desactivar Safe Pocket Web</string>
<string name="vpn_description">Habilite URLs amigables. Bloquee las amenazas.</string>
<string name="socks_addr">Dirección Socks:</string>
<string name="socks_udp_addr">Dirección UDP Socks:</string>
<string name="socks_port">Puerto Socks:</string>
<string name="socks_user">Usuario Socks:</string>
<string name="socks_pass">Contraseña Socks:</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="udp_in_tcp">Relé UDP sobre TCP</string>
<string name="remote_dns">DNS Remoto</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="global">Global</string>
<string name="apps">Aplicaciones</string>
<string name="vpn_stopping">Deteniendo VPN...</string>
<string name="vpn_starting">Iniciando VPN...</string>
<string name="user_initiated_conn">Conexión iniciada por el usuario</string>
<string name="vpn_permission_granted">Permiso de VPN concedido. Conectando...</string>
<string name="tproxy_channel_name">socks5</string>
<!-- WatchdogService / IIABWatchdog -->
<string name="watchdog_enable">Activar\nWatchdog Maestro</string>
<string name="watchdog_disable">Desactivar\nWatchdog Maestro</string>
<string name="watchdog_description">Protege Termux del modo Doze y mantiene el Wi-Fi activo.</string>
<string name="watchdog_stopped">Watchdog Detenido</string>
<string name="watchdog_started">Watchdog Iniciado</string>
<string name="watchdog_channel_name">Servicio IIAB Watchdog</string>
<string name="watchdog_channel_desc">Asegura que los servicios permanezcan activos cuando la pantalla está apagada.</string>
<string name="watchdog_notif_title">IIAB Watchdog Activo</string>
<string name="watchdog_notif_text">Protegiendo el entorno Termux...</string>
<string name="syncing_watchdog">Sincronizando estado del Watchdog. Activado: %b</string>
<string name="watchdog_thread_started">Watchdog Thread: Bucle iniciado</string>
<string name="watchdog_thread_interrupted">Watchdog Thread: Interrumpido, deteniéndose...</string>
<string name="watchdog_thread_error">Watchdog Thread: Error en el bucle</string>
<string name="watchdog_thread_ended">Watchdog Thread: Bucle finalizado</string>
<string name="cpu_wakelock_acquired">CPU WakeLock adquirido bajo protección VPN</string>
<string name="wifi_lock_acquired">Wi-Fi Lock adquirido bajo protección VPN</string>
<string name="error_acquiring_locks">Error al adquirir bloqueos</string>
<string name="cpu_wakelock_released">CPU WakeLock liberado</string>
<string name="wifi_lock_released">Wi-Fi Lock liberado</string>
<!-- Pulse / Heartbeat -->
<string name="pulse_stimulating">Pulso: Estimulando Termux...</string>
<string name="critical_os_blocked">CRÍTICO: El SO bloqueó el estímulo a Termux (SecurityException).</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="ping_fail">PING 8085: FALLO (%s)</string>
<string name="session_started">SESIÓN DE LATIDO INICIADA</string>
<string name="session_stopped">SESIÓN DE LATIDO DETENIDA</string>
<string name="permission_denied_log">Permiso denegado: Asegúrese de que el manifiesto tiene RUN_COMMAND y la app no está restringida.</string>
<string name="unexpected_error_termux">Error inesperado enviando intent a Termux</string>
<string name="pulse_error_log">Error de Pulso: %s</string>
<string name="maintenance_write_failed">Fallo en la escritura de mantenimiento</string>
<string name="failed_write_blackbox">Fallo al escribir en BlackBox</string>
<string name="recovery_pulse_received">Pulso de recuperación recibido del sistema. Forzando VPN...</string>
<!-- TermuxCallbackReceiver / Operations -->
<string name="termux_stimulus_ok">[Termux] Estímulo OK (exit 0)</string>
<string name="termux_pulse_error">[Termux] Error de pulso (exit %1$d): %2$s</string>
<string name="server_timeout_warning">Advertencia: Tiempo de espera agotado en la transición de estado del servidor.</string>
<string name="server_booting">Iniciando...</string>
<string name="server_shutting_down">Apagando...</string>
<string name="failed_termux_intent">CRÍTICO: Fallo en el Intent de Termux: %s</string>
<string name="sent_to_termux">Enviado a Termux: %s</string>
<string name="maintenance_mode_enabled">Modo de mantenimiento activado: Termux tiene acceso directo a Internet</string>
<string name="stop_server">🛑 Detener Servidor</string>
<string name="launch_server">🚀 Iniciar Servidor</string>
<string name="termux_perm_granted">Permiso de Termux concedido</string>
<string name="termux_perm_denied">Permiso de Termux denegado</string>
<string name="notif_perm_granted">Permiso de notificaciones concedido</string>
<string name="notif_perm_denied">Permiso de notificaciones denegado</string>
<!-- Logs -->
<string name="log_reset_confirm_title">¿Reiniciar historial de log?</string>
<string name="log_reset_confirm_msg">Esto borrará permanentemente todos los logs de conexión guardados. Esta acción no se puede deshacer.</string>
<string name="log_warning_rapid_growth">El archivo de log está creciendo demasiado rápido, verifique si algo está fallando</string>
<string name="reset_log">Reiniciar Log</string>
<string name="copy_all">Copiar Todo</string>
<string name="log_reset_log">Log reiniciado</string>
<string name="log_reset_user">Log reiniciado por el usuario</string>
<string name="log_copied_toast">Log copiado al portapapeles</string>
<string name="log_cleared_toast">Log borrado</string>
<string name="failed_reset_log">Fallo al reiniciar el log: %s</string>
<string name="log_size_format">Tamaño: %1$s / 10MB</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="no_blackbox_found">--- No se encontró el archivo BlackBox ---</string>
<string name="loading_history">--- Cargando Historial ---</string>
<string name="error_reading_history">Error al leer el historial: %s</string>
<string name="end_of_history">--- Fin del Historial ---</string>
<!-- Battery Optimizations -->
<string name="battery_opt_title">Optimización de Batería</string>
<string name="battery_opt_msg">Para que el Watchdog funcione de manera confiable, desactive las optimizaciones de batería para esta aplicación.</string>
<string name="go_to_settings">Ir a Ajustes</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme detectado: Asegúrese de activar \'Permitir actividad en segundo plano\' en los ajustes de esta aplicación.</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi detectado: Establezca el ahorro de batería a \'Sin restricciones\' en los ajustes.</string>
<string name="battery_opt_denied">Para que la app funcione al 100%, desactive la optimización de batería.</string>
<!-- UI / Misc -->
<string name="browse_content">🚀 Explorar Contenido</string>
<string name="system_ready">Sistema listo...\n</string>
<string name="app_started">Aplicación Iniciada</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="qr_error_no_server">Inicie el servidor para compartir contenido a través de la red.</string>
<string name="qr_error_no_network">Active Wi-Fi o Hotspot para compartir contenido a través de la red.</string>
<string name="qr_title_wifi">Red Wi-Fi</string>
<string name="qr_title_hotspot">Red Hotspot</string>
<string name="qr_flip_network">Cambiar Red</string>
<!-- Authentication / Security -->
<string name="unlock_watchdog_title">Desbloquear Watchdog Maestro</string>
<string name="unlock_watchdog_subtitle">Se requiere autenticación para detener la protección de Termux</string>
<string name="auth_success_disconnect">Autenticación exitosa. Desconectando...</string>
<string name="auth_required_title">Autenticación requerida</string>
<string name="auth_required_subtitle">Autentíquese para desactivar el entorno seguro</string>
<string name="security_required_title">Seguridad Requerida</string>
<string name="security_required_msg">Debe configurar un PIN, Patrón o Huella digital en su dispositivo antes de activar el entorno seguro.</string>
<!-- VPN Recovery Service -->
<string name="recovery_channel_name">Recuperación VPN</string>
<string name="recovery_notif_title">Safe Pocket Web Interrumpido</string>
<string name="recovery_notif_text">Toque para restaurar el entorno seguro inmediatamente.</string>
</resources>

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,155 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General App -->
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="save">Сохранить</string>
<string name="cancel">Отмена</string>
<string name="saved_toast">Сохранено</string>
<string name="settings_saved">Настройки сохранены</string>
<string name="fix_action">ИСПРАВИТЬ</string>
<string name="configuration_label">Конфигурация</string>
<string name="advanced_settings_label">Настройки туннеля</string>
<string name="connection_log_label">Журнал подключений</string>
<string name="settings_label">НАСТРОЙКИ</string>
<!-- SetupActivity -->
<string name="setup_title">Начальная настройка</string>
<string name="setup_welcome">Добро пожаловать в мастер настройки %1$s.\n\nДля правильной работы нам нужны следующие разрешения:</string>
<string name="setup_perm_notifications">Push-уведомления</string>
<string name="setup_perm_termux">Выполнение Termux</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_perm_battery">Отключить оптимизацию батареи</string>
<string name="setup_continue">Продолжить</string>
<string name="revoke_permission_warning">Чтобы отозвать разрешения, это нужно сделать в настройках системы.</string>
<string name="termux_not_installed_error">Termux не установлен или устройство не поддерживается.</string>
<string name="termux_not_installed">Termux не установлен.</string>
<!-- VPN / Socks -->
<string name="control_enable">Включить Safe Pocket Web</string>
<string name="control_disable">Выключить Safe Pocket Web</string>
<string name="vpn_description">Включить дружественные URL. Блокировать угрозы.</string>
<string name="socks_addr">Адрес Socks:</string>
<string name="socks_udp_addr">UDP адрес Socks:</string>
<string name="socks_port">Порт Socks:</string>
<string name="socks_user">Имя пользователя Socks:</string>
<string name="socks_pass">Пароль Socks:</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="udp_in_tcp">UDP ретрансляция через TCP</string>
<string name="remote_dns">Удаленный DNS</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="global">Глобально</string>
<string name="apps">Приложения</string>
<string name="vpn_stopping">Остановка VPN...</string>
<string name="vpn_starting">Запуск VPN...</string>
<string name="user_initiated_conn">Соединение инициировано пользователем</string>
<string name="vpn_permission_granted">Разрешение VPN получено. Подключение...</string>
<string name="tproxy_channel_name">socks5</string>
<!-- WatchdogService / IIABWatchdog -->
<string name="watchdog_enable">Включить\nМастер Watchdog</string>
<string name="watchdog_disable">Выключить\nМастер Watchdog</string>
<string name="watchdog_description">Защищает Termux от режима Doze и поддерживает Wi-Fi активным.</string>
<string name="watchdog_stopped">Watchdog остановлен</string>
<string name="watchdog_started">Watchdog запущен</string>
<string name="watchdog_channel_name">Служба IIAB Watchdog</string>
<string name="watchdog_channel_desc">Гарантирует, что службы остаются активными при выключенном экране.</string>
<string name="watchdog_notif_title">IIAB Watchdog активен</string>
<string name="watchdog_notif_text">Защита окружения Termux...</string>
<string name="syncing_watchdog">Синхронизация состояния Watchdog. Включено: %b</string>
<string name="watchdog_thread_started">Watchdog Thread: Цикл запущен</string>
<string name="watchdog_thread_interrupted">Watchdog Thread: Прервано, остановка...</string>
<string name="watchdog_thread_error">Watchdog Thread: Ошибка в цикле</string>
<string name="watchdog_thread_ended">Watchdog Thread: Цикл завершен</string>
<string name="cpu_wakelock_acquired">CPU WakeLock получен под защитой VPN</string>
<string name="wifi_lock_acquired">Wi-Fi Lock получен под защитой VPN</string>
<string name="error_acquiring_locks">Ошибка получения блокировок</string>
<string name="cpu_wakelock_released">CPU WakeLock освобожден</string>
<string name="wifi_lock_released">Wi-Fi Lock освобожден</string>
<!-- Pulse / Heartbeat -->
<string name="pulse_stimulating">Пульс: Стимуляция Termux...</string>
<string name="critical_os_blocked">КРИТИЧЕСКАЯ ОШИБКА: ОС заблокировала стимуляцию Termux (SecurityException).</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="ping_fail">PING 8085: ОШИБКА (%s)</string>
<string name="session_started">СЕАНС СЕРДЦЕБИЕНИЯ ЗАПУЩЕН</string>
<string name="session_stopped">СЕАНС СЕРДЦЕБИЕНИЯ ОСТАНОВЛЕН</string>
<string name="permission_denied_log">В доступе отказано: убедитесь, что в манифесте есть RUN_COMMAND и приложение не ограничено.</string>
<string name="unexpected_error_termux">Непредвиденная ошибка при отправке intent в Termux</string>
<string name="pulse_error_log">Ошибка пульса: %s</string>
<string name="maintenance_write_failed">Ошибка записи обслуживания</string>
<string name="failed_write_blackbox">Ошибка записи в BlackBox</string>
<string name="recovery_pulse_received">Пульс восстановления получен от системы. Принудительный VPN...</string>
<!-- TermuxCallbackReceiver / Operations -->
<string name="termux_stimulus_ok">[Termux] Стимул OK (exit 0)</string>
<string name="termux_pulse_error">[Termux] Ошибка пульса (exit %1$d): %2$s</string>
<string name="server_timeout_warning">Предупреждение: Время ожидания перехода состояния сервера истекло.</string>
<string name="server_booting">Загрузка...</string>
<string name="server_shutting_down">Выключение...</string>
<string name="failed_termux_intent">КРИТИЧЕСКАЯ ОШИБКА: Ошибка Intent Termux: %s</string>
<string name="sent_to_termux">Отправлено в Termux: %s</string>
<string name="maintenance_mode_enabled">Режим обслуживания включен: Termux имеет прямой доступ в Интернет</string>
<string name="stop_server">🛑 Остановить сервер</string>
<string name="launch_server">🚀 Запустить сервер</string>
<string name="termux_perm_granted">Разрешение Termux предоставлено</string>
<string name="termux_perm_denied">Разрешение Termux отклонено</string>
<string name="notif_perm_granted">Разрешение на уведомления предоставлено</string>
<string name="notif_perm_denied">Разрешение на уведомления отклонено</string>
<!-- Logs -->
<string name="log_reset_confirm_title">Сбросить историю журнала?</string>
<string name="log_reset_confirm_msg">Это безвозвратно удалит все сохраненные журналы подключений. Это действие нельзя отменить.</string>
<string name="log_warning_rapid_growth">Файл журнала растет слишком быстро, возможно, стоит проверить, нет ли ошибки</string>
<string name="reset_log">Сбросить журнал</string>
<string name="copy_all">Скопировать все</string>
<string name="log_reset_log">Журнал сброшен</string>
<string name="log_reset_user">Журнал сброшен пользователем</string>
<string name="log_copied_toast">Журнал скопирован в буфер обмена</string>
<string name="log_cleared_toast">Журнал очищен</string>
<string name="failed_reset_log">Ошибка сброса журнала: %s</string>
<string name="log_size_format">Размер: %1$s / 10MB</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="no_blackbox_found">--- Файл BlackBox не найден ---</string>
<string name="loading_history">--- Загрузка истории ---</string>
<string name="error_reading_history">Ошибка чтения истории: %s</string>
<string name="end_of_history">--- Конец истории ---</string>
<!-- Battery Optimizations -->
<string name="battery_opt_title">Оптимизация батареи</string>
<string name="battery_opt_msg">Для надежной работы Watchdog, пожалуйста, отключите оптимизацию батареи для этого приложения.</string>
<string name="go_to_settings">Перейти к настройкам</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme обнаружен: Пожалуйста, убедитесь, что вы включили "Разрешить фоновую активность" в настройках этого приложения.</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi обнаружен: Пожалуйста, установите экономию заряда батареи на "Без ограничений" в настройках.</string>
<string name="battery_opt_denied">Для 100% работы приложения, пожалуйста, отключите оптимизацию батареи.</string>
<!-- UI / Misc -->
<string name="browse_content">🚀 Исследовать контент</string>
<string name="system_ready">Система готова...\n</string>
<string name="app_started">Приложение запущено</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="qr_error_no_server">Запустите сервер, чтобы поделиться контентом по сети.</string>
<string name="qr_error_no_network">Включите Wi-Fi или точку доступа, чтобы поделиться контентом по сети.</string>
<string name="qr_title_wifi">Сеть Wi-Fi</string>
<string name="qr_title_hotspot">Сеть точки доступа</string>
<string name="qr_flip_network">Переключить сеть</string>
<!-- Authentication / Security -->
<string name="unlock_watchdog_title">Разблокировать Мастер Watchdog</string>
<string name="unlock_watchdog_subtitle">Требуется аутентификация для остановки защиты Termux</string>
<string name="auth_success_disconnect">Аутентификация успешна. Отключение...</string>
<string name="auth_required_title">Требуется аутентификация</string>
<string name="auth_required_subtitle">Пройдите аутентификацию, чтобы отключить безопасное окружение</string>
<string name="security_required_title">Требуется безопасность</string>
<string name="security_required_msg">Перед активацией безопасного окружения необходимо установить PIN-код, графический ключ или отпечаток пальца на устройстве.</string>
<!-- VPN Recovery Service -->
<string name="recovery_channel_name">Восстановление VPN</string>
<string name="recovery_notif_title">Safe Pocket Web прерван</string>
<string name="recovery_notif_text">Нажмите, чтобы немедленно восстановить безопасное окружение.</string>
</resources>

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,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#1A1A1A</color>
<color name="colorPrimaryDark">#000000</color>
<color name="colorAccent">#2E7D32</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="lightGray66">#AAAAAA</color>
<color name="background_dark">#121212</color>
<!-- Status Colors (Fixed for readability) -->
<color name="btn_watchdog_on">#D32F2F</color> <!-- Material Red -->
<color name="btn_watchdog_off">#1976D2</color> <!-- Material Blue -->
<color name="btn_vpn_on">#C62828</color> <!-- Dark Red -->
<color name="btn_vpn_off">#2E7D32</color> <!-- Material Green -->
<color name="btn_explore_ready">#F57C00</color>
<color name="btn_explore_disabled">#9E9E9E</color>
<color name="btn_vpn_on_dim">#EF9A9A</color>
<color name="btn_vpn_off_dim">#A5D6A7</color>
<!-- Sections -->
<color name="section_header_bg">#333333</color>
<color name="section_body_bg_light">#F5F5F5</color>
<color name="section_body_bg_dark">#1A1A1A</color>
<color name="text_warning">#FF9800</color>
<color name="text_muted">#888888</color>
<color name="divider_color">#444444</color>
<color name="btn_danger">#D32F2F</color>
<color name="btn_success">#388E3C</color>
</resources>

View File

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

View File

@ -1,155 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- General App -->
<string name="app_name">IIAB-oA Controller</string>
<string name="default_version">v0.1.x</string>
<string name="save">Save</string>
<string name="cancel">Cancel</string>
<string name="saved_toast">Saved</string>
<string name="settings_saved">Settings Saved</string>
<string name="fix_action">FIX</string>
<string name="configuration_label">Configuration</string>
<string name="advanced_settings_label">Tunnel Settings</string>
<string name="connection_log_label">Connection Log</string>
<string name="settings_label">SETTINGS</string>
<!-- SetupActivity -->
<string name="setup_title">Initial Setup</string>
<string name="setup_welcome">Welcome to the %1$s setup wizard.\n\nIn order to work properly, we need the following permissions:</string>
<string name="setup_perm_notifications">Push Notifications</string>
<string name="setup_perm_termux">Termux Execution</string>
<string name="setup_perm_vpn">Safe Pocket Web (VPN)</string>
<string name="setup_perm_battery">Disable Battery Optimization</string>
<string name="setup_continue">Continue</string>
<string name="revoke_permission_warning">To revoke permissions, you must do it from system settings.</string>
<string name="termux_not_installed_error">Termux is not installed or device not supported.</string>
<string name="termux_not_installed">Termux is not installed.</string>
<!-- VPN / Socks -->
<string name="control_enable">Enable Safe Pocket Web</string>
<string name="control_disable">Disable Safe Pocket Web</string>
<string name="vpn_description">Enable friendly URLs. Lock out the threats.</string>
<string name="socks_addr">Socks Address:</string>
<string name="socks_udp_addr">Socks UDP Address:</string>
<string name="socks_port">Socks Port:</string>
<string name="socks_user">Socks Username:</string>
<string name="socks_pass">Socks Password:</string>
<string name="dns_ipv4">DNS IPv4:</string>
<string name="dns_ipv6">DNS IPv6:</string>
<string name="udp_in_tcp">UDP relay over TCP</string>
<string name="remote_dns">Remote DNS</string>
<string name="ipv4">IPv4</string>
<string name="ipv6">IPv6</string>
<string name="global">Global</string>
<string name="apps">Apps</string>
<string name="vpn_stopping">VPN Stopping...</string>
<string name="vpn_starting">VPN Starting...</string>
<string name="user_initiated_conn">User initiated connection</string>
<string name="vpn_permission_granted">VPN Permission Granted. Connecting...</string>
<string name="tproxy_channel_name">socks5</string>
<!-- WatchdogService / IIABWatchdog -->
<string name="watchdog_enable">Enable\nMaster Watchdog</string>
<string name="watchdog_disable">Disable\nMaster Watchdog</string>
<string name="watchdog_description">Protects Termux from Doze mode and keeps Wi-Fi active.</string>
<string name="watchdog_stopped">Watchdog Stopped</string>
<string name="watchdog_started">Watchdog Started</string>
<string name="watchdog_channel_name">IIAB Watchdog Service</string>
<string name="watchdog_channel_desc">Ensures services remain active when screen is off.</string>
<string name="watchdog_notif_title">IIAB Watchdog Active</string>
<string name="watchdog_notif_text">Protecting Termux environment...</string>
<string name="syncing_watchdog">Syncing Watchdog state. Enabled: %b</string>
<string name="watchdog_thread_started">Watchdog Thread: Started loop</string>
<string name="watchdog_thread_interrupted">Watchdog Thread: Interrupted, stopping...</string>
<string name="watchdog_thread_error">Watchdog Thread: Error in loop</string>
<string name="watchdog_thread_ended">Watchdog Thread: Loop ended</string>
<string name="cpu_wakelock_acquired">CPU WakeLock acquired under VPN shield</string>
<string name="wifi_lock_acquired">Wi-Fi Lock acquired under VPN shield</string>
<string name="error_acquiring_locks">Error acquiring locks</string>
<string name="cpu_wakelock_released">CPU WakeLock released</string>
<string name="wifi_lock_released">Wi-Fi Lock released</string>
<!-- Pulse / Heartbeat -->
<string name="pulse_stimulating">Pulse: Stimulating Termux...</string>
<string name="critical_os_blocked">CRITICAL: OS blocked Termux stimulus (SecurityException).</string>
<string name="ping_ok">PING 8085: OK</string>
<string name="ping_fail">PING 8085: FAIL (%s)</string>
<string name="session_started">HEARTBEAT SESSION STARTED</string>
<string name="session_stopped">HEARTBEAT SESSION STOPPED</string>
<string name="permission_denied_log">Permission Denied: Ensure manifest has RUN_COMMAND and app is not restricted.</string>
<string name="unexpected_error_termux">Unexpected error sending intent to Termux</string>
<string name="pulse_error_log">Pulse Error: %s</string>
<string name="maintenance_write_failed">Maintenance write failed</string>
<string name="failed_write_blackbox">Failed to write to BlackBox</string>
<string name="recovery_pulse_received">Recovery Pulse Received from System. Enforcing VPN...</string>
<!-- TermuxCallbackReceiver / Operations -->
<string name="termux_stimulus_ok">[Termux] Stimulus OK (exit 0)</string>
<string name="termux_pulse_error">[Termux] Pulse Error (exit %1$d): %2$s</string>
<string name="server_timeout_warning">Warning: Server state transition timed out.</string>
<string name="server_booting">Booting...</string>
<string name="server_shutting_down">Shutting down...</string>
<string name="failed_termux_intent">CRITICAL: Failed Termux Intent: %s</string>
<string name="sent_to_termux">Sent to Termux: %s</string>
<string name="maintenance_mode_enabled">Maintenance mode enabled: Termux has direct Internet access</string>
<string name="stop_server">🛑 Stop Server</string>
<string name="launch_server">🚀 Launch Server</string>
<string name="termux_perm_granted">Termux permission granted</string>
<string name="termux_perm_denied">Termux permission denied</string>
<string name="notif_perm_granted">Notification permission granted</string>
<string name="notif_perm_denied">Notification permission denied</string>
<!-- Logs -->
<string name="log_reset_confirm_title">Reset Log History?</string>
<string name="log_reset_confirm_msg">This will permanently delete all stored connection logs. This action cannot be undone.</string>
<string name="log_warning_rapid_growth">The logging file is growing too rapidly, you might want to check if something is failing</string>
<string name="reset_log">Reset Log</string>
<string name="copy_all">Copy All</string>
<string name="log_reset_log">Log reset</string>
<string name="log_reset_user">Log reset by user</string>
<string name="log_copied_toast">Log copied to clipboard</string>
<string name="log_cleared_toast">Log cleared</string>
<string name="failed_reset_log">Failed to reset log: %s</string>
<string name="log_size_format">Size: %1$s / 10MB</string>
<string name="log_size_bytes">%d B</string>
<string name="log_size_kb">%.1f KB</string>
<string name="log_size_mb">%.2f MB</string>
<string name="no_blackbox_found">--- No BlackBox file found ---</string>
<string name="loading_history">--- Loading History ---</string>
<string name="error_reading_history">Error reading history: %s</string>
<string name="end_of_history">--- End of History ---</string>
<!-- Battery Optimizations -->
<string name="battery_opt_title">Battery Optimization</string>
<string name="battery_opt_msg">For the Watchdog to work reliably, please disable battery optimizations for this app.</string>
<string name="go_to_settings">Go to Settings</string>
<string name="battery_opt_oppo_extra">\n\nOPPO/Realme detected: Please ensure you also enable \'Allow background activity\' in this app\'s settings.</string>
<string name="battery_opt_xiaomi_extra">\n\nXiaomi detected: Please set battery saver to \'No restrictions\' in settings.</string>
<string name="battery_opt_denied">For the app to work 100%, please disable battery optimization.</string>
<!-- UI / Misc -->
<string name="browse_content">🚀 Explore Content</string>
<string name="system_ready">System ready...\n</string>
<string name="app_started">Application Started</string>
<string name="label_separator_down">▼ %s</string>
<string name="label_separator_up">▶ %s</string>
<string name="qr_error_no_server">Launch the server to share content over the network.</string>
<string name="qr_error_no_network">Enable Wi-Fi or Hotspot to share content over the network.</string>
<string name="qr_title_wifi">Wi-Fi Network</string>
<string name="qr_title_hotspot">Hotspot Network</string>
<string name="qr_flip_network">Switch Network</string>
<!-- Authentication / Security -->
<string name="unlock_watchdog_title">Unlock Master Watchdog</string>
<string name="unlock_watchdog_subtitle">Authentication required to stop Termux protection</string>
<string name="auth_success_disconnect">Authentication Success. Disconnecting...</string>
<string name="auth_required_title">Authentication required</string>
<string name="auth_required_subtitle">Authenticate to disable the secure environment</string>
<string name="security_required_title">Security Required</string>
<string name="security_required_msg">You must set up a PIN, Pattern, or Fingerprint on your device before enabling the secure environment.</string>
<!-- VPN Recovery Service -->
<string name="recovery_channel_name">VPN Recovery</string>
<string name="recovery_notif_title">Safe Pocket Web Interrupted</string>
<string name="recovery_notif_text">Tap to restore secure environment immediately.</string>
</resources>

View File

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

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

188
apk/controller/gradlew vendored
View File

@ -1,188 +0,0 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View File

@ -1,100 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -1 +0,0 @@
include ':app'

View File

@ -1,4 +0,0 @@
storeFile=debug.keystore
keyAlias=androiddebugkey
storePassword=android
keyPassword=android

View File

@ -7,7 +7,6 @@
# --clean Stop+delete+purge target VMs (by BASE-<number>, regardless of COUNT) # --clean Stop+delete+purge target VMs (by BASE-<number>, regardless of COUNT)
# Distro selection: # Distro selection:
# --debian-13 Debian 13 only (sets IMAGE=$DEBIAN13_IMAGE_URL and BASE=deb13) # --debian-13 Debian 13 only (sets IMAGE=$DEBIAN13_IMAGE_URL and BASE=deb13)
# --ubuntu-release VER Use specific Ubuntu release (e.g., 24.04, 26.04); sets IMAGE and BASE dynamically
# --both-distros Run Ubuntu + Debian 13 in parallel: COUNT=N => 2N VMs (default order: interleaved) # --both-distros Run Ubuntu + Debian 13 in parallel: COUNT=N => 2N VMs (default order: interleaved)
# --first-ubuntu (with --both-distros) order: all Ubuntu first, then all Debian # --first-ubuntu (with --both-distros) order: all Ubuntu first, then all Debian
# --first-debian (with --both-distros) order: all Debian first, then all Ubuntu # --first-debian (with --both-distros) order: all Debian first, then all Ubuntu
@ -47,8 +46,8 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# Source: Debian cloud images live under cloud.debian.org/images/cloud/ ('genericcloud' includes cloud-init). # Source: Debian cloud images live under cloud.debian.org/images/cloud/ ('genericcloud' includes cloud-init).
DEBIAN13_IMAGE_URL="${DEBIAN13_IMAGE_URL:-https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-${DPKG_ARCH}.qcow2}" DEBIAN13_IMAGE_URL="${DEBIAN13_IMAGE_URL:-https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-${DPKG_ARCH}.qcow2}"
IMAGE="${IMAGE:-26.04}" IMAGE="${IMAGE:-24.04}"
BASE="${BASE:-ubu2604}" BASE="${BASE:-ubu2404}"
# cloud-init controls # cloud-init controls
CLOUD_INIT_FILE="${CLOUD_INIT_FILE:-}" CLOUD_INIT_FILE="${CLOUD_INIT_FILE:-}"
RPIOS_CLOUD_INIT_FILE="${RPIOS_CLOUD_INIT_FILE:-$SCRIPT_DIR/cloud-init-rpios-like-arm64.yaml}" RPIOS_CLOUD_INIT_FILE="${RPIOS_CLOUD_INIT_FILE:-$SCRIPT_DIR/cloud-init-rpios-like-arm64.yaml}"
@ -101,7 +100,6 @@ Aliases:
--test-pr N (same as --run-pr N) --test-pr N (same as --run-pr N)
Image shortcuts: Image shortcuts:
--debian-13 Use Debian 13 cloud image; sets IMAGE=\$DEBIAN13_IMAGE_URL and BASE=deb13 --debian-13 Use Debian 13 cloud image; sets IMAGE=\$DEBIAN13_IMAGE_URL and BASE=deb13
--ubuntu-release VER Use specific Ubuntu release (e.g., 24.04, 26.04); sets IMAGE and BASE dynamically
--both-distros Run both Ubuntu + Debian 13 (COUNT=N => N + N VMs) --both-distros Run both Ubuntu + Debian 13 (COUNT=N => N + N VMs)
--first-ubuntu With --both-distros: run all Ubuntu first, then Debian --first-ubuntu With --both-distros: run all Ubuntu first, then Debian
--first-debian With --both-distros: run all Debian first, then Ubuntu --first-debian With --both-distros: run all Debian first, then Ubuntu
@ -134,12 +132,6 @@ while [[ $# -gt 0 ]]; do
[[ $# -lt 2 ]] && { echo "[ERROR] --cloud-init needs a file path"; exit 2; } [[ $# -lt 2 ]] && { echo "[ERROR] --cloud-init needs a file path"; exit 2; }
CLOUD_INIT_FILE="$2"; shift 2 ;; CLOUD_INIT_FILE="$2"; shift 2 ;;
--ubuntu-release)
[[ $# -lt 2 ]] && { echo "[ERROR] --ubuntu-release needs a version (e.g. 24.04 or 26.04)"; exit 2; }
IMAGE="$2"
BASE="ubu${2//./}"
shift 2 ;;
--debian-13) --debian-13)
DEBIAN13_ONLY=1 DEBIAN13_ONLY=1
IMAGE="$DEBIAN13_IMAGE_URL" IMAGE="$DEBIAN13_IMAGE_URL"
@ -239,9 +231,7 @@ else
DEB_BASE="deb13" DEB_BASE="deb13"
fi fi
LOG_MONTH="$(date +%Y%m)" LOGROOT="${LOGROOT:-iiab_multipass_runs_$(date +%Y%m%d)}"
LOG_RUN="$(date +%Y%m%d_%H%M%S)"
LOGROOT="${LOGROOT:-iiab_multipass_runs_${LOG_MONTH}/${LOG_RUN}}"
mkdir -p "$LOGROOT" mkdir -p "$LOGROOT"
echo "[INFO] Logging files stored at: $LOGROOT" echo "[INFO] Logging files stored at: $LOGROOT"
@ -356,12 +346,15 @@ clean_targets() {
local list local list
list="$(multipass list 2>/dev/null | awk 'NR>1 {print $1}' || true)" list="$(multipass list 2>/dev/null | awk 'NR>1 {print $1}' || true)"
# Regex modified to catch any Ubuntu base (ubu followed by 4 digits) or the Debian base
if [[ "$BOTH_DISTROS" == "1" ]]; then if [[ "$BOTH_DISTROS" == "1" ]]; then
local ubu_re deb_re
ubu_re="$(re_escape "$UBU_BASE")"
deb_re="$(re_escape "$DEB_BASE")" deb_re="$(re_escape "$DEB_BASE")"
printf '%s\n' "$list" | grep -E "^(ubu[0-9]{4}|${deb_re})-[0-9]+$" || true printf '%s\n' "$list" | grep -E "^(${ubu_re}|${deb_re})-[0-9]+$" || true
else else
printf '%s\n' "$list" | grep -E "^(ubu[0-9]{4}|deb13|rpios-d13)-[0-9]+$" || true local base_re
base_re="$(re_escape "$BASE")"
printf '%s\n' "$list" | grep -E "^${base_re}-[0-9]+$" || true
fi fi
} }

View File

@ -1,212 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# gen_simple_inplace.sh
# Moves wheels from a pool to their corresponding PEP 503 directory and generates the index.html files.
# Standardizes web permissions (755 dir, 644 files)
die() { echo "ERROR: $*" >&2; exit 1; }
FINAL_REPO=""
W_POOL=""
ONLY_PKG=""
NO_TOP=0
DO_VERIFY=0
VERIFY_ONLY=0
while [ $# -gt 0 ]; do
case "$1" in
--final-repo|--simple-dir) FINAL_REPO="${2:-}"; shift 2 ;;
--w-pool) W_POOL="${2:-}"; shift 2 ;;
--pkg) ONLY_PKG="${2:-}"; shift 2 ;;
--no-top) NO_TOP=1; shift ;;
--verify) DO_VERIFY=1; shift ;;
--verify-only) VERIFY_ONLY=1; DO_VERIFY=1; shift ;;
-h|--help)
cat <<'EOF'
Usage:
./simple_builder.sh --final-repo ~/simple/ --w-pool ~/wheel_pool/
./simple_builder.sh --final-repo ~/simple/ --pkg cffi
./simple_builder.sh --final-repo ~/simple/ --verify
Options:
--final-repo Path to the repository's root directory (e.g., ~/simple/)
--w-pool Path to the folder containing new wheels to accommodate (e.g., ~/wheel_pool/)
--pkg Update/Verify only a specific package
--no-top Do not regenerate the main /simple/index.html file
--verify Verify that the href attributes exist and the SHA256 attributes match
--verify-only Only verify (does not regenerate any index.html files or move wheels)
EOF
exit 0
;;
*) die "Unknown arg: $1" ;;
esac
done
[ -n "$FINAL_REPO" ] || die "--final-repo is required"
[ -d "$FINAL_REPO" ] || die "It is not a valid directory: $FINAL_REPO"
# Strict PEP 503 normalization
normalize_pkg_name() {
echo "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[-_.]+/-/g'
}
# 1. Process the Pool of Wheels
if [ "$VERIFY_ONLY" -eq 0 ] && [ -n "$W_POOL" ]; then
[ -d "$W_POOL" ] || die "The pool is not a valid directory: $W_POOL"
echo "=> Scanning pool of wheels in: $W_POOL"
while IFS= read -r -d '' whl; do
filename="$(basename "$whl")"
# The distribution name is everything that comes before the first hyphen (-)
raw_dist="${filename%%-*}"
norm_pkg="$(normalize_pkg_name "$raw_dist")"
dest_dir="$FINAL_REPO/$norm_pkg"
# Create the folder and ensure its web permissions
mkdir -p "$dest_dir"
chmod 755 "$dest_dir"
echo " -> Moving: $filename to the directory /$norm_pkg/"
mv -f "$whl" "$dest_dir/"
# Ensure that the newly moved file has web permissions
chmod 644 "$dest_dir/$filename"
done < <(find "$W_POOL" -maxdepth 1 -type f -name '*.whl' -print0)
echo "=> Moving complete."
fi
verify_pkg_index() {
local pkgdir="$1"
local pkgname="$2"
local idx="$pkgdir/index.html"
local errs=0
if [ ! -f "$idx" ]; then
echo "VERIFY FAIL [$pkgname]: index.html is missing" >&2
return 1
fi
while IFS= read -r href; do
[ -n "$href" ] || continue
local file="${href%%#sha256=*}"
local want="${href##*#sha256=}"
if [ ! -f "$pkgdir/$file" ]; then
echo "VERIFY FAIL [$pkgname]: file is missing: $file" >&2
errs=$((errs+1))
continue
fi
local got
got="$(sha256sum "$pkgdir/$file" | awk '{print $1}')"
if [ "$got" != "$want" ]; then
echo "VERIFY FAIL [$pkgname]: sha256 mismatch for $file" >&2
echo " expected: $want" >&2
echo " got: $got" >&2
errs=$((errs+1))
fi
done < <(
grep -oE 'href="[^"]+"' "$idx" \
| sed -E 's/^href="(.*)"$/\1/' \
| grep -E '#sha256=' || true
)
if [ "$errs" -gt 0 ]; then
echo "VERIFY FAIL [$pkgname]: $errs error(s)" >&2
return 1
fi
echo "VERIFY OK [$pkgname]"
return 0
}
write_pkg_index() {
local pkgdir="$1"
local pkgname="$2"
local idx="$pkgdir/index.html"
# Ensure directory and existing wheels permissions prior to this script
chmod 755 "$pkgdir"
find "$pkgdir" -maxdepth 1 -type f -name '*.whl' -exec chmod 644 {} +
mapfile -t files < <(find "$pkgdir" -maxdepth 1 -type f -name '*.whl' -printf '%f\n' | sort)
[ "${#files[@]}" -gt 0 ] || return 0
{
echo "<!doctype html>"
echo "<html><head><meta charset=\"utf-8\"><title>${pkgname}</title></head><body>"
for bn in "${files[@]}"; do
sha="$(sha256sum "$pkgdir/$bn" | awk '{print $1}')"
printf '<a href="%s#sha256=%s">%s</a><br/>\n' "$bn" "$sha" "$bn"
done
echo "</body></html>"
} > "$idx"
# Ensure generated index permissions
chmod 644 "$idx"
}
# Determine package directories
pkg_dirs=()
if [ -n "$ONLY_PKG" ]; then
ONLY_PKG="$(normalize_pkg_name "$ONLY_PKG")"
[ -d "$FINAL_REPO/$ONLY_PKG" ] || die "Package directory not found: $FINAL_REPO/$ONLY_PKG"
pkg_dirs+=("$FINAL_REPO/$ONLY_PKG")
else
while IFS= read -r d; do
pkg_dirs+=("$d")
done < <(find "$FINAL_REPO" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | sort)
fi
if [ "$VERIFY_ONLY" -eq 0 ]; then
echo "=> Generating indexes (index.html) and standardizing permissions..."
# Ensure root directory permissions
chmod 755 "$FINAL_REPO"
for d in "${pkg_dirs[@]}"; do
pkg="$(basename "$d")"
write_pkg_index "$d" "$pkg"
done
# Main Index
if [ "$NO_TOP" -eq 0 ] && [ -z "$ONLY_PKG" ]; then
top="$FINAL_REPO/index.html"
{
echo "<!doctype html>"
echo "<html><head><meta charset=\"utf-8\"><title>Simple Index</title></head><body>"
for d in "${pkg_dirs[@]}"; do
pkg="$(basename "$d")"
if find "$d" -maxdepth 1 -type f -name '*.whl' | grep -q .; then
printf '<a href="./%s/">%s</a><br/>\n' "$pkg" "$pkg"
fi
done
echo "</body></html>"
} > "$top"
# Main index permissions
chmod 644 "$top"
fi
fi
if [ "$DO_VERIFY" -eq 1 ]; then
echo "=> Verifying integrity..."
vfail=0
for d in "${pkg_dirs[@]}"; do
pkg="$(basename "$d")"
if ! verify_pkg_index "$d" "$pkg"; then
vfail=1
fi
done
if [ "$vfail" -ne 0 ]; then
die "Verification failed for one or more packages."
fi
fi
if [ "$VERIFY_ONLY" -eq 1 ]; then
echo "=> OK: indexes verified in: $FINAL_REPO"
else
echo "=> OK: process finished successfully in: $FINAL_REPO"
fi