297 lines
8.4 KiB
Bash
Executable File
297 lines
8.4 KiB
Bash
Executable File
#!/system/bin/sh
|
|
|
|
set -u
|
|
|
|
STATE_DIR=${INTELLIRAT_STATE_DIR:-/data/local/tmp}
|
|
CONFIG_FILE=${INTELLIRAT_CONFIG:-$STATE_DIR/config.conf}
|
|
LOG_FILE=${INTELLIRAT_LOG:-$STATE_DIR/events.log}
|
|
CMD_BIN=${INTELLIRAT_CMD:-cmd}
|
|
DUMPSYS_BIN=${INTELLIRAT_DUMPSYS:-dumpsys}
|
|
|
|
DEFAULT_SLOT=0
|
|
DEFAULT_HIGH_MASK=00011101001110000111
|
|
DEFAULT_LOW_MASK=00011100001110000111
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
IntelliRAT v0.1 manual prototype
|
|
|
|
Usage:
|
|
intellirat status [slot]
|
|
intellirat capture [slot]
|
|
intellirat high [slot]
|
|
intellirat low [slot]
|
|
intellirat restore [slot]
|
|
intellirat --dry-run high|low|restore [slot]
|
|
|
|
The prototype never changes RAT automatically. Capture the current state before
|
|
the first transition. Restore returns the captured user network mask.
|
|
EOF
|
|
}
|
|
|
|
fail() {
|
|
printf 'IntelliRAT: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
load_config() {
|
|
TARGET_SLOT=$DEFAULT_SLOT
|
|
HIGH_MASK=$DEFAULT_HIGH_MASK
|
|
LOW_MASK=$DEFAULT_LOW_MASK
|
|
|
|
if [ -f "$CONFIG_FILE" ]; then
|
|
# The file is created root-only by service.sh; reject unsafe ownership
|
|
# only when the platform exposes stat in the available shell.
|
|
# shellcheck disable=SC1090
|
|
. "$CONFIG_FILE"
|
|
fi
|
|
|
|
case "$TARGET_SLOT" in
|
|
''|*[!0-9]*) fail "invalid TARGET_SLOT" ;;
|
|
esac
|
|
case "$HIGH_MASK:$LOW_MASK" in
|
|
*[!01:]*|:*) fail "masks must be non-empty binary strings" ;;
|
|
esac
|
|
}
|
|
|
|
ensure_state_dir() {
|
|
mkdir -p "$STATE_DIR" || fail "cannot create $STATE_DIR"
|
|
if [ "$STATE_DIR" = /data/local/tmp ]; then
|
|
return
|
|
fi
|
|
chown 0 2000 "$STATE_DIR" 2>/dev/null || true
|
|
chmod 0770 "$STATE_DIR" 2>/dev/null || true
|
|
}
|
|
|
|
log_event() {
|
|
ensure_state_dir
|
|
printf '%s %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*" >> "$LOG_FILE"
|
|
chmod 0600 "$LOG_FILE" 2>/dev/null || true
|
|
}
|
|
|
|
slot_or_default() {
|
|
if [ "${1:-}" = '' ]; then
|
|
printf '%s' "$TARGET_SLOT"
|
|
else
|
|
case "$1" in
|
|
''|*[!0-9]*) fail "invalid slot: $1" ;;
|
|
esac
|
|
printf '%s' "$1"
|
|
fi
|
|
}
|
|
|
|
state_file() {
|
|
printf '%s/intellirat-sub-%s.state' "$STATE_DIR" "$1"
|
|
}
|
|
|
|
read_user_names() {
|
|
slot=$1
|
|
result=$(
|
|
"$CMD_BIN" phone get-allowed-network-types-for-users -s "$slot" 2>&1
|
|
) || fail "cannot read network types for slot $slot: $result"
|
|
case "$result" in
|
|
*'No valid subscription found'*|*'No valid NETWORK_TYPES_BITMASK'*)
|
|
fail "no valid subscription for slot $slot: $result"
|
|
;;
|
|
esac
|
|
result=$(printf '%s' "$result" | tr -d '[:space:]\r\n')
|
|
[ -n "$result" ] || fail "empty network type response for slot $slot"
|
|
printf '%s' "$result"
|
|
}
|
|
|
|
network_names_to_decimal() {
|
|
names=$1
|
|
mask=0
|
|
old_ifs=$IFS
|
|
IFS='|'
|
|
# shellcheck disable=SC2086
|
|
for name in $names; do
|
|
case "$name" in
|
|
GPRS) mask=$((mask | 1)) ;;
|
|
EDGE) mask=$((mask | 2)) ;;
|
|
UMTS) mask=$((mask | 4)) ;;
|
|
CDMA) mask=$((mask | 8)) ;;
|
|
EVDO_0) mask=$((mask | 16)) ;;
|
|
EVDO_A) mask=$((mask | 32)) ;;
|
|
1xRTT) mask=$((mask | 64)) ;;
|
|
HSDPA) mask=$((mask | 128)) ;;
|
|
HSUPA) mask=$((mask | 256)) ;;
|
|
HSPA) mask=$((mask | 512)) ;;
|
|
HSPA+) mask=$((mask | 16384)) ;;
|
|
IDEN) mask=$((mask | 1024)) ;;
|
|
EVDO_B) mask=$((mask | 2048)) ;;
|
|
LTE) mask=$((mask | 4096)) ;;
|
|
EHRPD) mask=$((mask | 8192)) ;;
|
|
HSPAP) mask=$((mask | 16384)) ;;
|
|
GSM) mask=$((mask | 32768)) ;;
|
|
TD_SCDMA) mask=$((mask | 65536)) ;;
|
|
IWLAN) mask=$((mask | 131072)) ;;
|
|
LTE_CA) mask=$((mask | 4096)) ;;
|
|
NR) mask=$((mask | 524288)) ;;
|
|
UNKNOWN|NONE) ;;
|
|
*) IFS=$old_ifs; fail "unknown network type from telephony: $name" ;;
|
|
esac
|
|
done
|
|
IFS=$old_ifs
|
|
printf '%s' "$mask"
|
|
}
|
|
|
|
decimal_to_binary() {
|
|
mask=$1
|
|
output=
|
|
bit=19
|
|
while [ "$bit" -ge 0 ]; do
|
|
value=$((1 << bit))
|
|
if [ $((mask & value)) -ne 0 ]; then
|
|
output="${output}1"
|
|
else
|
|
output="${output}0"
|
|
fi
|
|
bit=$((bit - 1))
|
|
done
|
|
printf '%s' "$output"
|
|
}
|
|
|
|
read_user_binary() {
|
|
names=$(read_user_names "$1")
|
|
decimal=$(network_names_to_decimal "$names")
|
|
decimal_to_binary "$decimal"
|
|
}
|
|
|
|
call_in_progress() {
|
|
dump=$(
|
|
"$DUMPSYS_BIN" telecom 2>/dev/null || true
|
|
)
|
|
current_calls=$(printf '%s\n' "$dump" | awk '
|
|
/^ All calls:/ { in_calls=1; next }
|
|
/^ Active dialing, or connecting calls:/ { in_calls=1; next }
|
|
/^ Ringing calls:/ { in_calls=1; next }
|
|
/^ Holding calls:/ { in_calls=1; next }
|
|
/^[[:space:]]*[A-Za-z][A-Za-z _-]*:$/ { in_calls=0 }
|
|
in_calls { print }
|
|
')
|
|
printf '%s\n' "$current_calls" | grep -Eq \
|
|
'(^|[^A-Za-z])(ACTIVE|RINGING|DIALING|CONNECTING)([^A-Za-z]|$)|mCallState=(ACTIVE|RINGING|DIALING|CONNECTING)'
|
|
}
|
|
|
|
write_state() {
|
|
slot=$1
|
|
original_names=$2
|
|
original_binary=$3
|
|
file=$(state_file "$slot")
|
|
tmp="${file}.tmp.$$"
|
|
{
|
|
printf 'original_names=%s\n' "$original_names"
|
|
printf 'original_mask=%s\n' "$original_binary"
|
|
printf 'captured_at=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
|
} > "$tmp" || fail "cannot write $file"
|
|
chmod 0600 "$tmp"
|
|
mv "$tmp" "$file" || fail "cannot commit $file"
|
|
}
|
|
|
|
capture() {
|
|
slot=$1
|
|
ensure_state_dir
|
|
names=$(read_user_names "$slot")
|
|
decimal=$(network_names_to_decimal "$names")
|
|
binary=$(decimal_to_binary "$decimal")
|
|
file=$(state_file "$slot")
|
|
if [ -f "$file" ] && [ "${INTELLIRAT_FORCE_CAPTURE:-0}" != 1 ]; then
|
|
fail "state already captured for slot $slot; set INTELLIRAT_FORCE_CAPTURE=1 to replace it"
|
|
fi
|
|
write_state "$slot" "$names" "$binary"
|
|
log_event "capture slot=$slot mask=$binary names=$names"
|
|
printf 'captured slot=%s mask=%s names=%s\n' "$slot" "$binary" "$names"
|
|
}
|
|
|
|
get_original_mask() {
|
|
file=$(state_file "$1")
|
|
[ -f "$file" ] || {
|
|
printf '%s' "$HIGH_MASK"
|
|
return
|
|
}
|
|
value=$(grep '^original_mask=' "$file" | cut -d= -f2-)
|
|
case "$value" in
|
|
''|*[!01]*) fail "invalid saved state for slot $1" ;;
|
|
esac
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
apply_mask() {
|
|
slot=$1
|
|
target=$2
|
|
reason=$3
|
|
current=$(read_user_binary "$slot")
|
|
if [ "$current" = "$target" ]; then
|
|
log_event "noop slot=$slot state=$reason mask=$target"
|
|
printf 'already state=%s slot=%s mask=%s\n' "$reason" "$slot" "$target"
|
|
return 0
|
|
fi
|
|
|
|
if call_in_progress; then
|
|
fail "telephony call is active; transition refused"
|
|
fi
|
|
|
|
if [ "${INTELLIRAT_DRY_RUN:-0}" = 1 ]; then
|
|
printf 'dry-run cmd phone set-allowed-network-types-for-users -s %s %s\n' \
|
|
"$slot" "$target"
|
|
return 0
|
|
fi
|
|
|
|
result=$(
|
|
"$CMD_BIN" phone set-allowed-network-types-for-users -s "$slot" "$target" 2>&1
|
|
) || fail "network transition failed: $result"
|
|
case "$result" in
|
|
*completed*) ;;
|
|
*) fail "telephony did not confirm transition: $result" ;;
|
|
esac
|
|
|
|
actual=$(read_user_binary "$slot")
|
|
[ "$actual" = "$target" ] || fail "verification mismatch: requested=$target actual=$actual"
|
|
log_event "transition slot=$slot previous=$current state=$reason requested=$target actual=$actual"
|
|
printf 'transition slot=%s state=%s previous=%s actual=%s\n' \
|
|
"$slot" "$reason" "$current" "$actual"
|
|
}
|
|
|
|
status() {
|
|
slot=$1
|
|
names=$(read_user_names "$slot")
|
|
decimal=$(network_names_to_decimal "$names")
|
|
current=$(decimal_to_binary "$decimal")
|
|
state=UNKNOWN
|
|
[ "$current" = "$HIGH_MASK" ] && state=HIGH
|
|
[ "$current" = "$LOW_MASK" ] && state=LOW
|
|
saved=NO
|
|
[ -f "$(state_file "$slot")" ] && saved=YES
|
|
printf 'slot=%s state=%s mask=%s names=%s saved_original=%s\n' \
|
|
"$slot" "$state" "$current" "$names" "$saved"
|
|
}
|
|
|
|
main() {
|
|
dry_run=0
|
|
if [ "${1:-}" = '--dry-run' ]; then
|
|
dry_run=1
|
|
INTELLIRAT_DRY_RUN=1
|
|
export INTELLIRAT_DRY_RUN
|
|
shift
|
|
fi
|
|
load_config
|
|
ensure_state_dir
|
|
|
|
command=${1:-help}
|
|
shift || true
|
|
slot=$(slot_or_default "${1:-}")
|
|
|
|
case "$command" in
|
|
help|-h|--help) usage ;;
|
|
status) status "$slot" ;;
|
|
capture) capture "$slot" ;;
|
|
high) apply_mask "$slot" "$HIGH_MASK" HIGH ;;
|
|
low) apply_mask "$slot" "$LOW_MASK" LOW ;;
|
|
restore) apply_mask "$slot" "$(get_original_mask "$slot")" RESTORE ;;
|
|
*) usage >&2; exit 2 ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|