Compare commits
13 Commits
main
...
replace_sh
| Author | SHA1 | Date |
|---|---|---|
|
|
c01e28ec58 | |
|
|
53f7a97ee5 | |
|
|
d9061203b4 | |
|
|
756c3342fd | |
|
|
df7c18a9af | |
|
|
2849b32d1e | |
|
|
645fbc276a | |
|
|
120785d2b7 | |
|
|
c3b64fc3a5 | |
|
|
b683a4f518 | |
|
|
c7d0620f7a | |
|
|
be870e1a9d | |
|
|
167baf23f3 |
|
|
@ -0,0 +1,28 @@
|
|||
# shellcheck shell=bash
|
||||
# Module file (no shebang). Bundled by build_bundle.sh
|
||||
|
||||
RED="\033[31m"; YEL="\033[33m"; GRN="\033[32m"; BLU="\033[34m"; RST="\033[0m"; BOLD="\033[1m"
|
||||
|
||||
log() { printf "${BLU}[iiab]${RST} %s\n" "$*"; }
|
||||
ok() { printf "${GRN}[iiab]${RST} %s\n" "$*"; }
|
||||
warn() { printf "${YEL}[iiab] WARNING:${RST} %s\n" "$*" >&2; }
|
||||
warn_red() { printf "${RED}${BOLD}[iiab] WARNING:${RST} %s\n" "$*" >&2; }
|
||||
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
need() { have "$1" || return 1; }
|
||||
die() { echo "[!] $*" >&2; exit 1; }
|
||||
|
||||
# -------------------------
|
||||
# Global defaults (may be overridden via environment)
|
||||
# -------------------------
|
||||
STATE_DIR="${STATE_DIR:-${HOME}/.iiab-android}"
|
||||
ADB_STATE_DIR="${ADB_STATE_DIR:-${STATE_DIR}/adbw_pair}"
|
||||
LOG_DIR="${LOG_DIR:-${STATE_DIR}/logs}"
|
||||
|
||||
HOST="${HOST:-127.0.0.1}"
|
||||
CONNECT_PORT="${CONNECT_PORT:-}"
|
||||
TIMEOUT_SECS="${TIMEOUT_SECS:-180}"
|
||||
|
||||
# Defaults used by ADB flows / logging / misc
|
||||
CLEANUP_OFFLINE="${CLEANUP_OFFLINE:-1}"
|
||||
DEBUG="${DEBUG:-0}"
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# shellcheck shell=bash
|
||||
# Module file (no shebang). Bundled by build_bundle.sh
|
||||
|
||||
# -------------------------
|
||||
# Logging
|
||||
# -------------------------
|
||||
LOG_ENABLED=1
|
||||
LOG_FILE="" # if empty, auto-generate under $LOG_DIR
|
||||
LOG_KEEP=20 # keep last N logs
|
||||
|
||||
rotate_logs() {
|
||||
[[ -d "${LOG_DIR:-}" ]] || return 0
|
||||
local n=$((LOG_KEEP + 1))
|
||||
while IFS= read -r f; do
|
||||
[[ -n "${f:-}" ]] || continue
|
||||
[[ -n "${LOG_FILE:-}" && "$f" == "$LOG_FILE" ]] && continue
|
||||
rm -f -- "$f" 2>/dev/null || true
|
||||
done < <(ls -1t "$LOG_DIR"/*.log 2>/dev/null | tail -n +"$n" || true)
|
||||
}
|
||||
|
||||
setup_logging() {
|
||||
# Save original console fds so interactive tools still work after we redirect stdout/stderr.
|
||||
exec 3>&1 4>&2
|
||||
|
||||
# If logging is disabled, still allow --debug to trace to console.
|
||||
if [[ "${LOG_ENABLED:-1}" -ne 1 ]]; then
|
||||
if [[ "${DEBUG:-0}" -eq 1 ]]; then
|
||||
set -x
|
||||
ok "Debug trace enabled (bash -x) -> console (logging disabled)"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
||||
|
||||
if [[ -z "${LOG_FILE:-}" ]]; then
|
||||
LOG_FILE="${LOG_DIR}/0_termux-setupv2.$(date +%Y%m%d-%H%M%S).log"
|
||||
else
|
||||
# Best-effort: ensure parent dir exists
|
||||
mkdir -p "$(dirname -- "$LOG_FILE")" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Header (best-effort)
|
||||
local started
|
||||
started="$(date -Is 2>/dev/null || date 2>/dev/null || echo "?")"
|
||||
{
|
||||
echo "=== iiab termux setup v2 log ==="
|
||||
echo "Started: $started"
|
||||
echo "Script: $0"
|
||||
echo "Args: ${*:-}"
|
||||
echo "Android SDK=${ANDROID_SDK:-?} Release=${ANDROID_REL:-?}"
|
||||
echo "PWD: $(pwd 2>/dev/null || true)"
|
||||
echo "================================"
|
||||
} >>"$LOG_FILE" 2>/dev/null || true
|
||||
|
||||
# Best-effort: restrict log readability (may include debug/xtrace)
|
||||
chmod 600 "$LOG_FILE" 2>/dev/null || true
|
||||
|
||||
rotate_logs
|
||||
|
||||
# Duplicate stdout/stderr to console + log (strip ANSI in log)
|
||||
exec \
|
||||
> >(tee >(sed -E 's/\x1B\[[0-9;]*[ -/]*[@-~]//g' >>"$LOG_FILE")) \
|
||||
2> >(tee >(sed -E 's/\x1B\[[0-9;]*[ -/]*[@-~]//g' >>"$LOG_FILE") >&2)
|
||||
|
||||
ok "Logging to: $LOG_FILE"
|
||||
|
||||
# If --debug, send xtrace only to log
|
||||
if [[ "${DEBUG:-0}" -eq 1 ]]; then
|
||||
exec 9>>"$LOG_FILE"
|
||||
export BASH_XTRACEFD=9
|
||||
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
||||
set -x
|
||||
ok "Debug trace enabled (bash -x) -> log only"
|
||||
fi
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
# shellcheck shell=bash
|
||||
# Module file (no shebang). Bundled by build_bundle.sh
|
||||
|
||||
# Termux apt options (avoid conffile prompts)
|
||||
TERMUX_APT_OPTS=( "-y" "-o" "Dpkg::Options::=--force-confdef" "-o" "Dpkg::Options::=--force-confold" )
|
||||
termux_apt() { apt-get "${TERMUX_APT_OPTS[@]}" "$@"; }
|
||||
|
||||
# -------------------------
|
||||
# Android info
|
||||
# -------------------------
|
||||
get_android_sdk() { getprop ro.build.version.sdk 2>/dev/null || true; }
|
||||
get_android_release() { getprop ro.build.version.release 2>/dev/null || true; }
|
||||
ANDROID_SDK="$(get_android_sdk)"
|
||||
ANDROID_REL="$(get_android_release)"
|
||||
|
||||
# -------------------------
|
||||
# Wakelock (Termux:API)
|
||||
# -------------------------
|
||||
WAKELOCK_HELD=0
|
||||
acquire_wakelock() {
|
||||
if have termux-wake-lock; then
|
||||
if termux-wake-lock; then
|
||||
WAKELOCK_HELD=1
|
||||
ok "Wakelock acquired (termux-wake-lock)."
|
||||
else
|
||||
warn "Failed to acquire wakelock (termux-wake-lock)."
|
||||
fi
|
||||
else
|
||||
warn "termux-wake-lock not available. Install: pkg install termux-api + Termux:API app."
|
||||
fi
|
||||
}
|
||||
release_wakelock() {
|
||||
if [[ "$WAKELOCK_HELD" -eq 1 ]] && have termux-wake-unlock; then
|
||||
termux-wake-unlock || true
|
||||
ok "Wakelock released (termux-wake-unlock)."
|
||||
fi
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# One-time repo selector
|
||||
# -------------------------
|
||||
step_termux_repo_select_once() {
|
||||
local stamp="$STATE_DIR/stamp.termux_repo_selected"
|
||||
[[ -f "$stamp" ]] && return 0
|
||||
if ! have termux-change-repo; then
|
||||
warn "termux-change-repo not found; skipping mirror selection."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -r /dev/tty ]]; then
|
||||
printf "\n${YEL}[iiab] One-time setup:${RST} Select a nearby Termux repository mirror for faster downloads.\n" >&2
|
||||
local ans="Y"
|
||||
printf "[iiab] Launch termux-change-repo now? [Y/n]: " > /dev/tty
|
||||
if ! read -r ans < /dev/tty; then
|
||||
warn "No interactive TTY available; skipping mirror selection (run script directly to be prompted)."
|
||||
return 0
|
||||
fi
|
||||
ans="${ans:-Y}"
|
||||
if [[ "$ans" =~ ^[Yy]$ ]]; then
|
||||
# Logging redirects stdout/stderr to pipes, which can break the UI.
|
||||
# Run it using /dev/tty and the original console fds (3/4).
|
||||
if [[ -r /dev/tty ]]; then
|
||||
termux-change-repo </dev/tty >&3 2>&4 || true
|
||||
else
|
||||
termux-change-repo || true
|
||||
fi
|
||||
ok "Mirror selection completed (or skipped inside the UI)."
|
||||
else
|
||||
warn "Mirror selection skipped by user."
|
||||
fi
|
||||
date > "$stamp"
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "No /dev/tty available; skipping mirror selection."
|
||||
return 0
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# Baseline packages
|
||||
# -------------------------
|
||||
step_termux_base() {
|
||||
local stamp="$STATE_DIR/stamp.termux_base"
|
||||
if [[ -f "$stamp" ]]; then
|
||||
ok "Termux baseline already prepared (stamp found)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Updating Termux packages (noninteractive) and installing baseline dependencies..."
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
termux_apt update || true
|
||||
termux_apt upgrade || true
|
||||
|
||||
termux_apt install \
|
||||
ca-certificates \
|
||||
curl \
|
||||
coreutils \
|
||||
grep \
|
||||
sed \
|
||||
gawk \
|
||||
openssh \
|
||||
proot proot-distro \
|
||||
android-tools \
|
||||
termux-api \
|
||||
|| true
|
||||
|
||||
if have proot-distro && \
|
||||
have adb && have termux-notification && \
|
||||
have termux-dialog; then
|
||||
ok "Termux baseline ready."
|
||||
date > "$stamp"
|
||||
else
|
||||
warn_red "Baseline incomplete (missing proot-distro/adb/termux-notification). Not stamping; rerun later."
|
||||
fi
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
# shellcheck shell=bash
|
||||
# Module file (no shebang). Bundled by build_bundle.sh
|
||||
|
||||
# -------------------------
|
||||
# Debian bootstrap
|
||||
# -------------------------
|
||||
debian_exists() {
|
||||
have proot-distro || return 1
|
||||
proot-distro login debian -- true >/dev/null 2>&1
|
||||
}
|
||||
|
||||
ensure_proot_distro() {
|
||||
if have proot-distro; then return 0; fi
|
||||
warn "proot-distro not found; attempting to install..."
|
||||
termux_apt install proot-distro || true
|
||||
have proot-distro
|
||||
}
|
||||
|
||||
proot_install_debian_safe() {
|
||||
local out rc
|
||||
set +e
|
||||
out="$(proot-distro install debian 2>&1)"
|
||||
rc=$?
|
||||
set -e
|
||||
if [[ $rc -eq 0 ]]; then return 0; fi
|
||||
if echo "$out" | grep -qi "already installed"; then
|
||||
warn "Debian is already installed; continuing."
|
||||
return 0
|
||||
fi
|
||||
printf "%s\n" "$out" >&2
|
||||
return $rc
|
||||
}
|
||||
|
||||
step_debian_bootstrap_default() {
|
||||
if ! ensure_proot_distro; then
|
||||
warn "Unable to ensure proot-distro; skipping Debian bootstrap."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$RESET_DEBIAN" -eq 1 ]]; then
|
||||
warn "Reset requested: reinstalling Debian (clean environment)..."
|
||||
if proot-distro help 2>/dev/null | grep -qE '\breset\b'; then
|
||||
proot-distro reset debian || true
|
||||
else
|
||||
if debian_exists; then proot-distro remove debian || true; fi
|
||||
proot_install_debian_safe || true
|
||||
fi
|
||||
else
|
||||
if debian_exists; then
|
||||
ok "Debian already present in proot-distro. Not reinstalling."
|
||||
else
|
||||
log "Installing Debian (proot-distro install debian)..."
|
||||
proot_install_debian_safe || true
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Installing minimal tools inside Debian (noninteractive)..."
|
||||
if ! debian_exists; then
|
||||
warn_red "Debian is not available in proot-distro (install may have failed). Rerun later."
|
||||
return 0
|
||||
fi
|
||||
local rc=0
|
||||
set +e
|
||||
proot-distro login debian -- bash -lc '
|
||||
set -e
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold \
|
||||
install ca-certificates curl coreutils
|
||||
'
|
||||
rc=$?
|
||||
set -e
|
||||
if [[ $rc -eq 0 ]]; then
|
||||
ok "Debian bootstrap complete."
|
||||
else
|
||||
warn_red "Debian bootstrap incomplete (inner apt-get failed, rc=$rc)."
|
||||
warn "You can retry later with: proot-distro login debian"
|
||||
fi
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
# shellcheck shell=bash
|
||||
# Module file (no shebang). Bundled by build_bundle.sh
|
||||
|
||||
# -------------------------
|
||||
# Termux:API notifications + prompts
|
||||
# -------------------------
|
||||
NOTIF_BASE_ID=9400
|
||||
NOTIF_SEQ=0
|
||||
LAST_NOTIF_ID=""
|
||||
|
||||
# Termux:API sanity check (notifications)
|
||||
termux_api_ready() {
|
||||
have termux-notification || return 1
|
||||
|
||||
# Quick probe: some setups fail until Termux:API app is installed/allowed.
|
||||
local msg="iiab test notification"
|
||||
if ! termux-notification --id "$NOTIF_BASE_ID" --title "iiab" --content "$msg" --priority max --sound >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
if have termux-notification-remove; then
|
||||
termux-notification-remove "$NOTIF_BASE_ID" >/dev/null 2>&1 || true
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
sanitize_timeout() {
|
||||
# Ensure TIMEOUT_SECS is a positive integer
|
||||
if ! [[ "${TIMEOUT_SECS:-}" =~ ^[0-9]+$ ]]; then
|
||||
warn "Invalid --timeout='${TIMEOUT_SECS:-}'. Falling back to 180."
|
||||
TIMEOUT_SECS=180
|
||||
elif (( TIMEOUT_SECS < 5 )); then
|
||||
warn "Very low --timeout=${TIMEOUT_SECS}. Forcing minimum 5 seconds."
|
||||
TIMEOUT_SECS=5
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_notif() {
|
||||
have termux-notification-remove || return 0
|
||||
termux-notification-remove "$NOTIF_BASE_ID" >/dev/null 2>&1 || true
|
||||
if [[ -n "${LAST_NOTIF_ID:-}" ]]; then
|
||||
termux-notification-remove "$LAST_NOTIF_ID" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
notify_ask_one() {
|
||||
# args: key title content
|
||||
local key="$1" title="$2" content="$3"
|
||||
local out="$ADB_STATE_DIR/$key.reply"
|
||||
rm -f "$out"
|
||||
|
||||
# Fresh notification each time + sound (use a new ID so Android plays sound each time)
|
||||
local nid
|
||||
nid=$((NOTIF_BASE_ID + 1 + NOTIF_SEQ))
|
||||
NOTIF_SEQ=$((NOTIF_SEQ + 1))
|
||||
LAST_NOTIF_ID="$nid"
|
||||
|
||||
if have termux-notification-remove; then
|
||||
termux-notification-remove "$nid" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Direct reply: Termux:API injects the user input into $REPLY for the action.
|
||||
# Write it to a known file, then the main loop reads it.
|
||||
local action
|
||||
action="sh -lc 'umask 077; printf \"%s\" \"\$REPLY\" > \"${out}\"'"
|
||||
|
||||
termux-notification \
|
||||
--id "$nid" \
|
||||
--ongoing \
|
||||
--priority max \
|
||||
--title "$title" \
|
||||
--content "$content" \
|
||||
--sound \
|
||||
--button1 "Answer" \
|
||||
--button1-action "$action" \
|
||||
|| return 1
|
||||
|
||||
local start now reply
|
||||
start="$(date +%s)"
|
||||
|
||||
while true; do
|
||||
if [[ -f "$out" ]]; then
|
||||
reply="$(tr -d '\r\n' < "$out" 2>/dev/null || true)"
|
||||
rm -f "$out" >/dev/null 2>&1 || true
|
||||
if have termux-notification-remove; then
|
||||
termux-notification-remove "$nid" >/dev/null 2>&1 || true
|
||||
fi
|
||||
printf '%s' "$reply"
|
||||
return 0
|
||||
fi
|
||||
|
||||
now="$(date +%s)"
|
||||
if (( now - start >= TIMEOUT_SECS )); then
|
||||
if have termux-notification-remove; then
|
||||
termux-notification-remove "$nid" >/dev/null 2>&1 || true
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
ask_port_5digits() {
|
||||
# args: key title
|
||||
local key="$1" title="$2" v=""
|
||||
while true; do
|
||||
v="$(notify_ask_one "$key" "$title" "(5 digits)")" || return 1
|
||||
v="${v//[[:space:]]/}"
|
||||
[[ "$v" =~ ^[0-9]{5}$ ]] || continue
|
||||
echo "$v"
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
ask_code_6digits() {
|
||||
local v=""
|
||||
while true; do
|
||||
v="$(notify_ask_one code "PAIR CODE" "(6 digits)")" || return 1
|
||||
v="${v//[[:space:]]/}"
|
||||
[[ -n "$v" ]] || continue
|
||||
[[ "$v" =~ ^[0-9]+$ ]] || continue
|
||||
# Normalize to 6 digits (allow missing leading zeros)
|
||||
if ((${#v} < 6)); then
|
||||
v="$(printf "%06d" "$v")"
|
||||
fi
|
||||
[[ "$v" =~ ^[0-9]{6}$ ]] || continue
|
||||
echo "$v"
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
# shellcheck shell=bash
|
||||
# Module file (no shebang). Bundled by build_bundle.sh
|
||||
|
||||
# -------------------------
|
||||
# ADB wireless pair/connect wizard
|
||||
# -------------------------
|
||||
|
||||
# Local stamp so we can detect "connect-only" misuse after reinstall/clear-data.
|
||||
ADB_PAIRED_STAMP="${ADB_STATE_DIR}/stamp.adb_paired"
|
||||
|
||||
adb_hostkey_fingerprint() {
|
||||
# Returns a stable fingerprint for THIS Termux install's adb host key.
|
||||
local pub="${HOME}/.android/adbkey.pub"
|
||||
[[ -r "$pub" ]] || return 1
|
||||
if have sha256sum; then
|
||||
sha256sum "$pub" | awk '{print $1}'
|
||||
elif have shasum; then
|
||||
shasum -a 256 "$pub" | awk '{print $1}'
|
||||
elif have openssl; then
|
||||
openssl dgst -sha256 "$pub" 2>/dev/null | awk '{print $2}'
|
||||
elif have md5sum; then
|
||||
md5sum "$pub" | awk '{print $1}'
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
adb_stamp_write() {
|
||||
# args: mode serial
|
||||
local mode="$1" serial="$2" fp=""
|
||||
fp="$(adb_hostkey_fingerprint 2>/dev/null || true)"
|
||||
{
|
||||
echo "ts=$(date -Is 2>/dev/null || date || true)"
|
||||
echo "mode=${mode}"
|
||||
echo "host=${HOST}"
|
||||
echo "serial=${serial}"
|
||||
echo "connect_port=${CONNECT_PORT:-}"
|
||||
echo "hostkey_fp=${fp}"
|
||||
} >"$ADB_PAIRED_STAMP" 2>/dev/null || true
|
||||
chmod 600 "$ADB_PAIRED_STAMP" 2>/dev/null || true
|
||||
}
|
||||
|
||||
adb_stamp_read_fp() {
|
||||
[[ -r "$ADB_PAIRED_STAMP" ]] || return 1
|
||||
sed -n 's/^hostkey_fp=//p' "$ADB_PAIRED_STAMP" 2>/dev/null | head -n 1
|
||||
}
|
||||
|
||||
adb_warn_connect_only_if_suspicious() {
|
||||
# Called only in connect-only flows.
|
||||
local cur_fp old_fp
|
||||
cur_fp="$(adb_hostkey_fingerprint 2>/dev/null || true)"
|
||||
old_fp="$(adb_stamp_read_fp 2>/dev/null || true)"
|
||||
|
||||
if [[ ! -f "$ADB_PAIRED_STAMP" ]]; then
|
||||
warn "connect-only assumes THIS Termux install has been paired before."
|
||||
warn "No local pairing stamp found ($ADB_PAIRED_STAMP)."
|
||||
warn "If you reinstalled Termux / cleared data / changed user, you must re-pair (run: --adb-only)."
|
||||
[[ -n "$cur_fp" ]] && warn "Current ADB hostkey fingerprint: ${cur_fp:0:12}..."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "$old_fp" && -n "$cur_fp" && "$old_fp" != "$cur_fp" ]]; then
|
||||
warn_red "ADB host key changed since last pairing stamp."
|
||||
warn "Old fingerprint: ${old_fp:0:12}... Current: ${cur_fp:0:12}..."
|
||||
warn "Android Wireless debugging -> Paired devices: remove the old entry, then run: --adb-only"
|
||||
fi
|
||||
}
|
||||
|
||||
adb_connect_verify() {
|
||||
# args: serial (HOST:PORT)
|
||||
local serial="$1" out rc start now state
|
||||
set +e
|
||||
out="$(adb connect "$serial" 2>&1)"
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
# Always verify via `adb devices` (adb may exit 0 even on failure).
|
||||
start="$(date +%s)"
|
||||
while true; do
|
||||
state="$(adb_device_state "$serial" || true)"
|
||||
[[ "$state" == "device" ]] && { printf '%s\n' "$out"; return 0; }
|
||||
now="$(date +%s)"
|
||||
(( now - start >= 5 )) && break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
warn_red "adb connect did not result in a usable device entry for: $serial (state='${state:-none}')."
|
||||
warn "adb connect output: ${out:-<none>}"
|
||||
warn "If you recently reinstalled Termux/cleared data, the phone may show an OLD paired device. Remove it and re-pair."
|
||||
return 1
|
||||
}
|
||||
|
||||
cleanup_offline_loopback() {
|
||||
local keep_serial="$1" # e.g. 127.0.0.1:41313
|
||||
local serial state rest
|
||||
while read -r serial state rest; do
|
||||
[[ -n "${serial:-}" ]] || continue
|
||||
[[ "$serial" == ${HOST}:* ]] || continue
|
||||
[[ "$state" == "offline" ]] || continue
|
||||
[[ "$serial" == "$keep_serial" ]] && continue
|
||||
adb disconnect "$serial" >/dev/null 2>&1 || true
|
||||
done < <(adb devices 2>/dev/null | tail -n +2 | sed '/^[[:space:]]*$/d')
|
||||
}
|
||||
|
||||
adb_pair_connect() {
|
||||
need adb || die "Missing adb. Install: pkg install android-tools"
|
||||
|
||||
# Only require Termux:API when we will prompt the user
|
||||
if [[ "$ONLY_CONNECT" != "1" || -z "${CONNECT_PORT:-}" ]]; then
|
||||
termux_api_ready || die "Termux:API not ready."
|
||||
fi
|
||||
|
||||
echo "[*] adb: $(adb version | head -n 1)"
|
||||
adb start-server >/dev/null 2>&1 || true
|
||||
|
||||
if [[ "$ONLY_CONNECT" == "1" ]]; then
|
||||
adb_warn_connect_only_if_suspicious
|
||||
if [[ -n "$CONNECT_PORT" ]]; then
|
||||
CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}"
|
||||
[[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid CONNECT PORT (must be 5 digits): '$CONNECT_PORT'"
|
||||
else
|
||||
echo "[*] Asking CONNECT PORT..."
|
||||
CONNECT_PORT="$(ask_port_5digits connect "CONNECT PORT")" || die "Timeout waiting CONNECT PORT."
|
||||
fi
|
||||
|
||||
local serial="${HOST}:${CONNECT_PORT}"
|
||||
adb disconnect "$serial" >/dev/null 2>&1 || true
|
||||
echo "[*] adb connect $serial"
|
||||
adb_connect_verify "$serial" >/dev/null || die "adb connect failed to $serial. Verify Wireless debugging is enabled, and pairing exists for THIS Termux install."
|
||||
|
||||
if [[ "$CLEANUP_OFFLINE" == "1" ]]; then
|
||||
cleanup_offline_loopback "$serial"
|
||||
fi
|
||||
|
||||
echo "[*] Devices:"
|
||||
adb devices -l
|
||||
|
||||
echo "[*] ADB check (shell):"
|
||||
adb -s "$serial" shell sh -lc 'echo "it worked: adb shell is working"; id' || true
|
||||
adb_stamp_write "connect-only" "$serial"
|
||||
|
||||
cleanup_notif
|
||||
ok "ADB connected (connect-only): $serial"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "$CONNECT_PORT" ]]; then
|
||||
CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}"
|
||||
[[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid --connect-port (must be 5 digits): '$CONNECT_PORT'"
|
||||
else
|
||||
echo "[*] Asking CONNECT PORT..."
|
||||
CONNECT_PORT="$(ask_port_5digits connect "CONNECT PORT")" || die "Timeout waiting CONNECT PORT."
|
||||
fi
|
||||
|
||||
echo "[*] Asking PAIR PORT..."
|
||||
local pair_port
|
||||
pair_port="$(ask_port_5digits pair "PAIR PORT")" || die "Timeout waiting PAIR PORT."
|
||||
|
||||
echo "[*] Asking PAIR CODE..."
|
||||
local code
|
||||
code="$(ask_code_6digits)" || die "Timeout waiting PAIR CODE."
|
||||
|
||||
local serial="${HOST}:${CONNECT_PORT}"
|
||||
adb disconnect "$serial" >/dev/null 2>&1 || true
|
||||
|
||||
echo "[*] adb pair ${HOST}:${pair_port}"
|
||||
printf '%s\n' "$code" | adb pair "${HOST}:${pair_port}" || die "adb pair failed. Verify PAIR PORT and PAIR CODE (and that the pairing dialog is showing)."
|
||||
|
||||
echo "[*] adb connect $serial"
|
||||
adb_connect_verify "$serial" >/dev/null || die "adb connect failed after pairing. Re-check CONNECT PORT and Wireless debugging."
|
||||
|
||||
if [[ "$CLEANUP_OFFLINE" == "1" ]]; then
|
||||
cleanup_offline_loopback "$serial"
|
||||
fi
|
||||
|
||||
echo "[*] Devices:"
|
||||
adb devices -l
|
||||
|
||||
echo "[*] ADB check (shell):"
|
||||
adb -s "$serial" shell sh -lc 'echo "it worked: adb shell is working"; getprop ro.product.model; getprop ro.build.version.release' || true
|
||||
adb_stamp_write "paired" "$serial"
|
||||
|
||||
cleanup_notif
|
||||
ok "ADB connected: $serial"
|
||||
}
|
||||
|
||||
# Return state for an exact serial (e.g. "device", "offline", empty)
|
||||
adb_device_state() {
|
||||
local s="$1"
|
||||
adb devices 2>/dev/null | awk -v s="$s" 'NR>1 && $1==s {print $2; exit}'
|
||||
}
|
||||
|
||||
# Return first loopback serial in "device" state (e.g. 127.0.0.1:41313)
|
||||
adb_any_loopback_device() {
|
||||
adb devices 2>/dev/null | awk -v h="$HOST" '
|
||||
NR>1 && $2=="device" && index($1, h":")==1 {print $1; found=1; exit}
|
||||
END { exit (found ? 0 : 1) }
|
||||
'
|
||||
}
|
||||
|
||||
# Pick the loopback serial we will operate on:
|
||||
# - If CONNECT_PORT is set, require that exact HOST:PORT to be in "device" state.
|
||||
# - Otherwise, return the first loopback device.
|
||||
adb_pick_loopback_serial() {
|
||||
if [[ -n "${CONNECT_PORT:-}" ]]; then
|
||||
local p="${CONNECT_PORT//[[:space:]]/}"
|
||||
[[ "$p" =~ ^[0-9]{5}$ ]] || return 1
|
||||
local target="${HOST}:${p}"
|
||||
[[ "$(adb_device_state "$target")" == "device" ]] && { echo "$target"; return 0; }
|
||||
return 1
|
||||
fi
|
||||
adb_any_loopback_device
|
||||
}
|
||||
|
||||
# If already connected, avoid re-pairing/re-connecting prompts (useful for --all),
|
||||
# BUT only consider loopback/target connections as "already connected".
|
||||
adb_pair_connect_if_needed() {
|
||||
need adb || die "Missing adb. Install: pkg install android-tools"
|
||||
adb start-server >/dev/null 2>&1 || true
|
||||
|
||||
local serial=""
|
||||
|
||||
# If user provided a connect-port, insist on that exact target serial.
|
||||
if [[ -n "${CONNECT_PORT:-}" ]]; then
|
||||
CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}"
|
||||
[[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid --connect-port (must be 5 digits): '$CONNECT_PORT'"
|
||||
|
||||
local target="${HOST}:${CONNECT_PORT}"
|
||||
|
||||
if [[ "$(adb_device_state "$target")" == "device" ]]; then
|
||||
ok "ADB already connected to target: $target (skipping pair/connect)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try connect-only first (in case it was already paired before)
|
||||
adb connect "$target" >/dev/null 2>&1 || true
|
||||
if [[ "$(adb_device_state "$target")" == "device" ]]; then
|
||||
ok "ADB connected to target: $target (connect-only succeeded; skipping pair)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Not connected: run full wizard (pair+connect)
|
||||
adb_pair_connect
|
||||
return $?
|
||||
fi
|
||||
|
||||
# No explicit port: only skip if we already have a loopback device connected.
|
||||
if serial="$(adb_any_loopback_device 2>/dev/null)"; then
|
||||
ok "ADB already connected (loopback): $serial (skipping pair/connect)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
adb_pair_connect
|
||||
}
|
||||
|
||||
require_adb_connected() {
|
||||
need adb || { warn_red "Missing adb. Install: pkg install android-tools"; return 1; }
|
||||
adb start-server >/dev/null 2>&1 || true
|
||||
if ! adb_pick_loopback_serial >/dev/null 2>&1; then
|
||||
warn_red "No ADB device connected."
|
||||
warn "If already paired before: run --connect-only [PORT]."
|
||||
warn "Otherwise: run --adb-only to pair+connect."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
adb_loopback_serial_or_die() {
|
||||
local s
|
||||
s="$(adb_pick_loopback_serial 2>/dev/null)" || return 1
|
||||
echo "$s"
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
# shellcheck shell=bash
|
||||
# Module file (no shebang). Bundled by build_bundle.sh
|
||||
|
||||
# PPK / phantom-process checks and tuning via ADB (best-effort)
|
||||
# Moved out of 99_main.sh to keep it as an orchestrator.
|
||||
|
||||
# -------------------------
|
||||
# PPK / phantom-process tuning (best-effort)
|
||||
# -------------------------
|
||||
ppk_fix_via_adb() {
|
||||
need adb || die "Missing adb. Install: pkg install android-tools"
|
||||
|
||||
local serial
|
||||
if ! serial="$(adb_pick_loopback_serial)"; then
|
||||
CHECK_NO_ADB=1
|
||||
warn "No ADB loopback device connected (expected ${HOST}:${CONNECT_PORT:-*})."
|
||||
return 1
|
||||
fi
|
||||
ok "Using ADB device: $serial"
|
||||
|
||||
log "Setting PPK: max_phantom_processes=256"
|
||||
# Some Android versions may ignore/rename this; we don't hard-fail.
|
||||
adb -s "$serial" shell sh -lc '
|
||||
set -e
|
||||
# Persist device_config changes if supported
|
||||
if command -v device_config >/dev/null 2>&1; then
|
||||
device_config set_sync_disabled_for_tests persistent >/dev/null 2>&1 || true
|
||||
device_config put activity_manager max_phantom_processes 256 || true
|
||||
echo "dumpsys effective max_phantom_processes:"
|
||||
dumpsys activity settings 2>/dev/null | grep -i "max_phantom_processes=" | head -n 1 || true
|
||||
else
|
||||
echo "device_config not found; skipping."
|
||||
fi
|
||||
' || true
|
||||
|
||||
ok "PPK set done (best effort)."
|
||||
return 0
|
||||
}
|
||||
|
||||
# Prefer Android 14+ feature-flag override if present:
|
||||
# OFF -> true, ON -> false
|
||||
adb_get_child_restrictions_flag() {
|
||||
local serial="$1"
|
||||
adb -s "$serial" shell getprop persist.sys.fflag.override.settings_enable_monitor_phantom_procs \
|
||||
2>/dev/null | tr -d '\r' || true
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# Check readiness (best-effort)
|
||||
# -------------------------
|
||||
check_readiness() {
|
||||
# Reset exported check signals so final_advice() never sees stale values
|
||||
CHECK_NO_ADB=0
|
||||
CHECK_SDK=""
|
||||
CHECK_MON=""
|
||||
CHECK_PPK=""
|
||||
|
||||
need adb || die "Missing adb. Install: pkg install android-tools"
|
||||
adb start-server >/dev/null 2>&1 || true
|
||||
|
||||
local serial
|
||||
if ! serial="$(adb_pick_loopback_serial)"; then
|
||||
CHECK_NO_ADB=1
|
||||
# Best-effort: keep local SDK so final_advice can still warn on A12-13.
|
||||
CHECK_SDK="${ANDROID_SDK:-}"
|
||||
warn_red "No ADB device connected. Cannot run checks."
|
||||
warn "If already paired before: run --connect-only [PORT]."
|
||||
warn "Otherwise: run --adb-only to pair+connect."
|
||||
return 1
|
||||
fi
|
||||
|
||||
ok "Check using ADB device: $serial"
|
||||
|
||||
local dev_enabled sdk rel mon mon_fflag ds ppk_eff
|
||||
sdk="$(adb -s "$serial" shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\r' || true)"
|
||||
rel="$(adb -s "$serial" shell getprop ro.build.version.release 2>/dev/null | tr -d '\r' || true)"
|
||||
dev_enabled="$(adb -s "$serial" shell settings get global development_settings_enabled 2>/dev/null | tr -d '\r' || true)"
|
||||
mon_fflag="$(adb_get_child_restrictions_flag "$serial")"
|
||||
if [[ "$mon_fflag" == "true" || "$mon_fflag" == "false" ]]; then
|
||||
mon="$mon_fflag"
|
||||
else
|
||||
mon="$(adb -s "$serial" shell settings get global settings_enable_monitor_phantom_procs 2>/dev/null | tr -d '\r' || true)"
|
||||
fi
|
||||
|
||||
# Get effective value from dumpsys (device_config get may return 'null' even when an effective value exists)
|
||||
ds="$(adb -s "$serial" shell dumpsys activity settings 2>/dev/null | tr -d '\r' || true)"
|
||||
ppk_eff="$(printf '%s\n' "$ds" | awk -F= '/max_phantom_processes=/{print $2; exit}' | tr -d '[:space:]' || true)"
|
||||
|
||||
# Export check signals for the final advice logic
|
||||
CHECK_SDK="${sdk:-}"
|
||||
CHECK_MON="${mon:-}"
|
||||
CHECK_PPK="${ppk_eff:-}"
|
||||
|
||||
log " Android release=${rel:-?} sdk=${sdk:-?}"
|
||||
|
||||
if [[ "${dev_enabled:-}" == "1" ]]; then
|
||||
ok " Developer options: enabled (development_settings_enabled=1)"
|
||||
elif [[ -n "${dev_enabled:-}" ]]; then
|
||||
warn " Developer options: unknown/disabled (development_settings_enabled=${dev_enabled})"
|
||||
else
|
||||
warn " Developer options: unreadable (permission/ROM differences)."
|
||||
fi
|
||||
|
||||
# Android 14+ only: "Disable child process restrictions" proxy flag
|
||||
if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
|
||||
if [[ "${mon:-}" == "false" ]]; then
|
||||
ok " Child restrictions: OK (monitor=false)"
|
||||
elif [[ "${mon:-}" == "true" ]]; then
|
||||
warn " Child restrictions: NOT OK (monitor=true)"
|
||||
elif [[ -n "${mon:-}" && "${mon:-}" != "null" ]]; then
|
||||
warn " Child restrictions: unknown (${mon})"
|
||||
else
|
||||
warn " Child restrictions: unreadable/absent"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Android 12-13 only: PPK matters (use effective value from dumpsys)
|
||||
if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
|
||||
if [[ "${ppk_eff:-}" =~ ^[0-9]+$ ]]; then
|
||||
if (( ppk_eff >= 256 )); then
|
||||
ok " PPK: OK (max_phantom_processes=${ppk_eff})"
|
||||
else
|
||||
warn " PPK: low (max_phantom_processes=${ppk_eff}) -> suggest: run --ppk-only"
|
||||
fi
|
||||
else
|
||||
warn " PPK: unreadable (dumpsys max_phantom_processes='${ppk_eff:-}')."
|
||||
fi
|
||||
fi
|
||||
|
||||
log " dumpsys (phantom-related):"
|
||||
printf '%s\n' "$ds" | grep -i phantom || true
|
||||
|
||||
if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
|
||||
log " Note: On A14+, max_phantom_processes is informational; rely on Child restrictions."
|
||||
fi
|
||||
if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 34 )) && [[ "${mon:-}" == "false" ]]; then
|
||||
log " Child restrictions OK."
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
self_check_android_flags() {
|
||||
have adb || return 0
|
||||
adb start-server >/dev/null 2>&1 || true
|
||||
|
||||
local serial sdk rel mon mon_fflag ds ppk_eff
|
||||
serial="$(adb_pick_loopback_serial 2>/dev/null)" || {
|
||||
warn "ADB: no loopback device connected. Tip: run --adb-only (pair+connect) or --check for more info."
|
||||
return 0
|
||||
}
|
||||
|
||||
sdk="$(adb -s "$serial" shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\r' || true)"
|
||||
rel="$(adb -s "$serial" shell getprop ro.build.version.release 2>/dev/null | tr -d '\r' || true)"
|
||||
log " Android flags (quick): release=${rel:-?} sdk=${sdk:-?} serial=$serial"
|
||||
|
||||
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
|
||||
mon_fflag="$(adb_get_child_restrictions_flag "$serial")"
|
||||
if [[ "$mon_fflag" == "true" || "$mon_fflag" == "false" ]]; then
|
||||
mon="$mon_fflag"
|
||||
else
|
||||
mon="$(adb -s "$serial" shell settings get global settings_enable_monitor_phantom_procs 2>/dev/null | tr -d '\r' || true)"
|
||||
fi
|
||||
|
||||
if [[ "$mon" == "false" ]]; then
|
||||
ok " Child restrictions: OK (monitor=false)"
|
||||
elif [[ "$mon" == "true" ]]; then
|
||||
warn " Child restrictions: NOT OK (monitor=true) -> check Developer Options"
|
||||
else
|
||||
warn " Child restrictions: unknown/unreadable (monitor='${mon:-}')"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
|
||||
ds="$(adb -s "$serial" shell dumpsys activity settings 2>/dev/null | tr -d '\r' || true)"
|
||||
ppk_eff="$(printf '%s\n' "$ds" | awk -F= '/max_phantom_processes=/{print $2; exit}' | tr -d '[:space:]' || true)"
|
||||
|
||||
if [[ "$ppk_eff" =~ ^[0-9]+$ ]]; then
|
||||
if (( ppk_eff >= 256 )); then
|
||||
ok " PPK: OK (max_phantom_processes=$ppk_eff)"
|
||||
else
|
||||
warn " PPK: low (max_phantom_processes=$ppk_eff) -> suggest: --ppk-only"
|
||||
fi
|
||||
else
|
||||
warn " PPK: unreadable (max_phantom_processes='${ppk_eff:-}')"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Avoid redundant tip when we're already in --check mode.
|
||||
if [[ "${MODE:-}" != "check" && "${MODE:-}" != "all" ]]; then
|
||||
log " Tip: run --check for full details."
|
||||
fi
|
||||
}
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
# shellcheck shell=bash
|
||||
# Module file (no shebang). Bundled by build_bundle.sh
|
||||
|
||||
# 0_termux-setupv2.sh
|
||||
# - Termux bootstrap (packages, wakelock)
|
||||
# - proot-distro + Debian bootstrap
|
||||
# - ADB wireless pair/connect via Termux:API notifications (no Shizuku)
|
||||
# - Optional PPK / phantom-process tweaks (best-effort)
|
||||
|
||||
# -------------------------
|
||||
# Defaults
|
||||
# -------------------------
|
||||
# NOTE: Core defaults live in 00_lib_common.sh to guarantee availability for all modules.
|
||||
|
||||
# Ensure state directories exist (safe even if user overrides via environment).
|
||||
mkdir -p "$STATE_DIR" "$ADB_STATE_DIR" "$LOG_DIR" 2>/dev/null || true
|
||||
|
||||
RESET_DEBIAN=0
|
||||
ONLY_CONNECT=0
|
||||
|
||||
CHECK_NO_ADB=0
|
||||
CHECK_SDK=""
|
||||
CHECK_MON=""
|
||||
CHECK_PPK=""
|
||||
|
||||
# Modes are mutually exclusive (baseline is default)
|
||||
MODE="baseline" # baseline|with-adb|adb-only|connect-only|ppk-only|check|all
|
||||
MODE_SET=0
|
||||
CONNECT_PORT_FROM="" # "", "flag", "positional"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./0_termux-setupv2.sh
|
||||
-> Termux baseline + Debian bootstrap (idempotent). No ADB prompts.
|
||||
|
||||
./0_termux-setupv2.sh --with-adb
|
||||
-> Termux baseline + Debian bootstrap + ADB pair/connect if needed (skips if already connected).
|
||||
|
||||
./0_termux-setupv2.sh --adb-only [--connect-port PORT]
|
||||
-> Only ADB pair/connect if needed (no Debian; skips if already connected).
|
||||
Tip: --connect-port skips the CONNECT PORT prompt (you’ll still be asked for PAIR PORT + PAIR CODE).
|
||||
|
||||
./0_termux-setupv2.sh --connect-only [CONNECT_PORT]
|
||||
-> Connect-only (no pairing). Use this after the device was already paired before.
|
||||
|
||||
./0_termux-setupv2.sh --ppk-only
|
||||
-> Set PPK only: max_phantom_processes=256 (requires ADB already connected).
|
||||
Android 14-16 usually achieve this via "Disable child process restrictions" in Developer Options.
|
||||
|
||||
./0_termux-setupv2.sh --check
|
||||
-> Check readiness: developer options flag (if readable),
|
||||
(Android 14+) "Disable child process restrictions" proxy flag, and (Android 12-13) PPK effective value.
|
||||
|
||||
./0_termux-setupv2.sh --all
|
||||
-> baseline + Debian + ADB pair/connect if needed + (Android 12-13 only) apply --ppk + run --check.
|
||||
|
||||
Optional:
|
||||
--connect-port 41313 (5 digits) Skip CONNECT PORT prompt used with --adb-only
|
||||
--timeout 180 Seconds to wait per prompt
|
||||
--reset-debian Reset (reinstall) Debian in proot-distro
|
||||
--no-log Disable logging
|
||||
--log-file /path/file Write logs to a specific file
|
||||
--debug Extra logs
|
||||
|
||||
Notes:
|
||||
- ADB prompts require: `pkg install termux-api` + Termux:API app installed + notification permission.
|
||||
- Wireless debugging must be enabled.
|
||||
- This script never uses adb root.
|
||||
EOF
|
||||
}
|
||||
|
||||
trap 'cleanup_notif >/dev/null 2>&1 || true; release_wakelock >/dev/null 2>&1 || true' EXIT INT TERM
|
||||
|
||||
# NOTE: Termux:API prompts live in 40_mod_termux_api.sh
|
||||
|
||||
# -------------------------
|
||||
# Self-check
|
||||
# -------------------------
|
||||
self_check() {
|
||||
log "Self-check summary:"
|
||||
log " Android release=${ANDROID_REL:-?} sdk=${ANDROID_SDK:-?}"
|
||||
|
||||
if have proot-distro; then
|
||||
log " proot-distro: present"
|
||||
log " proot-distro list:"
|
||||
proot-distro list 2>/dev/null | sed 's/^/ /' || true
|
||||
if debian_exists; then ok " Debian: present"; else warn " Debian: not present"; fi
|
||||
else
|
||||
warn " proot-distro: not present"
|
||||
fi
|
||||
|
||||
if have adb; then
|
||||
log " adb: present"
|
||||
adb devices -l 2>/dev/null | sed 's/^/ /' || true
|
||||
local serial
|
||||
# renable in need for verbose output.
|
||||
# if serial="$(adb_pick_loopback_serial 2>/dev/null)"; then
|
||||
# log " adb shell id (first device):"
|
||||
# adb -s "$serial" shell id 2>/dev/null | sed 's/^/ /' || true
|
||||
# fi
|
||||
else
|
||||
warn " adb: not present"
|
||||
fi
|
||||
# Quick Android flags check (best-effort; no prompts)
|
||||
self_check_android_flags || true
|
||||
|
||||
if have termux-wake-lock; then ok " Termux:API wakelock: available"; else warn " Termux:API wakelock: not available"; fi
|
||||
if have termux-notification; then ok " Termux:API notifications: command present"; else warn " Termux:API notifications: missing"; fi
|
||||
}
|
||||
|
||||
final_advice() {
|
||||
# 1) Android-related warnings (only meaningful if we attempted checks)
|
||||
local sdk="${CHECK_SDK:-${ANDROID_SDK:-}}"
|
||||
local adb_connected=0
|
||||
local serial="" mon="" mon_fflag=""
|
||||
|
||||
# Best-effort: detect whether an ADB loopback device is already connected.
|
||||
# (We do NOT prompt/pair here; we only check current state.)
|
||||
if have adb; then
|
||||
adb start-server >/dev/null 2>&1 || true
|
||||
if adb_pick_loopback_serial >/dev/null 2>&1; then
|
||||
adb_connected=1
|
||||
serial="$(adb_pick_loopback_serial 2>/dev/null || true)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Baseline safety gate:
|
||||
# On Android 12-13 (SDK 31-33), IIAB/proot installs can fail if PPK is low (often 32).
|
||||
# Baseline mode does NOT force ADB pairing nor run check_readiness(), so PPK may be unknown.
|
||||
# If PPK is not determined, suggest running --all BEFORE telling user to proceed to proot-distro.
|
||||
if [[ "$MODE" == "baseline" ]]; then
|
||||
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
|
||||
# If we didn't run checks, CHECK_PPK will be empty. Even with adb_connected=1, baseline
|
||||
# still doesn't populate CHECK_PPK unless user ran --check/--all.
|
||||
if [[ "${CHECK_PPK:-}" != "" && "${CHECK_PPK:-}" =~ ^[0-9]+$ ]]; then
|
||||
: # PPK determined -> ok to continue with normal advice below
|
||||
else
|
||||
warn "Android 12-13: PPK value hasn't been verified (max_phantom_processes may be low, e.g. 32)."
|
||||
warn "Before starting the IIAB install, run the complete setup so it can apply/check PPK=256; otherwise the installation may fail:"
|
||||
ok " ./0_termux-setupv2.sh --all"
|
||||
return 0
|
||||
fi
|
||||
elif [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
|
||||
# On Android 14+, rely on "Disable child process restrictions"
|
||||
# Proxy signals: settings_enable_monitor_phantom_procs (or the fflag override).
|
||||
# Baseline does not run check_readiness(), so CHECK_MON is usually empty.
|
||||
if [[ "${CHECK_MON:-}" == "false" ]]; then
|
||||
: # Verified OK (rare in baseline) -> continue
|
||||
else
|
||||
# If ADB is already connected, try to read the flag best-effort (no prompts).
|
||||
if [[ "$adb_connected" -eq 1 && -n "${serial:-}" ]]; then
|
||||
mon_fflag="$(adb_get_child_restrictions_flag "$serial")"
|
||||
if [[ "$mon_fflag" == "true" || "$mon_fflag" == "false" ]]; then
|
||||
mon="$mon_fflag"
|
||||
else
|
||||
mon="$(adb -s "$serial" shell settings get global settings_enable_monitor_phantom_procs 2>/dev/null | tr -d '\r' || true)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${mon:-}" == "false" ]]; then
|
||||
: # Restrictions already disabled -> ok to continue
|
||||
else
|
||||
if [[ "${mon:-}" == "true" ]]; then
|
||||
warn "Android 14+: child process restrictions appear ENABLED (monitor=true)."
|
||||
else
|
||||
warn "Android 14+: child process restrictions haven't been verified (monitor flag unreadable/unknown)."
|
||||
fi
|
||||
warn "Before starting the IIAB install, run the complete setup (--all) so it can guide you to verify such setting; otherwise the installation may fail:"
|
||||
ok " ./0_termux-setupv2.sh --all"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${CHECK_NO_ADB:-0}" -eq 1 ]]; then
|
||||
# If we could not check, still warn on A12-13 because PPK is critical there
|
||||
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
|
||||
warn "A12-13: verify PPK=256 before installing IIAB."
|
||||
fi
|
||||
else
|
||||
# A14+ child restrictions proxy (only if readable)
|
||||
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 34 )) && [[ "${CHECK_MON:-}" == "true" ]]; then
|
||||
warn "A14+: disable child process restrictions before installing IIAB."
|
||||
fi
|
||||
|
||||
# Only warn about PPK on A12-13 (A14+ uses child restrictions)
|
||||
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
|
||||
if [[ "${CHECK_PPK:-}" =~ ^[0-9]+$ ]] && (( CHECK_PPK < 256 )); then
|
||||
warn "PPK is low (${CHECK_PPK}); consider --ppk-only."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2) Debian “next step” should only be shown for modes that actually bootstrap Debian
|
||||
case "$MODE" in
|
||||
baseline|with-adb|all)
|
||||
if debian_exists; then
|
||||
ok "Next: proot-distro login debian"
|
||||
else
|
||||
warn "Debian not present. Run: proot-distro install debian"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# adb-only/connect-only/ppk-only/check: do not suggest Debian login as a generic ending
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# Args
|
||||
# -------------------------
|
||||
set_mode() {
|
||||
local new="$1"
|
||||
if [[ "$MODE_SET" -eq 1 ]]; then
|
||||
die "Modes are mutually exclusive. Already set: --${MODE}. Tried: --${new}"
|
||||
fi
|
||||
MODE="$new"
|
||||
MODE_SET=1
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--with-adb) set_mode "with-adb"; shift ;;
|
||||
--adb-only) set_mode "adb-only"; shift ;;
|
||||
--connect-only)
|
||||
set_mode "connect-only"
|
||||
ONLY_CONNECT=1
|
||||
# Optional positional port (5 digits)
|
||||
if [[ "${2:-}" =~ ^[0-9]{5}$ ]]; then
|
||||
[[ -n "${CONNECT_PORT_FROM:-}" && "${CONNECT_PORT_FROM}" != "positional" ]] && \
|
||||
die "CONNECT PORT specified twice (positional + --connect-port). Use only one."
|
||||
CONNECT_PORT="$2"
|
||||
CONNECT_PORT_FROM="positional"
|
||||
shift 2
|
||||
else
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
--ppk-only) set_mode "ppk-only"; shift ;;
|
||||
--check) set_mode "check"; shift ;;
|
||||
--all) set_mode "all"; shift ;;
|
||||
--connect-port)
|
||||
[[ -n "${CONNECT_PORT_FROM:-}" && "${CONNECT_PORT_FROM}" != "flag" ]] && \
|
||||
die "CONNECT PORT specified twice (positional + --connect-port). Use only one."
|
||||
CONNECT_PORT="${2:-}"
|
||||
CONNECT_PORT_FROM="flag"
|
||||
shift 2
|
||||
;;
|
||||
--timeout) TIMEOUT_SECS="${2:-180}"; shift 2 ;;
|
||||
--host) HOST="${2:-127.0.0.1}"; shift 2 ;;
|
||||
--reset-debian|--clean-debian) RESET_DEBIAN=1; shift ;;
|
||||
--no-log) LOG_ENABLED=0; shift ;;
|
||||
--log-file) LOG_FILE="${2:-}"; shift 2 ;;
|
||||
--debug) DEBUG=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
validate_args() {
|
||||
if [[ -n "${CONNECT_PORT:-}" ]]; then
|
||||
CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}"
|
||||
[[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid --connect-port (must be 5 digits): '$CONNECT_PORT'"
|
||||
case "$MODE" in
|
||||
adb-only|with-adb|connect-only|ppk-only|check|all) : ;;
|
||||
baseline)
|
||||
log "--connect-port requires an ADB mode."
|
||||
die "Use along with: --adb-only / --with-adb / --connect-only / --check / --ppk-only / --all"
|
||||
;;
|
||||
*)
|
||||
die "--connect-port is not valid with mode=$MODE"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
# -------------------------
|
||||
# Main flows
|
||||
# -------------------------
|
||||
main() {
|
||||
setup_logging "$@"
|
||||
validate_args
|
||||
sanitize_timeout
|
||||
acquire_wakelock
|
||||
|
||||
case "$MODE" in
|
||||
baseline)
|
||||
step_termux_repo_select_once
|
||||
step_termux_base
|
||||
step_debian_bootstrap_default
|
||||
;;
|
||||
|
||||
with-adb)
|
||||
step_termux_repo_select_once
|
||||
step_termux_base
|
||||
step_debian_bootstrap_default
|
||||
adb_pair_connect_if_needed
|
||||
;;
|
||||
|
||||
adb-only)
|
||||
step_termux_base
|
||||
adb_pair_connect_if_needed
|
||||
;;
|
||||
|
||||
connect-only)
|
||||
step_termux_base
|
||||
adb_pair_connect
|
||||
;;
|
||||
|
||||
ppk-only)
|
||||
# No baseline, no Debian. Requires adb already available + connected.
|
||||
require_adb_connected || exit 1
|
||||
ppk_fix_via_adb || true
|
||||
;;
|
||||
|
||||
check)
|
||||
step_termux_base
|
||||
check_readiness || true
|
||||
;;
|
||||
|
||||
all)
|
||||
step_termux_repo_select_once
|
||||
step_termux_base
|
||||
step_debian_bootstrap_default
|
||||
adb_pair_connect_if_needed
|
||||
|
||||
# Android 12-13 only (SDK 31-33): apply PPK tuning automatically
|
||||
if [[ "${ANDROID_SDK:-}" =~ ^[0-9]+$ ]] && (( ANDROID_SDK >= 31 && ANDROID_SDK <= 33 )); then
|
||||
log "Android SDK=${ANDROID_SDK} detected -> applying --ppk automatically (12-13 rule)."
|
||||
ppk_fix_via_adb || true
|
||||
else
|
||||
log "Android SDK=${ANDROID_SDK:-?} -> skipping auto-PPK (only for Android 12-13)."
|
||||
fi
|
||||
check_readiness || true
|
||||
;;
|
||||
|
||||
*)
|
||||
die "Unknown MODE='$MODE'"
|
||||
;;
|
||||
esac
|
||||
|
||||
self_check
|
||||
ok "0_termux-setupv2.sh completed (mode=$MODE)."
|
||||
log "---- Mode list ----"
|
||||
log "Connect-only --connect-only [PORT]"
|
||||
log "Pair+connect --adb-only [--connect-port PORT]"
|
||||
log "Check --check"
|
||||
log "Apply PPK --ppk-only"
|
||||
log "Base+Debian+Pair+connect --with-adb"
|
||||
log "Full run --all"
|
||||
log "Reset Debian --reset-debian"
|
||||
log "-------------------"
|
||||
final_advice
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# termux-setup modules
|
||||
|
||||
This project is maintained as multiple Bash "modules" that are bundled into a single script:
|
||||
`0_termux-setup_v2.sh` (the file served for: `curl ... | bash`).
|
||||
|
||||
## Rules
|
||||
|
||||
- Modules MUST NOT include a shebang (`#!...`).
|
||||
- Modules SHOULD NOT run top-level code (prefer functions), except `99_main.sh`.
|
||||
- Do not add `set -euo pipefail` in modules (the bundle already sets it once).
|
||||
- Keep module names stable and ordered via `manifest.sh`.
|
||||
|
||||
Recommended header for every module:
|
||||
|
||||
```
|
||||
# termux-setup module.
|
||||
# DO NOT add a shebang or "set -euo pipefail" here.
|
||||
# Keep only function/variable definitions (no top-level execution).
|
||||
# See: termux-setup/README.md
|
||||
```
|
||||
|
||||
## Rebuild:
|
||||
|
||||
```
|
||||
cd termux-setup
|
||||
bash build_bundle.sh
|
||||
```
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# build_bundle.sh
|
||||
# Bundles modules listed in manifest.sh into ../0_termux-setup_v2.sh
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$SCRIPT_DIR"
|
||||
|
||||
MANIFEST="${ROOT_DIR}/manifest.sh"
|
||||
[[ -f "$MANIFEST" ]] || { echo "[!] Missing manifest.sh at: $MANIFEST" >&2; exit 1; }
|
||||
|
||||
# Modules live next to build_bundle.sh (same dir)
|
||||
MOD_DIR="$ROOT_DIR"
|
||||
|
||||
PARENT_DIR="$(cd -- "${ROOT_DIR}/.." && pwd)"
|
||||
OUT_DIR="${ROOT_DIR}/dist"
|
||||
OUT_FILE="${PARENT_DIR}/0_termux-setup_v2.sh"
|
||||
TMP_FILE="${OUT_DIR}/.0_termux-setup_v2.tmp.$RANDOM$RANDOM"
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
# Load MODULES array
|
||||
# shellcheck source=/dev/null
|
||||
source "$MANIFEST"
|
||||
|
||||
# With "set -u", don't reference an unset array directly.
|
||||
if ! declare -p MODULES >/dev/null 2>&1; then
|
||||
echo "[!] manifest.sh did not define MODULES array." >&2
|
||||
exit 1
|
||||
fi
|
||||
if (( ${#MODULES[@]} < 1 )); then
|
||||
echo "[!] MODULES array is empty in manifest.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup_tmp() { rm -f -- "$TMP_FILE" 2>/dev/null || true; }
|
||||
trap cleanup_tmp EXIT
|
||||
|
||||
# Bundle header
|
||||
{
|
||||
echo '#!/data/data/com.termux/files/usr/bin/bash'
|
||||
echo 'set -euo pipefail'
|
||||
echo
|
||||
echo '# -----------------------------------------------------------------------------'
|
||||
echo '# GENERATED FILE: do not edit directly.'
|
||||
echo '# Source modules: termux-setup/*.sh + manifest.sh'
|
||||
echo '# Rebuild: (cd termux-setup && bash build_bundle.sh)'
|
||||
echo '# -----------------------------------------------------------------------------'
|
||||
echo
|
||||
} >"$TMP_FILE"
|
||||
|
||||
# Append each module
|
||||
for mod in "${MODULES[@]}"; do
|
||||
src="${MOD_DIR}/${mod}"
|
||||
|
||||
if [[ ! -f "$src" ]]; then
|
||||
echo "[!] Missing module: $src" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Disallow standalone scripts: modules must not have a shebang
|
||||
if head -n 1 "$src" | grep -q '^#!'; then
|
||||
echo "[!] Module must NOT include a shebang: $src" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo
|
||||
echo "# ---- BEGIN ${mod} ----"
|
||||
cat "$src"
|
||||
echo
|
||||
echo "# ---- END ${mod} ----"
|
||||
echo
|
||||
} >>"$TMP_FILE"
|
||||
done
|
||||
|
||||
# Ensure final newline
|
||||
printf '\n' >>"$TMP_FILE"
|
||||
|
||||
# Install bundle atomically
|
||||
chmod 700 "$TMP_FILE" 2>/dev/null || true
|
||||
mv -f -- "$TMP_FILE" "$OUT_FILE"
|
||||
chmod 700 "$OUT_FILE" 2>/dev/null || true
|
||||
|
||||
echo "[ok] Wrote: $OUT_FILE"
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# termux-setup manifest
|
||||
# Order matters: later modules can rely on functions/vars defined earlier.
|
||||
|
||||
MODULES=(
|
||||
"00_lib_common.sh"
|
||||
"10_mod_logging.sh"
|
||||
"20_mod_termux_base.sh"
|
||||
"30_mod_debian.sh"
|
||||
"40_mod_termux_api.sh"
|
||||
"50_mod_adb.sh"
|
||||
"60_mod_ppk_checks.sh"
|
||||
"99_main.sh"
|
||||
)
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
.gradle
|
||||
.idea/deploymentTargetSelector.xml
|
||||
app/.externalNativeBuild
|
||||
app/build
|
||||
app/release
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "app/src/main/jni/hev-socks5-tunnel"]
|
||||
path = app/src/main/jni/hev-socks5-tunnel
|
||||
url = https://github.com/heiher/hev-socks5-tunnel
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,291 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="WizardSettings">
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="imageWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="imageAssetPanel">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="actionbar">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="text">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="textAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="launcher">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="foregroundClipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundImage">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="$PROJECT_DIR$/../iiab-project/IIAB-on-Android-Controller.svg" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundText">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundTextAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="launcherLegacy">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="text">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="textAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="notification">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="text">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="textAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="tvBanner">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="foregroundText">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="tvChannel">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="foregroundClipArt">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="imagePath" value="/tmp/ic_android_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundImage">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundText">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="foregroundTextAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="color" value="000000" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MarkdownSettings">
|
||||
<option name="previewPanelProviderInfo">
|
||||
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="StudioBotProjectSettings">
|
||||
<option name="shareContext" value="OptedIn" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel" vcs="" />
|
||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/src/core" vcs="" />
|
||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/hev-task-system" vcs="" />
|
||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/lwip" vcs="" />
|
||||
<mapping directory="$PROJECT_DIR$/app/src/main/jni/hev-socks5-tunnel/third-part/yaml" vcs="" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2023 hev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
# IIAB-oA Controller
|
||||
|
||||
**IIAB-oA Controller** is a specialized infrastructure component for the **Internet-in-a-Box (IIAB)** ecosystem on Android. It acts as a "Walled Garden" and persistent "Watchdog" designed to keep the Termux environment alive and accessible, even on devices with aggressive power management (e.g., Oppo/ColorOS, MIUI).
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### 🛡️ Master Watchdog (Supervision Layer)
|
||||
An independent foreground service dedicated to environment stability:
|
||||
|
||||
* **CPU & Wi-Fi Shield**: Prevents the system from putting Termux into Doze mode or disabling the Wi-Fi radio.
|
||||
* **Heartbeat Pulse**: Sends a regulated API signal every 20 seconds to maintain process priority.
|
||||
* **Zero-Config Protection**: Works independently of the VPN tunnel.
|
||||
|
||||
### 🌐 Safe Pocket Web (Network Layer)
|
||||
A high-performance VPN tunnel based on the tun2socks engine:
|
||||
|
||||
* **Friendly URLs**: Routes traffic through internal IIAB services seamlessly.
|
||||
* **Walled Garden**: Ensures a secure, filtered browsing environment.
|
||||
* **Per-App Routing**: Granular control over which applications use the secure tunnel.
|
||||
|
||||
### 🔒 Built-in Security
|
||||
* **Biometric/PIN Lock**: Authentication is strictly required before the Watchdog or VPN can be disabled.
|
||||
* **Safety Check**: Prevents activation if the device lacks a secure lock method (PIN/Pattern/Fingerprint), ensuring the user is never "locked out" of their own settings.
|
||||
|
||||
## Acknowledgments
|
||||
This project is a heavily customized spin-off of **[SocksTun](https://github.com/heiher/sockstun)** created by **[heiher](https://github.com/heiher)**.
|
||||
All credit for the core native tunneling engine goes to the original author. This derivative has been re-architected to meet the specific requirements of the IIAB project.
|
||||
|
||||
## Technical Details
|
||||
* **Current Version**: v0.1.12alpha
|
||||
* **License**: **MIT License** (See [LICENSE](LICENSE) for details).
|
||||
* **Compatibility**: Android 8.0 (API 26) and above.
|
||||
|
||||
## Disclaimer
|
||||
This is a preview and demo published in the hope that it will be useful, but WITHOUT ANY WARRANTY.
|
||||
|
||||
---
|
||||
*Maintained by IIAB Contributors - 2026*
|
||||
|
|
@ -1,82 +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 26
|
||||
versionName "v0.1.30alpha"
|
||||
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'
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "org.iiab.controller",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 26,
|
||||
"versionName": "v0.1.30alpha",
|
||||
"outputFile": "org.iiab.controller-v0.1.30alpha-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File",
|
||||
"baselineProfiles": [
|
||||
{
|
||||
"minApi": 28,
|
||||
"maxApi": 30,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/1/org.iiab.controller-v0.1.30alpha-release.dm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"minApi": 31,
|
||||
"maxApi": 2147483647,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/0/org.iiab.controller-v0.1.30alpha-release.dm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"minSdkVersionForDexing": 24
|
||||
}
|
||||
|
|
@ -1,88 +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 (The Boomerang) -->
|
||||
<receiver android:name=".VpnRecoveryReceiver" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="org.iiab.controller.RECOVER_VPN" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!-- 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=".PortalActivity"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar" />
|
||||
</application>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<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" />
|
||||
</manifest>
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Safe Pocket Web</title>
|
||||
<style>
|
||||
/* Basic reset and background */
|
||||
body {
|
||||
margin: 0; padding: 0;
|
||||
background-color: #F4F7F6;
|
||||
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
min-height: 100vh;
|
||||
display: flex; flex-direction: column;
|
||||
justify-content: center; align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Header styles */
|
||||
.top-header {
|
||||
position: absolute;
|
||||
top: 0; left: 0; width: 100%;
|
||||
display: flex; align-items: center;
|
||||
padding: 16px 20px;
|
||||
box-sizing: border-box;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 900;
|
||||
color: #2C3E50;
|
||||
padding-right: 48px;
|
||||
}
|
||||
|
||||
/* Button container */
|
||||
.menu-container {
|
||||
width: 80%; display: flex;
|
||||
flex-direction: column; gap: 20px;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
/* Button styles */
|
||||
.btn {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: 18px; border-radius: 14px; text-decoration: none;
|
||||
font-size: 20px; font-weight: bold;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
|
||||
transition: transform 0.1s, box-shadow 0.1s;
|
||||
}
|
||||
.btn:active { transform: scale(0.96); box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
|
||||
|
||||
/* App colors */
|
||||
.btn-books { background-color: #00BCD4; color: #FFFFFF; }
|
||||
.btn-kiwix { background-color: #FF9800; color: #FFFFFF; }
|
||||
.btn-kolibri { background-color: #FFD54F; color: #333333; }
|
||||
.btn-maps { background-color: #4CAF50; color: #FFFFFF; }
|
||||
.btn-matomo { background-color: #1976D2; color: #FFFFFF; }
|
||||
|
||||
.btn span { margin-right: 12px; font-size: 26px; }
|
||||
|
||||
/* Spinner and Overlay styles */
|
||||
#loadingOverlay {
|
||||
display: none;
|
||||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background-color: rgba(244, 247, 246, 0.85);
|
||||
z-index: 9999;
|
||||
flex-direction: column; justify-content: center; align-items: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 50px; height: 50px;
|
||||
border: 6px solid #E0E0E0;
|
||||
border-top: 6px solid #1976D2;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||
.loading-text { margin-top: 15px; font-size: 18px; font-weight: bold; color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="top-header">
|
||||
<img src="logo.svg" alt="IIAB Logo" class="header-logo">
|
||||
<h1 class="header-title">Internet-in-a-Box<br />on Android</h1>
|
||||
</header>
|
||||
|
||||
<div id="loadingOverlay">
|
||||
<div class="spinner"></div>
|
||||
<div class="loading-text" id="loadingText">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="menu-container">
|
||||
<a href="http://box/books" class="btn btn-books"><span>📖</span> Books</a>
|
||||
<a href="http://box/kiwix" class="btn btn-kiwix"><span>🥝</span> Kiwix</a>
|
||||
<a href="http://box/kolibri" class="btn btn-kolibri"><span>📚</span> Kolibri</a>
|
||||
<a href="http://box/maps" class="btn btn-maps"><span>🗺️</span> Maps</a>
|
||||
<a href="http://box/matomo" class="btn btn-matomo"><span>📊</span> Matomo</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const overlay = document.getElementById('loadingOverlay');
|
||||
const textLabel = document.getElementById('loadingText');
|
||||
const buttons = document.querySelectorAll('.btn');
|
||||
|
||||
buttons.forEach(btn => {
|
||||
btn.addEventListener('click', function(e) {
|
||||
const appName = this.innerText.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g, '').trim();
|
||||
// Cambiado a Opening
|
||||
textLabel.innerText = 'Opening ' + appName + '...';
|
||||
overlay.style.display = 'flex';
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('pageshow', function(event) {
|
||||
overlay.style.display = 'none';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
// 1. Detect device language
|
||||
let userLang = (navigator.language || navigator.userLanguage).substring(0, 2).toLowerCase();
|
||||
|
||||
// Function to detect the user's language
|
||||
const applyTranslations = () => {
|
||||
if (!window.i18n) return; // If something went wrong, we return
|
||||
|
||||
// Search for all elements with the data-i18n attribute
|
||||
const elements = document.querySelectorAll("[data-i18n]");
|
||||
|
||||
elements.forEach(el => {
|
||||
const key = el.getAttribute("data-i18n");
|
||||
// Si la clave existe en el diccionario, reemplaza el texto
|
||||
if (window.i18n[key]) {
|
||||
el.innerText = window.i18n[key];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Function to load the .js file of the language
|
||||
const loadScript = (langCode, isFallback = false) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = `lang/${langCode}.js`;
|
||||
|
||||
// If the file exists, we apply the translations
|
||||
script.onload = () => applyTranslations();
|
||||
|
||||
// If the file does NOT exist
|
||||
script.onerror = () => {
|
||||
if (!isFallback) {
|
||||
console.log(`Idioma ${langCode} no encontrado. Cargando inglés...`);
|
||||
loadScript("en", true); // Intentamos con el idioma por defecto
|
||||
}
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
};
|
||||
|
||||
// 2. Start the script loading
|
||||
loadScript(userLang);
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
window.i18n = {
|
||||
"title": "Choose Content",
|
||||
"maps": "🗺️ Maps",
|
||||
"kolibri": "📚 Kolibri",
|
||||
"kiwix": "📖 Kiwix",
|
||||
"books": "📕 Books"
|
||||
};
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
window.i18n = {
|
||||
"title": "Elige el Contenido",
|
||||
"maps": "🗺️ Mapas",
|
||||
"kolibri": "📚 Kolibri",
|
||||
"kiwix": "📖 Kiwix",
|
||||
"books": "📕 Libros"
|
||||
};
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 135.46667 135.46667"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.4.3 (1:1.4.3+202512261035+0d15f75042)"
|
||||
sodipodi:docname="IIAB-on-Android-Controller.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.71891835"
|
||||
inkscape:cx="-140.48883"
|
||||
inkscape:cy="188.47759"
|
||||
inkscape:window-width="1918"
|
||||
inkscape:window-height="1008"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" /><defs
|
||||
id="defs1"><inkscape:path-effect
|
||||
effect="powerclip"
|
||||
message=""
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
inverse="true"
|
||||
flatten="false"
|
||||
hide_clip="false" /><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath2"><ellipse
|
||||
style="display:none;fill:none;fill-opacity:0;stroke:#000000;stroke-width:0.218723;stroke-opacity:1"
|
||||
id="ellipse2"
|
||||
cx="203.22194"
|
||||
cy="44.468861"
|
||||
rx="27.890638"
|
||||
ry="27.66296"
|
||||
d="m 231.11258,44.468861 a 27.890638,27.66296 0 0 1 -27.89064,27.66296 27.890638,27.66296 0 0 1 -27.89064,-27.66296 27.890638,27.66296 0 0 1 27.89064,-27.66296 27.890638,27.66296 0 0 1 27.89064,27.66296 z" /><path
|
||||
id="lpe_path-effect2"
|
||||
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:0.218723;stroke-opacity:1"
|
||||
class="powerclip"
|
||||
d="M 136.73034,-22.709513 H 269.71353 V 111.64723 H 136.73034 Z m 94.38224,67.178374 a 27.890638,27.66296 0 0 0 -27.89064,-27.66296 27.890638,27.66296 0 0 0 -27.89064,27.66296 27.890638,27.66296 0 0 0 27.89064,27.66296 27.890638,27.66296 0 0 0 27.89064,-27.66296 z" /></clipPath></defs><g
|
||||
inkscape:label="Capa 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-32.997666,-44.40659)"><rect
|
||||
style="fill:#000000;stroke:#000000;stroke-width:0.462333"
|
||||
id="rect2"
|
||||
width="135.00433"
|
||||
height="135.00433"
|
||||
x="33.228832"
|
||||
y="44.637756" /><path
|
||||
style="font-size:81.7498px;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;fill:#ffffff;stroke:#ffffff;stroke-width:0.491597"
|
||||
d="m 72.627453,134.58207 h 1.560831 q -0.178844,1.68277 -1.268175,2.80462 -1.089332,1.12185 -3.219217,1.12185 -2.064851,0 -3.333026,-1.47141 -1.260049,-1.47954 -1.284437,-3.92647 v -1.26818 q 0,-2.48757 1.276307,-3.9915 1.284434,-1.50393 3.479356,-1.50393 2.007944,0 3.089147,1.10559 1.081201,1.10559 1.260045,2.8534 h -1.560831 q -0.178844,-1.23566 -0.796676,-1.95104 -0.617827,-0.72351 -1.991685,-0.72351 -1.568963,0 -2.381895,1.15436 -0.812935,1.15436 -0.812935,3.04038 v 1.19501 q 0,1.7478 0.739771,2.98346 0.739769,1.22753 2.316859,1.22753 1.495799,0 2.105498,-0.69912 0.617831,-0.69913 0.821063,-1.95104 z m 3.064759,-0.72351 q 0,-1.9104 1.073072,-3.1867 1.073073,-1.28444 2.918429,-1.28444 1.84536,0 2.918432,1.26005 1.073073,1.25192 1.097461,3.12979 v 0.26827 q 0,1.91039 -1.081202,3.1867 -1.073072,1.27631 -2.918431,1.27631 -1.853486,0 -2.934689,-1.27631 -1.073072,-1.27631 -1.073072,-3.1867 z m 1.503926,0.18697 q 0,1.30882 0.61783,2.26809 0.625959,0.95925 1.886005,0.95925 1.227529,0 1.853488,-0.943 0.625959,-0.95113 0.634089,-2.25995 v -0.21136 q 0,-1.29257 -0.625959,-2.25996 -0.625959,-0.97552 -1.877878,-0.97552 -1.243786,0 -1.869745,0.97552 -0.61783,0.96739 -0.61783,2.25996 z m 12.039543,-3.38993 q -0.723509,0 -1.276304,0.39021 -0.552795,0.3902 -0.86984,1.01616 v 6.28397 h -1.503926 v -8.79593 h 1.422635 l 0.04877,1.09746 q 0.999907,-1.26005 2.625774,-1.26005 1.292563,0 2.048591,0.72351 0.764158,0.72351 0.77229,2.43068 v 5.80433 h -1.512058 v -5.77995 q 0,-1.03243 -0.455243,-1.47141 -0.447112,-0.43898 -1.300693,-0.43898 z m 9.267443,7.69034 q -0.512151,0.1626 -1.162496,0.1626 -0.837321,0 -1.430763,-0.51216 -0.593442,-0.51214 -0.593442,-1.83722 v -5.45478 h -1.609606 v -1.15437 h 1.609606 V 127.412 h 1.503928 v 2.13801 h 1.642124 v 1.15437 h -1.642124 v 5.46291 q 0,0.67474 0.292656,0.8617 0.292658,0.18698 0.674734,0.18698 0.284527,0 0.707253,-0.0975 z m 5.235286,-7.5115 q -1.47141,0 -2.00795,1.26819 v 6.24331 h -1.50393 v -8.79593 h 1.46329 l 0.0325,1.00804 q 0.72351,-1.17063 2.08923,-1.17063 0.42273,0 0.66661,0.11381 l -0.008,1.39825 q -0.3333,-0.0651 -0.73164,-0.0651 z m 1.56895,3.02412 q 0,-1.9104 1.07308,-3.1867 1.07307,-1.28444 2.91843,-1.28444 1.84535,0 2.91843,1.26005 1.07307,1.25192 1.09746,3.12979 v 0.26827 q 0,1.91039 -1.0812,3.1867 -1.07307,1.27631 -2.91843,1.27631 -1.85349,0 -2.93469,-1.27631 -1.07308,-1.27631 -1.07308,-3.1867 z m 1.50393,0.18697 q 0,1.30882 0.61783,2.26809 0.62596,0.95925 1.88601,0.95925 1.22752,0 1.85348,-0.943 0.62596,-0.95113 0.6341,-2.25995 v -0.21136 q 0,-1.29257 -0.62596,-2.25996 -0.62596,-0.97552 -1.87788,-0.97552 -1.24379,0 -1.86975,0.97552 -0.61783,0.96739 -0.61783,2.25996 z m 10.02347,-8.18624 v 12.48665 h -1.51205 v -12.48665 z m 4.04841,0 v 12.48665 h -1.51206 v -12.48665 z m 9.37312,10.95022 q -0.43086,0.65034 -1.2194,1.17875 -0.78855,0.52028 -2.08923,0.52028 -1.83724,0 -2.94282,-1.19501 -1.09746,-1.19502 -1.09746,-3.05664 v -0.34143 q 0,-1.43889 0.54466,-2.44693 0.5528,-1.01616 1.43076,-1.54457 0.87797,-0.53654 1.86974,-0.53654 1.88602,0 2.74772,1.23566 0.86984,1.22753 0.86984,3.07289 v 0.67474 h -5.95067 q 0.0325,1.21126 0.71538,2.06485 0.691,0.84544 1.89413,0.84544 0.79668,0 1.34947,-0.32517 0.5528,-0.32517 0.96739,-0.86984 z m -3.50375,-6.18643 q -0.89422,0 -1.51205,0.65035 -0.61783,0.65035 -0.77228,1.86975 h 4.39797 v -0.11382 q -0.0569,-0.87796 -0.51215,-1.64212 -0.44712,-0.76416 -1.60149,-0.76416 z m 8.88537,0.21136 q -1.47141,0 -2.00794,1.26819 v 6.24331 h -1.50393 v -8.79593 h 1.46328 l 0.0325,1.00804 q 0.72351,-1.17063 2.08924,-1.17063 0.42273,0 0.66661,0.11381 l -0.008,1.39825 q -0.33331,-0.0651 -0.73165,-0.0651 z"
|
||||
id="text2"
|
||||
aria-label="Controller" /><g
|
||||
id="g3"
|
||||
transform="translate(-1.9438155)"><path
|
||||
style="font-weight:bold;font-size:124.9px;font-family:Sans;-inkscape-font-specification:'Sans Bold';fill:#ffffff;stroke:#ffffff;stroke-width:0.751077"
|
||||
d="M 64.68422,94.628369 V 112.71227 H 60.958142 V 94.628369 Z m 7.427314,0 V 112.71227 H 68.385456 V 94.628369 Z m 2.173546,18.083901 6.73178,-18.083901 h 3.452831 l 6.76905,18.083901 h -3.97449 l -1.24202,-3.72608 h -6.545487 l -1.242021,3.72608 z m 6.197711,-6.7442 h 4.5334 l -2.260495,-6.793888 z m 25.883829,1.4159 q 0,2.60826 -1.66432,3.96207 -1.66431,1.34139 -4.744537,1.36623 H 92.927901 V 94.628369 h 6.346742 q 3.142327,0 4.918427,1.204765 1.7761,1.204764 1.7761,3.738498 0,1.229608 -0.62102,2.260488 -0.62101,1.03088 -1.9624,1.56495 1.5898,0.39745 2.28533,1.54011 0.69554,1.14267 0.69554,2.44679 z m -9.700227,-9.737479 v 4.545819 h 2.60825 q 2.956027,0 2.956027,-2.235652 0,-2.260486 -2.831817,-2.310167 z m 5.974147,9.700229 q 0,-2.43438 -2.45922,-2.52132 h -3.514927 v 4.88116 h 3.17958 q 1.428337,0 2.111447,-0.67069 0.68312,-0.67069 0.68312,-1.68915 z"
|
||||
id="text4"
|
||||
aria-label="IIAB" /><path
|
||||
id="path1"
|
||||
style="fill:#ffffff;stroke:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
d="m 203.20824,-17.709513 a 65.463825,62.177783 0 0 0 -11.35899,0.982886 L 187.76371,2.4205246 A 46.925591,44.569562 0 0 0 172.59564,10.741464 L 153.09037,4.5268556 A 65.463825,62.177783 0 0 0 141.73034,23.215121 l 15.42283,12.940296 a 46.925591,44.569562 0 0 0 -0.85679,8.313698 46.925591,44.569562 0 0 0 0.85679,8.31319 l -15.42283,12.9403 a 65.463825,62.177783 0 0 0 11.36003,18.68826 l 19.50527,-6.21409 a 46.925591,44.569562 0 0 0 15.16807,8.32094 l 4.08554,19.147145 a 65.463825,62.177783 0 0 0 11.35899,0.98237 65.463825,62.177783 0 0 0 11.359,-0.98237 l 4.08295,-19.136295 a 46.925591,44.569562 0 0 0 15.19029,-8.32559 l 19.48512,6.20841 a 65.463825,62.177783 0 0 0 11.36003,-18.68827 l -15.39906,-12.92014 a 46.925591,44.569562 0 0 0 0.86093,-8.33334 46.925591,44.569562 0 0 0 -0.8351,-8.355038 L 264.71353,23.192383 A 65.463825,62.177783 0 0 0 253.36435,4.5149706 L 233.87304,10.725444 A 46.925591,44.569562 0 0 0 218.64296,2.3750486 l -4.08347,-19.1383656 a 65.463825,62.177783 0 0 0 -11.35125,-0.945679 z"
|
||||
clip-path="url(#clipPath2)"
|
||||
inkscape:path-effect="#path-effect2"
|
||||
inkscape:original-d="m 203.20824,-17.709513 a 65.463825,62.177783 0 0 0 -11.35899,0.982886 L 187.76371,2.4205246 A 46.925591,44.569562 0 0 0 172.59564,10.741464 L 153.09037,4.5268556 A 65.463825,62.177783 0 0 0 141.73034,23.215121 l 15.42283,12.940296 a 46.925591,44.569562 0 0 0 -0.85679,8.313698 46.925591,44.569562 0 0 0 0.85679,8.31319 l -15.42283,12.9403 a 65.463825,62.177783 0 0 0 11.36003,18.68826 l 19.50527,-6.21409 a 46.925591,44.569562 0 0 0 15.16807,8.32094 l 4.08554,19.147145 a 65.463825,62.177783 0 0 0 11.35899,0.98237 65.463825,62.177783 0 0 0 11.359,-0.98237 l 4.08295,-19.136295 a 46.925591,44.569562 0 0 0 15.19029,-8.32559 l 19.48512,6.20841 a 65.463825,62.177783 0 0 0 11.36003,-18.68827 l -15.39906,-12.92014 a 46.925591,44.569562 0 0 0 0.86093,-8.33334 46.925591,44.569562 0 0 0 -0.8351,-8.355038 L 264.71353,23.192383 A 65.463825,62.177783 0 0 0 253.36435,4.5149706 L 233.87304,10.725444 A 46.925591,44.569562 0 0 0 218.64296,2.3750486 l -4.08347,-19.1383656 a 65.463825,62.177783 0 0 0 -11.35125,-0.945679 z"
|
||||
transform="matrix(0.29688448,0,0,0.29688448,66.177687,89.523053)" /></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
|
@ -1,225 +0,0 @@
|
|||
/*
|
||||
============================================================================
|
||||
Name : AppListActivity.java
|
||||
Author : hev <r@hev.cc>
|
||||
Copyright : Copyright (c) 2025 xyz
|
||||
Description : App List Activity
|
||||
============================================================================
|
||||
*/
|
||||
|
||||
package org.iiab.controller;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Comparator;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Bundle;
|
||||
import android.app.ListActivity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.EditText;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.Editable;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
public class AppListActivity extends ListActivity {
|
||||
private Preferences prefs;
|
||||
private AppArrayAdapter adapter;
|
||||
private boolean isChanged = false;
|
||||
|
||||
private class Package {
|
||||
public PackageInfo info;
|
||||
public boolean selected;
|
||||
public String label;
|
||||
|
||||
public Package(PackageInfo info, boolean selected, String label) {
|
||||
this.info = info;
|
||||
this.selected = selected;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
|
||||
private class AppArrayAdapter extends ArrayAdapter<Package> {
|
||||
private final List<Package> allPackages = new ArrayList<Package>();
|
||||
private final List<Package> filteredPackages = new ArrayList<Package>();
|
||||
private String lastFilter = "";
|
||||
|
||||
public AppArrayAdapter(Context context) {
|
||||
super(context, R.layout.appitem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Package pkg) {
|
||||
allPackages.add(pkg);
|
||||
if (matchesFilter(pkg, lastFilter))
|
||||
filteredPackages.add(pkg);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
allPackages.clear();
|
||||
filteredPackages.clear();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(Comparator<? super Package> cmp) {
|
||||
Collections.sort(allPackages, (Comparator) cmp);
|
||||
applyFilter(lastFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return filteredPackages.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Package getItem(int position) {
|
||||
return filteredPackages.get(position);
|
||||
}
|
||||
|
||||
public List<Package> getAllPackages() {
|
||||
return allPackages;
|
||||
}
|
||||
|
||||
private boolean matchesFilter(Package pkg, String filter) {
|
||||
if (filter == null || filter.length() == 0)
|
||||
return true;
|
||||
return pkg.label.toLowerCase().contains(filter.toLowerCase());
|
||||
}
|
||||
|
||||
public void applyFilter(String filter) {
|
||||
lastFilter = filter != null ? filter : "";
|
||||
filteredPackages.clear();
|
||||
if (lastFilter.length() == 0) {
|
||||
filteredPackages.addAll(allPackages);
|
||||
} else {
|
||||
String f = lastFilter.toLowerCase();
|
||||
for (Package p : allPackages) {
|
||||
if (p.label != null && p.label.toLowerCase().contains(f))
|
||||
filteredPackages.add(p);
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View rowView = inflater.inflate(R.layout.appitem, parent, false);
|
||||
ImageView imageView = (ImageView) rowView.findViewById(R.id.icon);
|
||||
TextView textView = (TextView) rowView.findViewById(R.id.name);
|
||||
CheckBox checkBox = (CheckBox) rowView.findViewById(R.id.checked);
|
||||
|
||||
Package pkg = getItem(position);
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
ApplicationInfo appinfo = pkg.info.applicationInfo;
|
||||
imageView.setImageDrawable(appinfo.loadIcon(pm));
|
||||
textView.setText(appinfo.loadLabel(pm).toString());
|
||||
checkBox.setChecked(pkg.selected);
|
||||
|
||||
return rowView;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
|
||||
prefs = new Preferences(this);
|
||||
Set<String> apps = prefs.getApps();
|
||||
PackageManager pm = getPackageManager();
|
||||
adapter = new AppArrayAdapter(this);
|
||||
|
||||
for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)) {
|
||||
if (info.packageName.equals(getPackageName()))
|
||||
continue;
|
||||
if (info.requestedPermissions == null)
|
||||
continue;
|
||||
if (!Arrays.asList(info.requestedPermissions).contains(Manifest.permission.INTERNET))
|
||||
continue;
|
||||
boolean selected = apps.contains(info.packageName);
|
||||
String label = info.applicationInfo.loadLabel(pm).toString();
|
||||
Package pkg = new Package(info, selected, label);
|
||||
adapter.add(pkg);
|
||||
}
|
||||
|
||||
EditText searchBox = new EditText(this);
|
||||
searchBox.setHint("Search");
|
||||
int pad = (int) (8 * getResources().getDisplayMetrics().density);
|
||||
searchBox.setPadding(pad, pad, pad, pad);
|
||||
getListView().addHeaderView(searchBox, null, false);
|
||||
|
||||
adapter.sort(new Comparator<Package>() {
|
||||
public int compare(Package a, Package b) {
|
||||
if (a.selected != b.selected)
|
||||
return a.selected ? -1 : 1;
|
||||
return a.label.compareTo(b.label);
|
||||
}
|
||||
});
|
||||
|
||||
setListAdapter(adapter);
|
||||
|
||||
searchBox.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
adapter.applyFilter(s.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) { }
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (isChanged) {
|
||||
Set<String> apps = new HashSet<String>();
|
||||
|
||||
for (Package pkg : adapter.getAllPackages()) {
|
||||
if (pkg.selected)
|
||||
apps.add(pkg.info.packageName);
|
||||
}
|
||||
|
||||
prefs.setApps(apps);
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||
int headers = l.getHeaderViewsCount();
|
||||
int adjPos = position - headers;
|
||||
if (adjPos < 0)
|
||||
return;
|
||||
Package pkg = adapter.getItem(adjPos);
|
||||
pkg.selected = !pkg.selected;
|
||||
CheckBox checkbox = (CheckBox) v.findViewById(R.id.checked);
|
||||
if (checkbox != null)
|
||||
checkbox.setChecked(pkg.selected);
|
||||
isChanged = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,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, "Permission Denied: Ensure manifest has RUN_COMMAND and app is not restricted.", e);
|
||||
writeToBlackBox(context, context.getString(R.string.critical_os_blocked));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unexpected error sending intent to Termux", e);
|
||||
writeToBlackBox(context, "Pulse Error: " + 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, "Failed to write to 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, "Maintenance write failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static long calculateSize(List<String> lines) {
|
||||
long size = 0;
|
||||
for (String s : lines) size += s.length() + 1;
|
||||
return size;
|
||||
}
|
||||
|
||||
private static void setRapidGrowthFlag(Context context, boolean enabled) {
|
||||
SharedPreferences prefs = context.getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean(PREF_RAPID_GROWTH, enabled).apply();
|
||||
}
|
||||
|
||||
private static void broadcastLog(Context context, String message) {
|
||||
Intent intent = new Intent(ACTION_LOG_MESSAGE);
|
||||
intent.putExtra(EXTRA_MESSAGE, message);
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,809 +0,0 @@
|
|||
/*
|
||||
============================================================================
|
||||
Name : MainActivity.java
|
||||
Author : hev <r@hev.cc>
|
||||
Copyright : Copyright (c) 2023 xyz
|
||||
Description : Main Activity
|
||||
============================================================================
|
||||
*/
|
||||
|
||||
package org.iiab.controller;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Bundle;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.Context;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.net.VpnService;
|
||||
import android.net.Uri;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricManager;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
|
||||
private static final String TAG = "IIAB-MainActivity";
|
||||
private static final String TERMUX_PERMISSION = "com.termux.permission.RUN_COMMAND";
|
||||
private Preferences prefs;
|
||||
private EditText edittext_socks_addr;
|
||||
private EditText edittext_socks_udp_addr;
|
||||
private EditText edittext_socks_port;
|
||||
private EditText edittext_socks_user;
|
||||
private EditText edittext_socks_pass;
|
||||
private EditText edittext_dns_ipv4;
|
||||
private EditText edittext_dns_ipv6;
|
||||
private CheckBox checkbox_udp_in_tcp;
|
||||
private CheckBox checkbox_remote_dns;
|
||||
private CheckBox checkbox_global;
|
||||
private CheckBox checkbox_maintenance;
|
||||
private TextView textview_maintenance_warning;
|
||||
private CheckBox checkbox_ipv4;
|
||||
private CheckBox checkbox_ipv6;
|
||||
private Button button_apps;
|
||||
private Button button_save;
|
||||
private Button button_control;
|
||||
private Button button_browse_content;
|
||||
private Button watchdogControl;
|
||||
private TextView connectionLog;
|
||||
private LinearLayout logActions;
|
||||
private LinearLayout configLayout;
|
||||
private TextView configLabel;
|
||||
private LinearLayout advancedConfig;
|
||||
private TextView advConfigLabel;
|
||||
private TextView logLabel;
|
||||
private TextView logWarning;
|
||||
private TextView logSizeText;
|
||||
private ImageButton themeToggle;
|
||||
private TextView versionFooter;
|
||||
private ProgressBar logProgress;
|
||||
|
||||
private ActivityResultLauncher<Intent> vpnPermissionLauncher;
|
||||
private ActivityResultLauncher<String[]> requestPermissionsLauncher;
|
||||
private ActivityResultLauncher<Intent> batteryOptLauncher;
|
||||
|
||||
private boolean isReadingLogs = false;
|
||||
private final Handler sizeUpdateHandler = new Handler();
|
||||
private Runnable sizeUpdateRunnable;
|
||||
|
||||
private final BroadcastReceiver logReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (IIABWatchdog.ACTION_LOG_MESSAGE.equals(intent.getAction())) {
|
||||
String message = intent.getStringExtra(IIABWatchdog.EXTRA_MESSAGE);
|
||||
addToLog(message);
|
||||
updateLogSizeUI();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
prefs = new Preferences(this);
|
||||
setContentView(R.layout.main);
|
||||
|
||||
// 1. Initialize Result Launchers
|
||||
vpnPermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == RESULT_OK && prefs.getEnable()) {
|
||||
connectVpn();
|
||||
}
|
||||
checkBatteryOptimizations();
|
||||
}
|
||||
);
|
||||
|
||||
batteryOptLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
Log.d(TAG, "Returned from the battery settings screen");
|
||||
checkBatteryOptimizations();
|
||||
}
|
||||
);
|
||||
|
||||
requestPermissionsLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestMultiplePermissions(),
|
||||
result -> {
|
||||
for (Map.Entry<String, Boolean> entry : result.entrySet()) {
|
||||
if (entry.getKey().equals(TERMUX_PERMISSION)) {
|
||||
addToLog(entry.getValue() ? "Termux permission granted" : "Termux permission denied");
|
||||
} else if (entry.getKey().equals(Manifest.permission.POST_NOTIFICATIONS)) {
|
||||
addToLog(entry.getValue() ? "Notification permission granted" : "Notification permission denied");
|
||||
}
|
||||
}
|
||||
prepareVpn();
|
||||
}
|
||||
);
|
||||
|
||||
// UI Bindings
|
||||
edittext_socks_addr = findViewById(R.id.socks_addr);
|
||||
edittext_socks_udp_addr = findViewById(R.id.socks_udp_addr);
|
||||
edittext_socks_port = findViewById(R.id.socks_port);
|
||||
edittext_socks_user = findViewById(R.id.socks_user);
|
||||
edittext_socks_pass = findViewById(R.id.socks_pass);
|
||||
edittext_dns_ipv4 = findViewById(R.id.dns_ipv4);
|
||||
edittext_dns_ipv6 = findViewById(R.id.dns_ipv6);
|
||||
checkbox_ipv4 = findViewById(R.id.ipv4);
|
||||
checkbox_ipv6 = findViewById(R.id.ipv6);
|
||||
checkbox_global = findViewById(R.id.global);
|
||||
checkbox_udp_in_tcp = findViewById(R.id.udp_in_tcp);
|
||||
checkbox_remote_dns = findViewById(R.id.remote_dns);
|
||||
checkbox_maintenance = findViewById(R.id.checkbox_maintenance);
|
||||
checkbox_maintenance.setOnClickListener(this);
|
||||
textview_maintenance_warning = findViewById(R.id.maintenance_warning);
|
||||
button_apps = findViewById(R.id.apps);
|
||||
button_save = findViewById(R.id.save);
|
||||
button_control = findViewById(R.id.control);
|
||||
button_browse_content = findViewById(R.id.btnBrowseContent);
|
||||
watchdogControl = findViewById(R.id.watchdog_control);
|
||||
|
||||
logActions = findViewById(R.id.log_actions);
|
||||
Button btnClearLog = findViewById(R.id.btn_clear_log);
|
||||
Button btnCopyLog = findViewById(R.id.btn_copy_log);
|
||||
connectionLog = findViewById(R.id.connection_log);
|
||||
logProgress = findViewById(R.id.log_progress);
|
||||
logWarning = findViewById(R.id.log_warning_text);
|
||||
logSizeText = findViewById(R.id.log_size_text);
|
||||
themeToggle = findViewById(R.id.theme_toggle);
|
||||
versionFooter = findViewById(R.id.version_text);
|
||||
configLayout = findViewById(R.id.config_layout);
|
||||
configLabel = findViewById(R.id.config_label);
|
||||
advancedConfig = findViewById(R.id.advanced_config);
|
||||
advConfigLabel = findViewById(R.id.adv_config_label);
|
||||
logLabel = findViewById(R.id.log_label);
|
||||
|
||||
// Listeners
|
||||
watchdogControl.setOnClickListener(this);
|
||||
btnClearLog.setOnClickListener(this);
|
||||
btnCopyLog.setOnClickListener(this);
|
||||
themeToggle.setOnClickListener(v -> toggleTheme());
|
||||
configLabel.setOnClickListener(v -> handleConfigToggle());
|
||||
advConfigLabel.setOnClickListener(v -> toggleVisibility(advancedConfig, advConfigLabel, getString(R.string.advanced_settings_label)));
|
||||
logLabel.setOnClickListener(v -> handleLogToggle());
|
||||
checkbox_udp_in_tcp.setOnClickListener(this);
|
||||
checkbox_remote_dns.setOnClickListener(this);
|
||||
checkbox_global.setOnClickListener(this);
|
||||
button_apps.setOnClickListener(this);
|
||||
button_save.setOnClickListener(this);
|
||||
button_control.setOnClickListener(this);
|
||||
|
||||
// Logic to open the WebView (PortalActivity)
|
||||
button_browse_content.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(MainActivity.this, PortalActivity.class);
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
connectionLog.setMovementMethod(new ScrollingMovementMethod());
|
||||
connectionLog.setTextIsSelectable(true);
|
||||
connectionLog.setOnTouchListener((v, event) -> {
|
||||
v.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
|
||||
v.getParent().requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
applySavedTheme();
|
||||
setVersionFooter();
|
||||
updateUI();
|
||||
initiatePermissionChain();
|
||||
|
||||
addToLog(getString(R.string.app_started));
|
||||
|
||||
sizeUpdateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateLogSizeUI();
|
||||
sizeUpdateHandler.postDelayed(this, 10000);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void showBatterySnackbar() {
|
||||
View rootView = findViewById(android.R.id.content);
|
||||
Snackbar.make(rootView, R.string.battery_opt_denied, Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction(R.string.fix_action, v -> checkBatteryOptimizations())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void initiatePermissionChain() {
|
||||
List<String> permissions = new ArrayList<>();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||
permissions.add(Manifest.permission.POST_NOTIFICATIONS);
|
||||
}
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(this, TERMUX_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
|
||||
permissions.add(TERMUX_PERMISSION);
|
||||
}
|
||||
|
||||
if (!permissions.isEmpty()) {
|
||||
requestPermissionsLauncher.launch(permissions.toArray(new String[0]));
|
||||
} else {
|
||||
prepareVpn();
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareVpn() {
|
||||
Intent intent = VpnService.prepare(MainActivity.this);
|
||||
if (intent != null) {
|
||||
vpnPermissionLauncher.launch(intent);
|
||||
} else {
|
||||
if (prefs.getEnable()) connectVpn();
|
||||
checkBatteryOptimizations();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLogToggle() {
|
||||
boolean isOpening = connectionLog.getVisibility() == View.GONE;
|
||||
if (isOpening) {
|
||||
readBlackBoxLogs();
|
||||
startLogSizeUpdates();
|
||||
} else {
|
||||
stopLogSizeUpdates();
|
||||
}
|
||||
toggleVisibility(connectionLog, logLabel, getString(R.string.connection_log_label));
|
||||
logActions.setVisibility(connectionLog.getVisibility());
|
||||
if (logSizeText != null) logSizeText.setVisibility(connectionLog.getVisibility());
|
||||
}
|
||||
|
||||
private void startLogSizeUpdates() {
|
||||
sizeUpdateHandler.removeCallbacks(sizeUpdateRunnable);
|
||||
sizeUpdateHandler.post(sizeUpdateRunnable);
|
||||
}
|
||||
|
||||
private void stopLogSizeUpdates() {
|
||||
sizeUpdateHandler.removeCallbacks(sizeUpdateRunnable);
|
||||
}
|
||||
|
||||
private void updateLogSizeUI() {
|
||||
if (logSizeText == null) return;
|
||||
File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt");
|
||||
long size = logFile.exists() ? logFile.length() : 0;
|
||||
String sizeStr;
|
||||
if (size < 1024) {
|
||||
sizeStr = size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
sizeStr = String.format(Locale.getDefault(), "%.1f KB", size / 1024.0);
|
||||
} else {
|
||||
sizeStr = String.format(Locale.getDefault(), "%.2f MB", size / (1024.0 * 1024.0));
|
||||
}
|
||||
logSizeText.setText(getString(R.string.log_size_format, sizeStr));
|
||||
}
|
||||
|
||||
private void connectVpn() {
|
||||
Intent intent = new Intent(this, TProxyService.class);
|
||||
startService(intent.setAction(TProxyService.ACTION_CONNECT));
|
||||
addToLog(getString(R.string.vpn_permission_granted));
|
||||
}
|
||||
|
||||
private void readBlackBoxLogs() {
|
||||
if (isReadingLogs) return;
|
||||
isReadingLogs = true;
|
||||
if (logProgress != null) logProgress.setVisibility(View.VISIBLE);
|
||||
|
||||
new Thread(() -> {
|
||||
File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (!logFile.exists()) {
|
||||
sb.append(getString(R.string.no_blackbox_found)).append("\n");
|
||||
} else {
|
||||
sb.append(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(getString(R.string.error_reading_history, e.getMessage())).append("\n");
|
||||
}
|
||||
sb.append(getString(R.string.end_of_history)).append("\n");
|
||||
}
|
||||
|
||||
final String result = sb.toString();
|
||||
SharedPreferences internalPrefs = getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE);
|
||||
final boolean isRapid = internalPrefs.getBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
if (connectionLog != null) {
|
||||
connectionLog.setText(result);
|
||||
scrollToBottom();
|
||||
}
|
||||
if (logProgress != null) {
|
||||
logProgress.setVisibility(View.GONE);
|
||||
}
|
||||
if (logWarning != null) {
|
||||
logWarning.setVisibility(isRapid ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
updateLogSizeUI();
|
||||
isReadingLogs = false;
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void setVersionFooter() {
|
||||
try {
|
||||
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
String version = pInfo.versionName;
|
||||
versionFooter.setText(version);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
versionFooter.setText("v0.1.x");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
stopLogSizeUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Check battery status whenever returning to the app
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
|
||||
Log.d(TAG, "onResume: Battery still optimized, showing warning");
|
||||
showBatterySnackbar();
|
||||
}
|
||||
}
|
||||
|
||||
if (getIntent() != null && getIntent().getBooleanExtra(VpnRecoveryReceiver.EXTRA_RECOVERY, false)) {
|
||||
addToLog(getString(R.string.recovery_pulse_received));
|
||||
Intent vpnIntent = new Intent(this, TProxyService.class);
|
||||
startService(vpnIntent.setAction(TProxyService.ACTION_CONNECT));
|
||||
setIntent(null);
|
||||
}
|
||||
if (connectionLog != null && connectionLog.getVisibility() == View.VISIBLE) {
|
||||
startLogSizeUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
private void checkBatteryOptimizations() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
|
||||
String manufacturer = Build.MANUFACTURER.toLowerCase();
|
||||
String message = getString(R.string.battery_opt_msg);
|
||||
|
||||
if (manufacturer.contains("oppo") || manufacturer.contains("realme") || manufacturer.contains("xiaomi")) {
|
||||
|
||||
if (manufacturer.contains("oppo") || manufacturer.contains("realme")) {
|
||||
message += getString(R.string.battery_opt_oppo_extra);
|
||||
} else if (manufacturer.contains("xiaomi")) {
|
||||
message += getString(R.string.battery_opt_xiaomi_extra);
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.battery_opt_title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.go_to_settings, (dialog, which) -> openBatterySettings(manufacturer))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
else {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
batteryOptLauncher.launch(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void openBatterySettings(String manufacturer) {
|
||||
boolean success = false;
|
||||
|
||||
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"));
|
||||
startActivity(intent);
|
||||
success = true;
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName("com.coloros.oppoguardelf", "com.coloros.oppoguardelf.Permission.BackgroundAllowAppListActivity"));
|
||||
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", getPackageName());
|
||||
intent.putExtra("package_label", getString(R.string.app_name));
|
||||
startActivity(intent);
|
||||
success = true;
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
try {
|
||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
startActivity(intent);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleTheme() {
|
||||
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
|
||||
int currentMode = AppCompatDelegate.getDefaultNightMode();
|
||||
int nextMode = (currentMode == AppCompatDelegate.MODE_NIGHT_NO) ? AppCompatDelegate.MODE_NIGHT_YES :
|
||||
(currentMode == AppCompatDelegate.MODE_NIGHT_YES) ? AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM : AppCompatDelegate.MODE_NIGHT_NO;
|
||||
sharedPref.edit().putInt("ui_mode", nextMode).apply();
|
||||
AppCompatDelegate.setDefaultNightMode(nextMode);
|
||||
updateThemeToggleButton(nextMode);
|
||||
}
|
||||
|
||||
private void applySavedTheme() {
|
||||
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
|
||||
int savedMode = sharedPref.getInt("ui_mode", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
AppCompatDelegate.setDefaultNightMode(savedMode);
|
||||
updateThemeToggleButton(savedMode);
|
||||
}
|
||||
|
||||
private void updateThemeToggleButton(int mode) {
|
||||
if (mode == AppCompatDelegate.MODE_NIGHT_NO) themeToggle.setImageResource(R.drawable.ic_theme_dark);
|
||||
else if (mode == AppCompatDelegate.MODE_NIGHT_YES) themeToggle.setImageResource(R.drawable.ic_theme_light);
|
||||
else themeToggle.setImageResource(R.drawable.ic_theme_system);
|
||||
}
|
||||
|
||||
private void toggleVisibility(View view, TextView label, String text) {
|
||||
boolean isGone = view.getVisibility() == View.GONE;
|
||||
view.setVisibility(isGone ? View.VISIBLE : View.GONE);
|
||||
label.setText((isGone ? "▼ " : "▶ ") + text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
IntentFilter filter = new IntentFilter(IIABWatchdog.ACTION_LOG_MESSAGE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(logReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(logReceiver, filter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
try { unregisterReceiver(logReceiver); } catch (Exception e) {}
|
||||
stopLogSizeUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view == checkbox_global || view == checkbox_remote_dns || view == checkbox_maintenance) {
|
||||
savePrefs();
|
||||
updateUI();
|
||||
} else if (view == button_apps) {
|
||||
startActivity(new Intent(this, AppListActivity.class));
|
||||
} else if (view.getId() == R.id.save) {
|
||||
savePrefs();
|
||||
Toast.makeText(this, R.string.saved_toast, Toast.LENGTH_SHORT).show();
|
||||
addToLog(getString(R.string.settings_saved));
|
||||
} else if (view.getId() == R.id.control) handleControlClick();
|
||||
else if (view.getId() == R.id.watchdog_control) handleWatchdogClick();
|
||||
else if (view.getId() == R.id.btn_clear_log) showResetLogConfirmation();
|
||||
else if (view.getId() == R.id.btn_copy_log) {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("IIAB Log", connectionLog.getText().toString());
|
||||
if (clipboard != null) {
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(this, R.string.log_copied_toast, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showResetLogConfirmation() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.log_reset_confirm_title)
|
||||
.setMessage(R.string.log_reset_confirm_msg)
|
||||
.setPositiveButton(R.string.reset_log, (dialog, which) -> resetLogFile())
|
||||
.setNegativeButton(R.string.cancel, null).show();
|
||||
}
|
||||
|
||||
private void resetLogFile() {
|
||||
File logFile = new File(getFilesDir(), "watchdog_heartbeat_log.txt");
|
||||
try (PrintWriter pw = new PrintWriter(logFile)) {
|
||||
pw.print("");
|
||||
connectionLog.setText("");
|
||||
addToLog(getString(R.string.log_reset_user));
|
||||
getSharedPreferences("IIAB_Internal", Context.MODE_PRIVATE).edit().putBoolean(IIABWatchdog.PREF_RAPID_GROWTH, false).apply();
|
||||
if (logWarning != null) logWarning.setVisibility(View.GONE);
|
||||
updateLogSizeUI();
|
||||
Toast.makeText(this, R.string.log_cleared_toast, Toast.LENGTH_SHORT).show();
|
||||
} catch (IOException e) {
|
||||
Toast.makeText(this, getString(R.string.failed_reset_log, e.getMessage()), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleWatchdogClick() {
|
||||
// if (prefs.getWatchdogEnable()) showWatchdogBiometricPrompt();
|
||||
// else toggleWatchdog(false);
|
||||
toggleWatchdog(prefs.getWatchdogEnable());
|
||||
}
|
||||
|
||||
private void toggleWatchdog(boolean stop) {
|
||||
prefs.setWatchdogEnable(!stop);
|
||||
Intent intent = new Intent(this, WatchdogService.class);
|
||||
if (stop) {
|
||||
stopService(intent);
|
||||
addToLog(getString(R.string.watchdog_stopped));
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(intent.setAction(WatchdogService.ACTION_START));
|
||||
else startService(intent.setAction(WatchdogService.ACTION_START));
|
||||
addToLog(getString(R.string.watchdog_started));
|
||||
}
|
||||
updateUI();
|
||||
}
|
||||
|
||||
private void handleControlClick() {
|
||||
if (prefs.getEnable()) showBiometricPrompt();
|
||||
else {
|
||||
BiometricManager bm = BiometricManager.from(this);
|
||||
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) getSystemService(Context.KEYGUARD_SERVICE);
|
||||
if (bm.canAuthenticate(auth) == BiometricManager.BIOMETRIC_SUCCESS || (km != null && km.isDeviceSecure())) {
|
||||
addToLog(getString(R.string.user_initiated_conn));
|
||||
toggleService(false);
|
||||
} else showEnrollmentDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void showEnrollmentDialog() {
|
||||
new AlertDialog.Builder(this)
|
||||
.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);
|
||||
startActivity(intent);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showBiometricPrompt() {
|
||||
Executor ex = ContextCompat.getMainExecutor(this);
|
||||
BiometricPrompt bp = new BiometricPrompt(this, ex, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
addToLog(getString(R.string.auth_success_disconnect));
|
||||
toggleService(true);
|
||||
}
|
||||
});
|
||||
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
bp.authenticate(new BiometricPrompt.PromptInfo.Builder().setTitle(getString(R.string.auth_required_title)).setSubtitle(getString(R.string.auth_required_subtitle)).setAllowedAuthenticators(auth).build());
|
||||
}
|
||||
|
||||
// --- Secure Advanced Settings Menu ---
|
||||
private void handleConfigToggle() {
|
||||
if (configLayout.getVisibility() == View.GONE) {
|
||||
// The menu is closed. We want to open it, so we check for security first.
|
||||
BiometricManager bm = BiometricManager.from(this);
|
||||
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) getSystemService(Context.KEYGUARD_SERVICE);
|
||||
|
||||
if (bm.canAuthenticate(auth) == BiometricManager.BIOMETRIC_SUCCESS || (km != null && km.isDeviceSecure())) {
|
||||
showConfigBiometricPrompt();
|
||||
} else {
|
||||
showEnrollmentDialog(); // Forces user to set a PIN if device has no security
|
||||
}
|
||||
} else {
|
||||
// The menu is open. Just close it without asking for fingerprint.
|
||||
toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label));
|
||||
}
|
||||
}
|
||||
|
||||
private void showConfigBiometricPrompt() {
|
||||
Executor ex = ContextCompat.getMainExecutor(this);
|
||||
BiometricPrompt bp = new BiometricPrompt(this, ex, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
// Auth successful! Open the menu.
|
||||
toggleVisibility(configLayout, configLabel, getString(R.string.advanced_settings_label));
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Reusing your existing strings to avoid compilation errors
|
||||
bp.authenticate(new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.auth_required_title))
|
||||
.setSubtitle(getString(R.string.auth_required_subtitle))
|
||||
.setAllowedAuthenticators(auth)
|
||||
.build());
|
||||
}
|
||||
// ------------------------------------------------
|
||||
|
||||
private void showWatchdogBiometricPrompt() {
|
||||
Executor ex = ContextCompat.getMainExecutor(this);
|
||||
BiometricPrompt bp = new BiometricPrompt(this, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
toggleWatchdog(true);
|
||||
}
|
||||
});
|
||||
int auth = BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
bp.authenticate(new BiometricPrompt.PromptInfo.Builder().setTitle(getString(R.string.unlock_watchdog_title)).setSubtitle(getString(R.string.unlock_watchdog_subtitle)).setAllowedAuthenticators(auth).build());
|
||||
}
|
||||
|
||||
private void toggleService(boolean stop) {
|
||||
prefs.setEnable(!stop);
|
||||
savePrefs();
|
||||
updateUI();
|
||||
Intent intent = new Intent(this, TProxyService.class);
|
||||
startService(intent.setAction(stop ? TProxyService.ACTION_DISCONNECT : TProxyService.ACTION_CONNECT));
|
||||
addToLog(getString(stop ? R.string.vpn_stopping : R.string.vpn_starting));
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
boolean vpnActive = prefs.getEnable();
|
||||
boolean watchdogActive = prefs.getWatchdogEnable();
|
||||
if (vpnActive) {
|
||||
button_control.setText(R.string.control_disable);
|
||||
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_on));
|
||||
} else {
|
||||
button_control.setText(R.string.control_enable);
|
||||
button_control.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_vpn_off));
|
||||
}
|
||||
if (vpnActive) {
|
||||
button_browse_content.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
button_browse_content.setVisibility(View.GONE);
|
||||
}
|
||||
if (watchdogActive) {
|
||||
watchdogControl.setText(R.string.watchdog_disable);
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_on));
|
||||
} else {
|
||||
watchdogControl.setText(R.string.watchdog_enable);
|
||||
watchdogControl.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.btn_watchdog_off));
|
||||
}
|
||||
edittext_socks_addr.setText(prefs.getSocksAddress());
|
||||
edittext_socks_udp_addr.setText(prefs.getSocksUdpAddress());
|
||||
edittext_socks_port.setText(String.valueOf(prefs.getSocksPort()));
|
||||
edittext_socks_user.setText(prefs.getSocksUsername());
|
||||
edittext_socks_pass.setText(prefs.getSocksPassword());
|
||||
edittext_dns_ipv4.setText(prefs.getDnsIpv4());
|
||||
edittext_dns_ipv6.setText(prefs.getDnsIpv6());
|
||||
checkbox_ipv4.setChecked(prefs.getIpv4());
|
||||
checkbox_ipv6.setChecked(prefs.getIpv6());
|
||||
checkbox_global.setChecked(prefs.getGlobal());
|
||||
checkbox_udp_in_tcp.setChecked(prefs.getUdpInTcp());
|
||||
checkbox_remote_dns.setChecked(prefs.getRemoteDns());
|
||||
checkbox_maintenance.setChecked(prefs.getMaintenanceMode());
|
||||
boolean editable = !vpnActive;
|
||||
edittext_socks_addr.setEnabled(editable);
|
||||
edittext_socks_port.setEnabled(editable);
|
||||
button_save.setEnabled(editable);
|
||||
|
||||
checkbox_maintenance.setEnabled(editable);
|
||||
if (textview_maintenance_warning != null) {
|
||||
textview_maintenance_warning.setVisibility(vpnActive ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
//DEFAULT VALUES ON ORIGINAL INTERFACE
|
||||
// private void savePrefs() {
|
||||
// prefs.setSocksAddress(edittext_socks_addr.getText().toString());
|
||||
// prefs.setSocksPort(Integer.parseInt(edittext_socks_port.getText().toString()));
|
||||
// prefs.setSocksUdpAddress(edittext_socks_udp_addr.getText().toString());
|
||||
// prefs.setSocksUsername(edittext_socks_user.getText().toString());
|
||||
// prefs.setSocksPassword(edittext_socks_pass.getText().toString());
|
||||
// prefs.setDnsIpv4(edittext_dns_ipv4.getText().toString());
|
||||
// prefs.setDnsIpv6(edittext_dns_ipv6.getText().toString());
|
||||
// prefs.setIpv4(checkbox_ipv4.isChecked());
|
||||
// prefs.setIpv6(checkbox_ipv6.isChecked());
|
||||
// prefs.setGlobal(checkbox_global.isChecked());
|
||||
// prefs.setUdpInTcp(checkbox_udp_in_tcp.isChecked());
|
||||
// prefs.setRemoteDns(checkbox_remote_dns.isChecked());
|
||||
// prefs.setMaintenanceMode(checkbox_maintenance.isChecked());
|
||||
// }
|
||||
|
||||
private void savePrefs() {
|
||||
// 1. Hardcoded / Hidden Secure Values (Walled Garden defaults)
|
||||
prefs.setSocksAddress("127.0.0.1");
|
||||
prefs.setSocksPort(1080);
|
||||
prefs.setSocksUdpAddress(""); // Empty by default
|
||||
prefs.setSocksUsername("");
|
||||
prefs.setSocksPassword("");
|
||||
prefs.setIpv4(true);
|
||||
prefs.setIpv6(true);
|
||||
prefs.setUdpInTcp(false);
|
||||
prefs.setRemoteDns(true);
|
||||
|
||||
// CRITICAL: Force Global to TRUE so the tunnel catches ALL system traffic
|
||||
prefs.setGlobal(true);
|
||||
|
||||
// 2. User Editable Values (The only things read from the UI)
|
||||
prefs.setDnsIpv4(edittext_dns_ipv4.getText().toString());
|
||||
prefs.setDnsIpv6(edittext_dns_ipv6.getText().toString());
|
||||
prefs.setMaintenanceMode(checkbox_maintenance.isChecked());
|
||||
}
|
||||
|
||||
private void addToLog(String message) {
|
||||
runOnUiThread(() -> {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
|
||||
String currentTime = sdf.format(new Date());
|
||||
String logEntry = "[" + currentTime + "] " + message + "\n";
|
||||
if (connectionLog != null) {
|
||||
connectionLog.append(logEntry);
|
||||
scrollToBottom();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scrollToBottom() {
|
||||
if (connectionLog.getLayout() != null) {
|
||||
int scroll = connectionLog.getLayout().getLineTop(connectionLog.getLineCount()) - connectionLog.getHeight();
|
||||
if (scroll > 0) connectionLog.scrollTo(0, scroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,194 +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);
|
||||
|
||||
// Button actions
|
||||
btnBack.setOnClickListener(v -> { if (webView.canGoBack()) webView.goBack(); });
|
||||
btnForward.setOnClickListener(v -> { if (webView.canGoForward()) webView.goForward(); });
|
||||
btnHome.setOnClickListener(v -> webView.loadUrl("file:///android_asset/index.html"));
|
||||
|
||||
// Dual logic: Forced reload or Stop
|
||||
btnReload.setOnClickListener(v -> {
|
||||
if (isPageLoading) {
|
||||
webView.stopLoading();
|
||||
} else {
|
||||
// 1. Disable cache temporarily
|
||||
webView.getSettings().setCacheMode(android.webkit.WebSettings.LOAD_NO_CACHE);
|
||||
// 2. Force download from scratch
|
||||
webView.reload();
|
||||
}
|
||||
});
|
||||
|
||||
// --- 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();
|
||||
|
||||
// 1. Local main menu
|
||||
if (url.startsWith("file://")) {
|
||||
return false; // return false means: "WebView, handle it yourself"
|
||||
}
|
||||
|
||||
// 2. 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
|
||||
}
|
||||
|
||||
// 3. 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);
|
||||
}
|
||||
});
|
||||
|
||||
// --- 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);
|
||||
};
|
||||
|
||||
// --- 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. Start 5-second countdown (5000 ms)
|
||||
hideHandler.removeCallbacks(hideRunnable); // Cancel if there was a previous countdown
|
||||
hideHandler.postDelayed(hideRunnable, 5000); // Change 5000 to 7000 for 7 seconds
|
||||
});
|
||||
|
||||
// --- 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);
|
||||
|
||||
// 2. Port and Mirror logic
|
||||
Preferences prefs = new Preferences(this);
|
||||
int tempPort = prefs.getSocksPort();
|
||||
if (tempPort <= 0) tempPort = 1080;
|
||||
|
||||
// Variable safe to read in lambda
|
||||
final int finalProxyPort = tempPort;
|
||||
|
||||
// 3. Proxy block
|
||||
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("file:///android_asset/index.html");
|
||||
});
|
||||
} else {
|
||||
// Fallback for older devices
|
||||
Log.w(TAG, "Proxy Override not supported");
|
||||
}
|
||||
webView.loadUrl("file:///android_asset/index.html");
|
||||
}
|
||||
|
||||
// 4. Cleanup (Important to not leave the proxy active)
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
|
||||
ProxyController.getInstance().clearProxyOverride(Runnable::run, () -> {
|
||||
Log.d(TAG, "WebView proxy released");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,230 +0,0 @@
|
|||
/*
|
||||
============================================================================
|
||||
Name : Preferences.java
|
||||
Author : hev <r@hev.cc>
|
||||
Copyright : Copyright (c) 2023 xyz
|
||||
Description : Preferences
|
||||
============================================================================
|
||||
*/
|
||||
|
||||
package org.iiab.controller;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class Preferences
|
||||
{
|
||||
public static final String PREFS_NAME = "SocksPrefs";
|
||||
public static final String SOCKS_ADDR = "SocksAddr";
|
||||
public static final String SOCKS_UDP_ADDR = "SocksUdpAddr";
|
||||
public static final String SOCKS_PORT = "SocksPort";
|
||||
public static final String SOCKS_USER = "SocksUser";
|
||||
public static final String SOCKS_PASS = "SocksPass";
|
||||
public static final String DNS_IPV4 = "DnsIpv4";
|
||||
public static final String DNS_IPV6 = "DnsIpv6";
|
||||
public static final String IPV4 = "Ipv4";
|
||||
public static final String IPV6 = "Ipv6";
|
||||
public static final String GLOBAL = "Global";
|
||||
public static final String UDP_IN_TCP = "UdpInTcp";
|
||||
public static final String REMOTE_DNS = "RemoteDNS";
|
||||
public static final String APPS = "Apps";
|
||||
public static final String ENABLE = "Enable";
|
||||
public static final String WATCHDOG_ENABLE = "WatchdogEnable";
|
||||
public static final String MAINTENANCE_MODE = "MaintenanceMode";
|
||||
|
||||
private SharedPreferences prefs;
|
||||
|
||||
public Preferences(Context context) {
|
||||
prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_MULTI_PROCESS);
|
||||
}
|
||||
|
||||
public String getSocksAddress() {
|
||||
return prefs.getString(SOCKS_ADDR, "127.0.0.1");
|
||||
}
|
||||
|
||||
public void setSocksAddress(String addr) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(SOCKS_ADDR, addr);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public String getSocksUdpAddress() {
|
||||
return prefs.getString(SOCKS_UDP_ADDR, "");
|
||||
}
|
||||
|
||||
public void setSocksUdpAddress(String addr) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(SOCKS_UDP_ADDR, addr);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public int getSocksPort() {
|
||||
return prefs.getInt(SOCKS_PORT, 1080);
|
||||
}
|
||||
|
||||
public void setSocksPort(int port) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putInt(SOCKS_PORT, port);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public String getSocksUsername() {
|
||||
return prefs.getString(SOCKS_USER, "");
|
||||
}
|
||||
|
||||
public void setSocksUsername(String user) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(SOCKS_USER, user);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public String getSocksPassword() {
|
||||
return prefs.getString(SOCKS_PASS, "");
|
||||
}
|
||||
|
||||
public void setSocksPassword(String pass) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(SOCKS_PASS, pass);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public String getDnsIpv4() {
|
||||
return prefs.getString(DNS_IPV4, "8.8.8.8");
|
||||
}
|
||||
|
||||
public void setDnsIpv4(String addr) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(DNS_IPV4, addr);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public String getDnsIpv6() {
|
||||
return prefs.getString(DNS_IPV6, "2001:4860:4860::8888");
|
||||
}
|
||||
|
||||
public void setDnsIpv6(String addr) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(DNS_IPV6, addr);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public String getMappedDns() {
|
||||
return "198.18.0.2";
|
||||
}
|
||||
|
||||
public boolean getUdpInTcp() {
|
||||
return prefs.getBoolean(UDP_IN_TCP, false);
|
||||
}
|
||||
|
||||
public void setUdpInTcp(boolean enable) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(UDP_IN_TCP, enable);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getRemoteDns() {
|
||||
return prefs.getBoolean(REMOTE_DNS, true);
|
||||
}
|
||||
|
||||
public void setRemoteDns(boolean enable) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(REMOTE_DNS, enable);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getIpv4() {
|
||||
return prefs.getBoolean(IPV4, true);
|
||||
}
|
||||
|
||||
public void setIpv4(boolean enable) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(IPV4, enable);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getIpv6() {
|
||||
return prefs.getBoolean(IPV6, true);
|
||||
}
|
||||
|
||||
public void setIpv6(boolean enable) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(IPV6, enable);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getGlobal() {
|
||||
return prefs.getBoolean(GLOBAL, false);
|
||||
}
|
||||
|
||||
public void setGlobal(boolean enable) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(GLOBAL, enable);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public Set<String> getApps() {
|
||||
return prefs.getStringSet(APPS, new HashSet<String>());
|
||||
}
|
||||
|
||||
public void setApps(Set<String> apps) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putStringSet(APPS, apps);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getEnable() {
|
||||
return prefs.getBoolean(ENABLE, false);
|
||||
}
|
||||
|
||||
public void setEnable(boolean enable) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(ENABLE, enable);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getWatchdogEnable() {
|
||||
return prefs.getBoolean(WATCHDOG_ENABLE, false);
|
||||
}
|
||||
|
||||
public void setWatchdogEnable(boolean enable) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(WATCHDOG_ENABLE, enable);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getMaintenanceMode() {
|
||||
return prefs.getBoolean(MAINTENANCE_MODE, false);
|
||||
}
|
||||
|
||||
public void setMaintenanceMode(boolean enable) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(MAINTENANCE_MODE, enable);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public int getTunnelMtu() {
|
||||
return 8500;
|
||||
}
|
||||
|
||||
public String getTunnelIpv4Address() {
|
||||
return "198.18.0.1";
|
||||
}
|
||||
|
||||
public int getTunnelIpv4Prefix() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
public String getTunnelIpv6Address() {
|
||||
return "fc00::1";
|
||||
}
|
||||
|
||||
public int getTunnelIpv6Prefix() {
|
||||
return 128;
|
||||
}
|
||||
|
||||
public int getTaskStackSize() {
|
||||
return 81920;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
============================================================================
|
||||
Name : ServiceReceiver.java
|
||||
Author : hev <r@hev.cc>
|
||||
Copyright : Copyright (c) 2023 xyz
|
||||
Description : ServiceReceiver
|
||||
============================================================================
|
||||
*/
|
||||
|
||||
package org.iiab.controller;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.VpnService;
|
||||
import android.os.Build;
|
||||
|
||||
public class ServiceReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
|
||||
Preferences prefs = new Preferences(context);
|
||||
|
||||
/* Auto-start */
|
||||
if (prefs.getEnable()) {
|
||||
Intent i = VpnService.prepare(context);
|
||||
if (i != null) {
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(i);
|
||||
}
|
||||
i = new Intent(context, TProxyService.class);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(i.setAction(TProxyService.ACTION_CONNECT));
|
||||
} else {
|
||||
context.startService(i.setAction(TProxyService.ACTION_CONNECT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,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, "Syncing Watchdog state. Enabled: " + 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, "Watchdog Thread: Started loop");
|
||||
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, "Watchdog Thread: Interrupted, stopping...");
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Watchdog Thread: Error in loop", e);
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Watchdog Thread: Loop 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, "CPU WakeLock acquired under VPN shield");
|
||||
}
|
||||
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, "Wi-Fi Lock acquired under VPN shield");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error acquiring locks", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseLocks() {
|
||||
if (wakeLock != null && wakeLock.isHeld()) {
|
||||
wakeLock.release();
|
||||
wakeLock = null;
|
||||
Log.i(TAG, "CPU WakeLock released");
|
||||
}
|
||||
if (wifiLock != null && wifiLock.isHeld()) {
|
||||
wifiLock.release();
|
||||
wifiLock = null;
|
||||
Log.i(TAG, "Wi-Fi 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, "Maintenance mode enabled: Termux has direct Internet access");
|
||||
}
|
||||
} 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 = "socks5";
|
||||
initNotificationChannel(channelName);
|
||||
createNotification(channelName);
|
||||
|
||||
// Start loop and locks if enabled
|
||||
syncWatchdogLocks();
|
||||
}
|
||||
|
||||
public void stopService() {
|
||||
if (tunFd == null)
|
||||
return;
|
||||
|
||||
stopWatchdogLoop();
|
||||
releaseLocks();
|
||||
stopForeground(true);
|
||||
|
||||
/* TProxy */
|
||||
TProxyStopService();
|
||||
|
||||
/* VPN */
|
||||
try {
|
||||
tunFd.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
tunFd = null;
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private void createNotification(String channelName) {
|
||||
Intent i = new Intent(this, MainActivity.class);
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_IMMUTABLE);
|
||||
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, channelName);
|
||||
Notification notify = notification
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setSmallIcon(android.R.drawable.sym_def_app_icon)
|
||||
.setContentIntent(pi)
|
||||
.build();
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(1, notify);
|
||||
} else {
|
||||
startForeground(1, notify, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||
}
|
||||
}
|
||||
|
||||
// create NotificationChannel
|
||||
private void initNotificationChannel(String channelName) {
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
CharSequence name = getString(R.string.app_name);
|
||||
NotificationChannel channel = new NotificationChannel(channelName, name, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package org.iiab.controller;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class TermuxCallbackReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (IIABWatchdog.ACTION_TERMUX_OUTPUT.equals(intent.getAction())) {
|
||||
Bundle resultExtras = intent.getExtras();
|
||||
if (resultExtras != null) {
|
||||
int exitCode = resultExtras.getInt("exitCode", -1);
|
||||
String stdout = resultExtras.getString("stdout", "");
|
||||
String stderr = resultExtras.getString("stderr", "");
|
||||
|
||||
String logMsg;
|
||||
if (exitCode == 0) {
|
||||
logMsg = "[Termux] Stimulus OK (exit 0)";
|
||||
} else {
|
||||
logMsg = "[Termux] Pulse Error (exit " + exitCode + ")";
|
||||
if (!stderr.isEmpty()) {
|
||||
logMsg += ": " + stderr;
|
||||
}
|
||||
}
|
||||
|
||||
// Write to BlackBox log
|
||||
IIABWatchdog.writeToBlackBox(context, logMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
package org.iiab.controller;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
public class VpnRecoveryReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "IIAB-VpnRecovery";
|
||||
public static final String EXTRA_RECOVERY = "recovery_mode";
|
||||
private static final String CHANNEL_ID = "recovery_channel";
|
||||
private static final int NOTIFICATION_ID = 911;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if ("org.iiab.controller.RECOVER_VPN".equals(intent.getAction())) {
|
||||
Log.i(TAG, "Boomerang Signal Received! Triggering high-priority recovery...");
|
||||
|
||||
Preferences prefs = new Preferences(context);
|
||||
if (prefs.getEnable()) {
|
||||
showRecoveryNotification(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showRecoveryNotification(Context context) {
|
||||
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
CHANNEL_ID, "VPN Recovery",
|
||||
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("Safe Pocket Web Interrupted")
|
||||
.setContentText("Tap to restore secure environment immediately.")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(true)
|
||||
.setFullScreenIntent(pendingIntent, true) // High priority request to open
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
if (manager != null) {
|
||||
manager.notify(NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,143 +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";
|
||||
|
||||
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_STOP.equals(action)) {
|
||||
stopWatchdog();
|
||||
return START_NOT_STICKY;
|
||||
} else if (ACTION_HEARTBEAT.equals(action)) {
|
||||
IIABWatchdog.performHeartbeat(this);
|
||||
// CRITICAL: Reschedule for the next pulse to create an infinite loop
|
||||
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();
|
||||
}
|
||||
|
||||
private void stopWatchdog() {
|
||||
cancelHeartbeat();
|
||||
IIABWatchdog.logSessionStop(this);
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 void onDestroy() {
|
||||
stopWatchdog();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@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, "IIAB Watchdog Service",
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
);
|
||||
channel.setDescription("Ensures services remain active when screen is off.");
|
||||
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("IIAB Watchdog Active")
|
||||
.setContentText("Protecting Termux environment...")
|
||||
.setSmallIcon(android.R.drawable.ic_lock_idle_lock)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setOngoing(true)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# Copyright (C) 2023 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
include $(call all-subdir-makefiles)
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# Copyright (C) 2023 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
APP_OPTIM := release
|
||||
APP_PLATFORM := android-29
|
||||
APP_ABI := armeabi-v7a arm64-v8a
|
||||
APP_CFLAGS := -O3 -DPKGNAME=hev/sockstun
|
||||
APP_CPPFLAGS := -O3 -std=c++11
|
||||
NDK_TOOLCHAIN_VERSION := clang
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 4d6c334dbfb68a79d1970c2744e62d09f71df12f
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="135.47"
|
||||
android:viewportHeight="135.47">
|
||||
<path
|
||||
android:pathData="M0.23,0.23h135v135h-135z"
|
||||
android:strokeWidth="0.462333"
|
||||
android:fillColor="#000000"
|
||||
android:strokeColor="#000000"/>
|
||||
<path
|
||||
android:pathData="m39.63,90.18h1.56q-0.18,1.68 -1.27,2.8 -1.09,1.12 -3.22,1.12 -2.06,0 -3.33,-1.47 -1.26,-1.48 -1.28,-3.93v-1.27q0,-2.49 1.28,-3.99 1.28,-1.5 3.48,-1.5 2.01,0 3.09,1.11 1.08,1.11 1.26,2.85h-1.56q-0.18,-1.24 -0.8,-1.95 -0.62,-0.72 -1.99,-0.72 -1.57,0 -2.38,1.15 -0.81,1.15 -0.81,3.04v1.2q0,1.75 0.74,2.98 0.74,1.23 2.32,1.23 1.5,0 2.11,-0.7 0.62,-0.7 0.82,-1.95zM42.69,89.45q0,-1.91 1.07,-3.19 1.07,-1.28 2.92,-1.28 1.85,0 2.92,1.26 1.07,1.25 1.1,3.13v0.27q0,1.91 -1.08,3.19 -1.07,1.28 -2.92,1.28 -1.85,0 -2.93,-1.28 -1.07,-1.28 -1.07,-3.19zM44.2,89.64q0,1.31 0.62,2.27 0.63,0.96 1.89,0.96 1.23,0 1.85,-0.94 0.63,-0.95 0.63,-2.26v-0.21q0,-1.29 -0.63,-2.26 -0.63,-0.98 -1.88,-0.98 -1.24,0 -1.87,0.98 -0.62,0.97 -0.62,2.26zM56.24,86.25q-0.72,0 -1.28,0.39 -0.55,0.39 -0.87,1.02v6.28h-1.5v-8.8h1.42l0.05,1.1q1,-1.26 2.63,-1.26 1.29,0 2.05,0.72 0.76,0.72 0.77,2.43v5.8h-1.51v-5.78q0,-1.03 -0.46,-1.47 -0.45,-0.44 -1.3,-0.44zM65.51,93.94q-0.51,0.16 -1.16,0.16 -0.84,0 -1.43,-0.51 -0.59,-0.51 -0.59,-1.84v-5.45h-1.61v-1.15h1.61L62.32,83.01h1.5v2.14h1.64v1.15h-1.64v5.46q0,0.67 0.29,0.86 0.29,0.19 0.67,0.19 0.28,0 0.71,-0.1zM70.74,86.43q-1.47,0 -2.01,1.27v6.24h-1.5v-8.8h1.46l0.03,1.01q0.72,-1.17 2.09,-1.17 0.42,0 0.67,0.11l-0.01,1.4q-0.33,-0.07 -0.73,-0.07zM72.31,89.45q0,-1.91 1.07,-3.19 1.07,-1.28 2.92,-1.28 1.85,0 2.92,1.26 1.07,1.25 1.1,3.13v0.27q0,1.91 -1.08,3.19 -1.07,1.28 -2.92,1.28 -1.85,0 -2.93,-1.28 -1.07,-1.28 -1.07,-3.19zM73.81,89.64q0,1.31 0.62,2.27 0.63,0.96 1.89,0.96 1.23,0 1.85,-0.94 0.63,-0.95 0.63,-2.26v-0.21q0,-1.29 -0.63,-2.26 -0.63,-0.98 -1.88,-0.98 -1.24,0 -1.87,0.98 -0.62,0.97 -0.62,2.26zM83.84,81.45v12.49h-1.51v-12.49zM87.89,81.45v12.49h-1.51v-12.49zM97.26,92.4q-0.43,0.65 -1.22,1.18 -0.79,0.52 -2.09,0.52 -1.84,0 -2.94,-1.2 -1.1,-1.2 -1.1,-3.06v-0.34q0,-1.44 0.54,-2.45 0.55,-1.02 1.43,-1.54 0.88,-0.54 1.87,-0.54 1.89,0 2.75,1.24 0.87,1.23 0.87,3.07v0.67h-5.95q0.03,1.21 0.72,2.06 0.69,0.85 1.89,0.85 0.8,0 1.35,-0.33 0.55,-0.33 0.97,-0.87zM93.75,86.22q-0.89,0 -1.51,0.65 -0.62,0.65 -0.77,1.87h4.4v-0.11q-0.06,-0.88 -0.51,-1.64 -0.45,-0.76 -1.6,-0.76zM102.64,86.43q-1.47,0 -2.01,1.27v6.24h-1.5v-8.8h1.46l0.03,1.01q0.72,-1.17 2.09,-1.17 0.42,0 0.67,0.11l-0.01,1.4q-0.33,-0.07 -0.73,-0.07z"
|
||||
android:strokeWidth="0.491597"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M29.74,50.22L29.74,68.31L26.02,68.31L26.02,50.22ZM37.17,50.22L37.17,68.31L33.44,68.31L33.44,50.22ZM39.34,68.31 L46.08,50.22h3.45l6.77,18.08h-3.97l-1.24,-3.73h-6.55l-1.24,3.73zM45.54,61.56h4.53l-2.26,-6.79zM71.43,62.98q0,2.61 -1.66,3.96 -1.66,1.34 -4.74,1.37L57.99,68.31L57.99,50.22h6.35q3.14,0 4.92,1.2 1.78,1.2 1.78,3.74 0,1.23 -0.62,2.26 -0.62,1.03 -1.96,1.56 1.59,0.4 2.29,1.54 0.7,1.14 0.7,2.45zM61.72,53.24v4.55h2.61q2.96,0 2.96,-2.24 0,-2.26 -2.83,-2.31zM67.7,62.94q0,-2.43 -2.46,-2.52h-3.51v4.88h3.18q1.43,0 2.11,-0.67 0.68,-0.67 0.68,-1.69z"
|
||||
android:strokeWidth="0.751077"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M71.83,38.37L111.31,38.37L111.31,78.26L71.83,78.26ZM99.85,58.32a8.21,8.28 90,0 0,-8.28 -8.21,8.21 8.28,90 0,0 -8.28,8.21 8.21,8.28 90,0 0,8.28 8.21,8.21 8.28,90 0,0 8.28,-8.21z"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="m91.57,39.86a18.46,19.44 90,0 0,-3.37 0.29L86.98,45.84A13.23,13.93 90,0 0,82.48 48.31L76.69,46.46A18.46,19.44 90,0 0,73.31 52.01l4.58,3.84a13.23,13.93 90,0 0,-0.25 2.47,13.23 13.93,90 0,0 0.25,2.47l-4.58,3.84a18.46,19.44 90,0 0,3.37 5.55l5.79,-1.84a13.23,13.93 90,0 0,4.5 2.47l1.21,5.68a18.46,19.44 90,0 0,3.37 0.29,18.46 19.44,90 0,0 3.37,-0.29l1.21,-5.68a13.23,13.93 90,0 0,4.51 -2.47l5.78,1.84a18.46,19.44 90,0 0,3.37 -5.55l-4.57,-3.84a13.23,13.93 90,0 0,0.26 -2.47,13.23 13.93,90 0,0 -0.25,-2.48L109.83,52A18.46,19.44 90,0 0,106.46 46.46L100.67,48.3A13.23,13.93 90,0 0,96.15 45.82l-1.21,-5.68a18.46,19.44 90,0 0,-3.37 -0.28z"
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeColor="#00000000"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#FFFFFF">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36 -0.98,1.37 -2.58,2.26 -4.4,2.26 -3.03,0 -5.5,-2.47 -5.5,-5.5 0,-1.82 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3z" />
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#FFFFFF">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM2,13h2c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L2,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM20,13h2c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-2c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM11,2v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1L13,2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1zM11,20v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1zM5.99,4.58c-0.39,-0.39 -1.03,-0.39 -1.41,0s-0.39,1.03 0,1.41l1.06,1.06c0.39,0.39 1.03,0.39 1.41,0s0.39,-1.03 0,-1.41L5.99,4.58zM18.36,16.95c-0.39,-0.39 -1.03,-0.39 -1.41,0s-0.39,1.03 0,1.41l1.06,1.06c0.39,0.39 1.03,0.39 1.41,0s0.39,-1.03 0,-1.41l-1.06,-1.06zM19.42,5.99c0.39,-0.39 0.39,-1.03 0,-1.41s-1.03,-0.39 -1.41,0l-1.06,1.06c-0.39,0.39 -0.39,1.03 0,1.41s1.03,0.39 1.41,0l1.06,-1.06zM7.05,18.36c0.39,-0.39 0.39,-1.03 0,-1.41s-1.03,-0.39 -1.41,0l-1.06,1.06c-0.39,0.39 -0.39,1.03 0,1.41s1.03,0.39 1.41,0l1.06,-1.06z" />
|
||||
</vector>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#FFFFFF">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20V4c4.42,0 8,3.58 8,8s-3.58,8 -8,8z" />
|
||||
</vector>
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="12dp" />
|
||||
<solid android:color="#FFFFFF" /> <!-- Default color, replaced by backgroundTint -->
|
||||
</shape>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#888888" />
|
||||
<corners android:radius="5dp" />
|
||||
</shape>
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/myWebView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnHandle"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:background="#AA000000"
|
||||
android:text="⌃"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp"
|
||||
android:padding="0dp"
|
||||
android:stateListAnimator="@null" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottomNav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="#EEF2F2F2"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
android:elevation="8dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnHideNav"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:text="⌄"
|
||||
android:textSize="20sp"
|
||||
android:textColor="#555555"
|
||||
android:stateListAnimator="@null" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<Button android:id="@+id/btnBack" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="◀" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
|
||||
<Button android:id="@+id/btnHome" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="🏠" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
|
||||
<Button android:id="@+id/btnReload" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="↻" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
|
||||
|
||||
<Button android:id="@+id/btnExit" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="✖" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle" android:textColor="#D32F2F" />
|
||||
|
||||
<Button android:id="@+id/btnForward" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="▶" android:textSize="20sp" style="?android:attr/buttonBarButtonStyle"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="2dp" >
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true" >
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="100px"
|
||||
android:layout_height="100px"
|
||||
android:layout_marginLeft="5px"
|
||||
android:layout_marginRight="20px"
|
||||
android:layout_marginTop="5px" />
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="50px"
|
||||
android:maxEms="14"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true" />
|
||||
</LinearLayout>
|
||||
<CheckBox
|
||||
android:id="@+id/checked"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:focusable="false"
|
||||
android:clickable="false" />
|
||||
</RelativeLayout>
|
||||
|
|
@ -1,374 +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 -->
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/actionBarSize"
|
||||
android:background="#1A1A1A"
|
||||
android:padding="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/header_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@id/header_icon"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Triple Toggle Theme ImageButton -->
|
||||
<ImageButton
|
||||
android:id="@+id/theme_toggle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_theme_system"
|
||||
android:contentDescription="Toggle Theme"
|
||||
android:padding="8dp"
|
||||
android:scaleType="fitCenter" />
|
||||
</RelativeLayout>
|
||||
|
||||
<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">
|
||||
|
||||
<!-- HR above Watchdog -->
|
||||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#444444" android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Watchdog Control Section -->
|
||||
<Button
|
||||
android:id="@+id/watchdog_control"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="90dp"
|
||||
android:text="@string/watchdog_enable"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#FFFFFF"
|
||||
android:background="@drawable/rounded_button"
|
||||
android:backgroundTint="#1976D2"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textAllCaps="false"/>
|
||||
|
||||
<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"/>
|
||||
|
||||
<!-- HR below Watchdog -->
|
||||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#444444" android:layout_marginBottom="20dp"/>
|
||||
|
||||
<!-- 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="#2E7D32"
|
||||
android:textAllCaps="false"/>
|
||||
|
||||
<TextView
|
||||
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="#F57C00"
|
||||
android:textAllCaps="false"
|
||||
android:elevation="4dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<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" />
|
||||
|
||||
<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>
|
||||
|
||||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="#444444" 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="#FF9800"
|
||||
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="#D32F2F"
|
||||
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="#388E3C"
|
||||
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="#444444" 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="#888888"
|
||||
android:textSize="10sp"
|
||||
android:paddingBottom="16dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 870 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
|
@ -1,80 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">IIAB-oA Controller</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="save">Guardar</string>
|
||||
<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="watchdog_enable">Activar Watchdog Maestro</string>
|
||||
<string name="watchdog_disable">Desactivar Watchdog Maestro</string>
|
||||
<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>
|
||||
|
||||
<!-- New strings for translatability -->
|
||||
<string name="browse_content">🚀 Explorar Contenido</string>
|
||||
<string name="watchdog_description">Protege Termux del modo Doze y mantiene el Wi-Fi activo.</string>
|
||||
<string name="reset_log">Reiniciar Log</string>
|
||||
<string name="copy_all">Copiar Todo</string>
|
||||
<string name="system_ready">Sistema listo...\n</string>
|
||||
<string name="configuration_label">Configuración</string>
|
||||
<string name="advanced_settings_label">Ajustes Avanzados</string>
|
||||
<string name="connection_log_label">Log de Conexión</string>
|
||||
<string name="app_started">Aplicación Iniciada</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>
|
||||
<string name="recovery_pulse_received">Pulso de recuperación recibido del sistema. Forzando VPN...</string>
|
||||
<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="cancel">Cancelar</string>
|
||||
<string name="saved_toast">Guardado</string>
|
||||
<string name="settings_saved">Ajustes Guardados</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="watchdog_stopped">Watchdog Detenido</string>
|
||||
<string name="watchdog_started">Watchdog Iniciado</string>
|
||||
<string name="vpn_stopping">Deteniendo VPN...</string>
|
||||
<string name="vpn_starting">Iniciando VPN...</string>
|
||||
<string name="log_cleared_toast">Log borrado</string>
|
||||
<string name="failed_reset_log">Error al reiniciar log: %s</string>
|
||||
<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>
|
||||
<string name="user_initiated_conn">Conexión iniciada por el usuario</string>
|
||||
<string name="vpn_permission_granted">Permiso de VPN concedido. Conectando...</string>
|
||||
|
||||
<!-- IIABWatchdog strings -->
|
||||
<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: ERROR (%s)</string>
|
||||
<string name="session_started">SESIÓN DE LATIDO INICIADA</string>
|
||||
<string name="session_stopped">SESIÓN DE LATIDO DETENIDA</string>
|
||||
|
||||
<!-- TermuxCallbackReceiver strings -->
|
||||
<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="log_size_format">Tamaño: %1$s / 10MB</string>
|
||||
</resources>
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.IIABController" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="colorPrimary">#000000</item>
|
||||
<item name="colorPrimaryDark">#000000</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
|
||||
<item name="android:windowBackground">@color/background_dark</item>
|
||||
<item name="android:textColorPrimary">@color/white</item>
|
||||
|
||||
<item name="sectionBackground">@color/section_body_bg_dark</item>
|
||||
<item name="sectionHeaderBackground">@color/black</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">SocksTun</string>
|
||||
<string name="socks_addr">Адрес Socks:</string>
|
||||
<string name="socks_udp_addr">UDP-aдрес 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="save">Сохранить</string>
|
||||
<string name="control_enable">Включить</string>
|
||||
<string name="control_disable">Отключить</string>
|
||||
</resources>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<attr name="sectionBackground" format="color" />
|
||||
<attr name="sectionHeaderBackground" format="color" />
|
||||
</resources>
|
||||
|
|
@ -1,21 +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="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 -->
|
||||
|
||||
<!-- Secciones -->
|
||||
<color name="section_header_bg">#333333</color>
|
||||
<color name="section_body_bg_light">#F5F5F5</color>
|
||||
<color name="section_body_bg_dark">#1A1A1A</color>
|
||||
</resources>
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">IIAB-oA Controller</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="save">Save</string>
|
||||
<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="watchdog_enable">Enable Master Watchdog</string>
|
||||
<string name="watchdog_disable">Disable Master Watchdog</string>
|
||||
<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>
|
||||
|
||||
<!-- New strings for translatability -->
|
||||
<string name="browse_content">🚀 Explore Content</string>
|
||||
<string name="watchdog_description">Protects Termux from Doze mode and keeps Wi-Fi active.</string>
|
||||
<string name="reset_log">Reset Log</string>
|
||||
<string name="copy_all">Copy All</string>
|
||||
<string name="system_ready">System ready...\n</string>
|
||||
<string name="configuration_label">Configuration</string>
|
||||
<string name="advanced_settings_label">Advanced Settings</string>
|
||||
<string name="connection_log_label">Connection Log</string>
|
||||
<string name="app_started">Application Started</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>
|
||||
<string name="recovery_pulse_received">Recovery Pulse Received from System. Enforcing VPN...</string>
|
||||
<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="cancel">Cancel</string>
|
||||
<string name="saved_toast">Saved</string>
|
||||
<string name="settings_saved">Settings Saved</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="watchdog_stopped">Watchdog Stopped</string>
|
||||
<string name="watchdog_started">Watchdog Started</string>
|
||||
<string name="vpn_stopping">VPN Stopping...</string>
|
||||
<string name="vpn_starting">VPN Starting...</string>
|
||||
<string name="log_cleared_toast">Log cleared</string>
|
||||
<string name="failed_reset_log">Failed to reset log: %s</string>
|
||||
<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>
|
||||
<string name="user_initiated_conn">User initiated connection</string>
|
||||
<string name="vpn_permission_granted">VPN Permission Granted. Connecting...</string>
|
||||
|
||||
<!-- IIABWatchdog strings -->
|
||||
<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>
|
||||
|
||||
<!-- TermuxCallbackReceiver strings -->
|
||||
<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="log_size_format">Size: %1$s / 10MB</string>
|
||||
|
||||
<!-- Brand specific battery warnings -->
|
||||
<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>
|
||||
<string name="fix_action">FIX</string>
|
||||
</resources>
|
||||
|
|
@ -1,14 +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>
|
||||
</resources>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* The buildscript block is where you configure the repositories and
|
||||
* dependencies for Gradle itself--meaning, you should not include dependencies
|
||||
* for your modules here. For example, this block includes the Android plugin for
|
||||
* Gradle as a dependency because it provides the additional instructions Gradle
|
||||
* needs to build Android app modules.
|
||||
*/
|
||||
|
||||
buildscript {
|
||||
|
||||
/**
|
||||
* The repositories block configures the repositories Gradle uses to
|
||||
* search or download the dependencies. Gradle pre-configures support for remote
|
||||
* repositories such as JCenter, Maven Central, and Ivy. You can also use local
|
||||
* repositories or define your own remote repositories. The code below defines
|
||||
* JCenter as the repository Gradle should use to look for its dependencies.
|
||||
*/
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
/**
|
||||
* The dependencies block configures the dependencies Gradle needs to use
|
||||
* to build your project. The following line adds Android plugin for Gradle
|
||||
* version 2.3.1 as a classpath dependency.
|
||||
*/
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.4.1'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The allprojects block is where you configure the repositories and
|
||||
* dependencies used by all modules in your project, such as third-party plugins
|
||||
* or libraries. Dependencies that are not required by all the modules in the
|
||||
* project should be configured in module-level build.gradle files. For new
|
||||
* projects, Android Studio configures JCenter as the default repository, but it
|
||||
* does not configure any dependencies.
|
||||
*/
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
A simple and lightweight VPN over socks5 proxy for Android. It is based on a high-performance and low-overhead tun2socks.
|
||||
|
||||
<h2>Features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Redirect TCP connections.</li>
|
||||
<li>Redirect UDP packets. (Fullcone NAT, UDP in UDP/TCP)</li>
|
||||
<li>Simple username/password authentication.</li>
|
||||
<li>Specifying DNS addresses.</li>
|
||||
<li>IPv4/IPv6 dual stack.</li>
|
||||
<li>Global/per-App modes.</li>
|
||||
</ul>
|
||||
|
Before Width: | Height: | Size: 443 KiB |
|
|
@ -1 +0,0 @@
|
|||
A simple and lightweight VPN over socks5 proxy (tun2socks)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
Простое и лёгкое VPN-решение поверх Socks5 прокси для Android. Основано на высокопроизводительном и малозатратном tun2socks.
|
||||
|
||||
<h2>Особенности</h2>
|
||||
|
||||
<ul>
|
||||
<li>Перенаправление TCP-соединений.</li>
|
||||
<li>Перенаправление UDP-пакетов. (Fullcone NAT, UDP внутри UDP/TCP)</li>
|
||||
<li>Простая аутентификация по имени пользователя и паролю.</li>
|
||||
<li>Указание адресов DNS.</li>
|
||||
<li>Поддержка двойного стека IPv4/IPv6.</li>
|
||||
<li>Глобальный режим и режим для отдельных приложений.</li>
|
||||
</ul>
|
||||
|
|
@ -1 +0,0 @@
|
|||
Простое и лёгкое VPN поверх Socks5 прокси (tun2socks)
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
## This file must *NOT* be checked into Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
#
|
||||
# Location of the SDK. This is only used by Gradle.
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
#Tue Mar 03 10:51:56 CST 2026
|
||||
sdk.dir=/home/ark/Android/Sdk
|
||||
|
|
@ -1 +0,0 @@
|
|||
include ':app'
|
||||