"""Device control utilities for iOS automation via WebDriverAgent.""" import subprocess import time from typing import Optional from phone_agent.config.apps_ios import APP_PACKAGES_IOS as APP_PACKAGES SCALE_FACTOR = 3 # 3 for most modern iPhone def _get_wda_session_url(wda_url: str, session_id: str | None, endpoint: str) -> str: """ Get the correct WDA URL for a session endpoint. Args: wda_url: Base WDA URL. session_id: Optional session ID. endpoint: The endpoint path. Returns: Full URL for the endpoint. """ base = wda_url.rstrip("/") if session_id: return f"{base}/session/{session_id}/{endpoint}" else: # Try to use WDA endpoints without session when possible return f"{base}/{endpoint}" def get_current_app( wda_url: str = "http://localhost:8100", session_id: str | None = None ) -> str: """ Get the currently active app bundle ID and name. Args: wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. Returns: The app name if recognized, otherwise "System Home". """ try: import requests # Get active app info from WDA using activeAppInfo endpoint response = requests.get( f"{wda_url.rstrip('/')}/wda/activeAppInfo", timeout=5, verify=False ) if response.status_code == 200: data = response.json() # Extract bundle ID from response # Response format: {"value": {"bundleId": "com.apple.AppStore", "name": "", "pid": 825, "processArguments": {...}}, "sessionId": "..."} value = data.get("value", {}) bundle_id = value.get("bundleId", "") if bundle_id: # Try to find app name from bundle ID for app_name, package in APP_PACKAGES.items(): if package == bundle_id: return app_name return "System Home" except ImportError: print("Error: requests library required. Install: pip install requests") except Exception as e: print(f"Error getting current app: {e}") return "System Home" def tap( x: int, y: int, wda_url: str = "http://localhost:8100", session_id: str | None = None, delay: float = 1.0, ) -> None: """ Tap at the specified coordinates using WebDriver W3C Actions API. Args: x: X coordinate. y: Y coordinate. wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. delay: Delay in seconds after tap. """ try: import requests url = _get_wda_session_url(wda_url, session_id, "actions") # W3C WebDriver Actions API for tap/click actions = { "actions": [ { "type": "pointer", "id": "finger1", "parameters": {"pointerType": "touch"}, "actions": [ {"type": "pointerMove", "duration": 0, "x": x / SCALE_FACTOR, "y": y / SCALE_FACTOR}, {"type": "pointerDown", "button": 0}, {"type": "pause", "duration": 0.1}, {"type": "pointerUp", "button": 0}, ], } ] } requests.post(url, json=actions, timeout=15, verify=False) time.sleep(delay) except ImportError: print("Error: requests library required. Install: pip install requests") except Exception as e: print(f"Error tapping: {e}") def double_tap( x: int, y: int, wda_url: str = "http://localhost:8100", session_id: str | None = None, delay: float = 1.0, ) -> None: """ Double tap at the specified coordinates using WebDriver W3C Actions API. Args: x: X coordinate. y: Y coordinate. wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. delay: Delay in seconds after double tap. """ try: import requests url = _get_wda_session_url(wda_url, session_id, "actions") # W3C WebDriver Actions API for double tap actions = { "actions": [ { "type": "pointer", "id": "finger1", "parameters": {"pointerType": "touch"}, "actions": [ {"type": "pointerMove", "duration": 0, "x": x / SCALE_FACTOR, "y": y / SCALE_FACTOR}, {"type": "pointerDown", "button": 0}, {"type": "pause", "duration": 100}, {"type": "pointerUp", "button": 0}, {"type": "pause", "duration": 100}, {"type": "pointerDown", "button": 0}, {"type": "pause", "duration": 100}, {"type": "pointerUp", "button": 0}, ], } ] } requests.post(url, json=actions, timeout=10, verify=False) time.sleep(delay) except ImportError: print("Error: requests library required. Install: pip install requests") except Exception as e: print(f"Error double tapping: {e}") def long_press( x: int, y: int, duration: float = 3.0, wda_url: str = "http://localhost:8100", session_id: str | None = None, delay: float = 1.0, ) -> None: """ Long press at the specified coordinates using WebDriver W3C Actions API. Args: x: X coordinate. y: Y coordinate. duration: Duration of press in seconds. wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. delay: Delay in seconds after long press. """ try: import requests url = _get_wda_session_url(wda_url, session_id, "actions") # W3C WebDriver Actions API for long press # Convert duration to milliseconds duration_ms = int(duration * 1000) actions = { "actions": [ { "type": "pointer", "id": "finger1", "parameters": {"pointerType": "touch"}, "actions": [ {"type": "pointerMove", "duration": 0, "x": x / SCALE_FACTOR, "y": y / SCALE_FACTOR}, {"type": "pointerDown", "button": 0}, {"type": "pause", "duration": duration_ms}, {"type": "pointerUp", "button": 0}, ], } ] } requests.post(url, json=actions, timeout=int(duration + 10), verify=False) time.sleep(delay) except ImportError: print("Error: requests library required. Install: pip install requests") except Exception as e: print(f"Error long pressing: {e}") def swipe( start_x: int, start_y: int, end_x: int, end_y: int, duration: float | None = None, wda_url: str = "http://localhost:8100", session_id: str | None = None, delay: float = 1.0, ) -> None: """ Swipe from start to end coordinates using WDA dragfromtoforduration endpoint. Args: start_x: Starting X coordinate. start_y: Starting Y coordinate. end_x: Ending X coordinate. end_y: Ending Y coordinate. duration: Duration of swipe in seconds (auto-calculated if None). wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. delay: Delay in seconds after swipe. """ try: import requests if duration is None: # Calculate duration based on distance dist_sq = (start_x - end_x) ** 2 + (start_y - end_y) ** 2 duration = dist_sq / 1000000 # Convert to seconds duration = max(0.3, min(duration, 2.0)) # Clamp between 0.3-2 seconds url = _get_wda_session_url(wda_url, session_id, "wda/dragfromtoforduration") # WDA dragfromtoforduration API payload payload = { "fromX": start_x / SCALE_FACTOR, "fromY": start_y / SCALE_FACTOR, "toX": end_x / SCALE_FACTOR, "toY": end_y / SCALE_FACTOR, "duration": duration, } requests.post(url, json=payload, timeout=int(duration + 10), verify=False) time.sleep(delay) except ImportError: print("Error: requests library required. Install: pip install requests") except Exception as e: print(f"Error swiping: {e}") def back( wda_url: str = "http://localhost:8100", session_id: str | None = None, delay: float = 1.0, ) -> None: """ Navigate back (swipe from left edge). Args: wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. delay: Delay in seconds after navigation. Note: iOS doesn't have a universal back button. This simulates a back gesture by swiping from the left edge of the screen. """ try: import requests url = _get_wda_session_url(wda_url, session_id, "wda/dragfromtoforduration") # Swipe from left edge to simulate back gesture payload = { "fromX": 0, "fromY": 640, "toX": 400, "toY": 640, "duration": 0.3, } requests.post(url, json=payload, timeout=10, verify=False) time.sleep(delay) except ImportError: print("Error: requests library required. Install: pip install requests") except Exception as e: print(f"Error performing back gesture: {e}") def home( wda_url: str = "http://localhost:8100", session_id: str | None = None, delay: float = 1.0, ) -> None: """ Press the home button. Args: wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. delay: Delay in seconds after pressing home. """ try: import requests url = f"{wda_url.rstrip('/')}/wda/homescreen" requests.post(url, timeout=10, verify=False) time.sleep(delay) except ImportError: print("Error: requests library required. Install: pip install requests") except Exception as e: print(f"Error pressing home: {e}") def launch_app( app_name: str, wda_url: str = "http://localhost:8100", session_id: str | None = None, delay: float = 1.0, ) -> bool: """ Launch an app by name. Args: app_name: The app name (must be in APP_PACKAGES). wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. delay: Delay in seconds after launching. Returns: True if app was launched, False if app not found. """ if app_name not in APP_PACKAGES: return False try: import requests bundle_id = APP_PACKAGES[app_name] url = _get_wda_session_url(wda_url, session_id, "wda/apps/launch") response = requests.post( url, json={"bundleId": bundle_id}, timeout=10, verify=False ) time.sleep(delay) return response.status_code in (200, 201) except ImportError: print("Error: requests library required. Install: pip install requests") return False except Exception as e: print(f"Error launching app: {e}") return False def get_screen_size( wda_url: str = "http://localhost:8100", session_id: str | None = None ) -> tuple[int, int]: """ Get the screen dimensions. Args: wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. Returns: Tuple of (width, height). Returns (375, 812) as default if unable to fetch. """ try: import requests url = _get_wda_session_url(wda_url, session_id, "window/size") response = requests.get(url, timeout=5, verify=False) if response.status_code == 200: data = response.json() value = data.get("value", {}) width = value.get("width", 375) height = value.get("height", 812) return width, height except ImportError: print("Error: requests library required. Install: pip install requests") except Exception as e: print(f"Error getting screen size: {e}") # Default iPhone screen size (iPhone X and later) return 375, 812 def press_button( button_name: str, wda_url: str = "http://localhost:8100", session_id: str | None = None, delay: float = 1.0, ) -> None: """ Press a physical button. Args: button_name: Button name (e.g., "home", "volumeUp", "volumeDown"). wda_url: WebDriverAgent URL. session_id: Optional WDA session ID. delay: Delay in seconds after pressing. """ try: import requests url = f"{wda_url.rstrip('/')}/wda/pressButton" requests.post(url, json={"name": button_name}, timeout=10, verify=False) time.sleep(delay) except ImportError: print("Error: requests library required. Install: pip install requests") except Exception as e: print(f"Error pressing button: {e}")