testsAndMisc/python_pkg/phone_lease/serial.py
Krzysztof kuhy Rudnicki c0c51036fc phone_lease: per-app leases, a self-cleaning whole-phone lease, one serial
Several sessions can now share one phone: a lease covers one package (the
real screen stays the default scope, so existing callers keep their
meaning), and only phone-wide actions take `--whole-phone`, which is capped
at 300 s and runs its `--cleanup` commands exactly once on release or, via a
detached reaper, on expiry. The serial is always resolved, never the
"default" placeholder that let android_ui and phone_deploy lease different
keys for the same phone (2026-09-25). Adds status --json, holds and whoami
for phone_vd.sh and the raw-adb hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ajzz98xksbfak8CaunZWC
2026-09-25 15:05:57 +02:00

63 lines
1.7 KiB
Python

"""Which phone a call is about, resolved the same way by every caller.
Before this existed android_ui leased the literal key ``default`` while
phone_deploy leased the real serial, so the two never saw each other and
drove the same phone at once (2026-09-25).
"""
from __future__ import annotations
import os
import shutil
import subprocess
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Callable, Mapping
_ENV_KEYS = ("ANDROID_SERIAL", "ADB_SERIAL")
class NoPhoneError(RuntimeError):
"""Raised when no serial is given and adb cannot name exactly one phone."""
def _adb_serial() -> str:
adb = shutil.which("adb")
if adb is None:
return ""
done = subprocess.run(
[adb, "get-serialno"],
check=False,
capture_output=True,
text=True,
timeout=10,
)
return done.stdout.strip() if done.returncode == 0 else ""
def resolve_serial(
explicit: str | None = None,
*,
environ: Mapping[str, str] | None = None,
ask_adb: Callable[[], str] | None = None,
) -> str:
"""``explicit``, else $ANDROID_SERIAL / $ADB_SERIAL, else ``adb get-serialno``."""
if explicit:
return explicit
env = os.environ if environ is None else environ
for key in _ENV_KEYS:
if env.get(key):
return env[key]
try:
found = (ask_adb or _adb_serial)()
except OSError, subprocess.TimeoutExpired:
found = ""
if not found or found == "unknown":
msg = (
"no phone serial: pass one, set ANDROID_SERIAL, or connect exactly "
"one device (adb get-serialno found none or several)"
)
raise NoPhoneError(msg)
return found