Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions src/claw_anything/task/mobile_gui/init_gui_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,24 @@ def inject_fossify_notes(step: dict, task_dir: Path, device: str | None) -> bool
MATTERMOST_PACKAGE = "com.mattermost.rnbeta"
MATTERMOST_STATE_PATH = f"/sdcard/Android/data/{MATTERMOST_PACKAGE}/files/state.json"

SHADOW_STATE_ROOT = "/data/local/tmp/claw_gui_state"


def _shadow_state_fallback_path(package: str) -> str:
return f"{SHADOW_STATE_ROOT}/{package}/state.json"


def _push_json_state(state: dict, remote_path: str, device: str | None) -> bool:
remote_dir = remote_path.rsplit("/", 1)[0]
adb_shell(f"mkdir -p {remote_dir}", device=device)
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8") as tmp:
json.dump(state, tmp, ensure_ascii=False, indent=2)
tmp_path = tmp.name
try:
return adb_push(tmp_path, remote_path, device=device)
finally:
os.unlink(tmp_path)


def _inject_shadow_app_state(
step: dict,
Expand Down Expand Up @@ -2163,35 +2181,35 @@ def _inject_shadow_app_state(
print(f" [FAIL] {app_label} fixture must be a JSON list", file=sys.stderr)
return False

state = {state_key: items}
fallback_path = _shadow_state_fallback_path(package)
adb_shell(f"rm -f {fallback_path}", device=device)
if not _push_json_state(state, fallback_path, device=device):
return False

installed, package_msg = _package_installed(package, device=device)
if not installed:
print(
f" [FAIL] package not installed: {package}"
f" [WARN] package not installed: {package}; "
f"wrote {app_label} shadow state to {fallback_path}"
+ (f" ({package_msg})" if package_msg else ""),
file=sys.stderr,
)
return False
return True

adb_shell(f"am force-stop {package}", device=device)
adb_shell(f"rm -f {state_path}", device=device)
adb_shell(f"mkdir -p $(dirname {state_path})", device=device)

state = {state_key: items}
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False, encoding="utf-8") as tmp:
json.dump(state, tmp, ensure_ascii=False, indent=2)
tmp_path = tmp.name
try:
ok = adb_push(tmp_path, state_path, device=device)
finally:
os.unlink(tmp_path)
if not ok:
if not _push_json_state(state, state_path, device=device):
return False

launch_ok, launch_msg = _start_package_launcher(package, device=device)
if not launch_ok:
print(f" [FAIL] failed to relaunch {app_label}: {launch_msg}", file=sys.stderr)
return False
print(f" [OK] {app_label} restarted ({len(items)} item(s) injected)")
print(
f" [OK] {app_label} restarted ({len(items)} item(s) injected; "
f"fallback state also at {fallback_path})"
)
return True


Expand Down
56 changes: 56 additions & 0 deletions tests/test_mobile_gui_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from __future__ import annotations

import importlib
import json

import pytest

gui_init = importlib.import_module("claw_anything.task.mobile_gui.init_gui_task")


def test_shadow_app_state_falls_back_when_package_is_missing(tmp_path, monkeypatch) -> None:
fixture = tmp_path / "products.json"
fixture.write_text(
json.dumps([{"product_id": "PROD-1", "name": "Gift"}]),
encoding="utf-8",
)

shells: list[str] = []
pushed: list[tuple[str, dict]] = []

def fake_adb_shell(cmd: str, device=None, root_required: bool = False):
shells.append(cmd)
return True, ""

def fake_adb_push(local: str, remote: str, device=None, root_required: bool = False) -> bool:
pushed.append((remote, json.loads(open(local, encoding="utf-8").read())))
return True

monkeypatch.setattr(gui_init, "adb_shell", fake_adb_shell)
monkeypatch.setattr(gui_init, "adb_push", fake_adb_push)
monkeypatch.setattr(gui_init, "_package_installed", lambda package, device=None: (False, "missing"))
monkeypatch.setattr(
gui_init,
"_start_package_launcher",
lambda *args, **kwargs: pytest.fail("missing shadow app package should not be launched"),
)

ok = gui_init._inject_shadow_app_state(
{"fixture": "products.json"},
tmp_path,
None,
package="com.testmall.app",
state_path="/sdcard/Android/data/com.testmall.app/files/state.json",
state_key="products",
default_fixture="fixtures/gui/testmall_gui/products.json",
app_label="TestMall",
)

assert ok
assert pushed == [
(
"/data/local/tmp/claw_gui_state/com.testmall.app/state.json",
{"products": [{"product_id": "PROD-1", "name": "Gift"}]},
)
]
assert "rm -f /data/local/tmp/claw_gui_state/com.testmall.app/state.json" in shells