70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
"""Bounded native metadata shared by JSON and XML hierarchy observations."""
|
|
|
|
import json
|
|
import math
|
|
from typing import Any
|
|
|
|
|
|
def _finite_number(value: Any) -> bool:
|
|
# Android ranges are finite floats. Reject malformed oversized JSON integers
|
|
# before conversion, which otherwise raises OverflowError in math.isfinite.
|
|
return type(value) in (int, float) and -1e308 <= value <= 1e308 and math.isfinite(value)
|
|
|
|
|
|
def native_control_state(node: dict[str, Any]) -> dict[str, Any]:
|
|
state: dict[str, Any] = {}
|
|
for key in ("range", "actions"):
|
|
value = node.get(key)
|
|
if isinstance(value, str) and len(value) <= 8192:
|
|
try:
|
|
value = json.loads(value)
|
|
except ValueError:
|
|
value = None
|
|
if key == "actions" and isinstance(value, list):
|
|
state[key] = [item for item in value[:32] if isinstance(item, str) and len(item) <= 64]
|
|
elif key == "range" and isinstance(value, dict):
|
|
numbers = [value.get(k) for k in ("min", "max", "current")]
|
|
if all(_finite_number(v) for v in numbers):
|
|
if numbers[0] <= numbers[2] <= numbers[1]:
|
|
state[key] = {k: value[k] for k in ("min", "max", "current")}
|
|
state[key]["type"] = value.get("type")
|
|
for key in (
|
|
"visible",
|
|
"enabled",
|
|
"scrollable",
|
|
"editable",
|
|
"password",
|
|
"checked",
|
|
"checkable",
|
|
"selected",
|
|
"focused",
|
|
):
|
|
value = node.get(key)
|
|
if isinstance(value, bool) or value in ("true", "false"):
|
|
state[key] = value is True or value == "true"
|
|
for key in ("selection_start", "selection_end"):
|
|
value = node.get(key, node.get(key.replace("_", "-")))
|
|
if isinstance(value, str) and len(value) < 12:
|
|
digits = value[1:] if value.startswith("-") else value
|
|
if digits and digits.isascii() and digits.isdecimal():
|
|
value = int(value)
|
|
if isinstance(value, int) and not isinstance(value, bool) and -1 <= value <= 2**31 - 1:
|
|
state[key] = value
|
|
return state
|
|
|
|
|
|
def informative_native_control(state: dict[str, Any]) -> bool:
|
|
return bool(
|
|
state.get("range")
|
|
or state.get("scrollable")
|
|
or set(state.get("actions", []))
|
|
& {
|
|
"click",
|
|
"long_click",
|
|
"set_progress",
|
|
"set_text",
|
|
"set_selection",
|
|
"ime_enter",
|
|
}
|
|
)
|