#!/usr/bin/env python3 """Install the Chromium-hosted PWA shortcuts used by the hardware matrix.""" import os import re import subprocess import sys import time import xml.etree.ElementTree as ET sys.path.insert(0, os.path.dirname(__file__)) import cdp_adb ENV = dict( os.environ, ADB_SERVER_SOCKET=os.environ.get("ADB_SERVER_SOCKET", "tcp:127.0.0.1:15037"), ) PACKAGE = os.environ.get("CHROMIUM_PACKAGE", "org.chromium.chrome") ACTIVITY = os.environ.get( "CHROMIUM_ACTIVITY", "org.chromium.chrome/org.chromium.chrome.browser.ChromeTabbedActivity", ) BASE_URL = os.environ.get( "BASE_URL", "https://static.januschka.com/i-407420295" ) def adb(*args, capture=False): result = subprocess.run( ["adb", *args], env=ENV, check=True, capture_output=capture, text=capture ) return result.stdout if capture else "" def dump(): adb("shell", "uiautomator", "dump", "/sdcard/window.xml") return ET.fromstring(adb("exec-out", "cat", "/sdcard/window.xml", capture=True)) def center(bounds): x1, y1, x2, y2 = [int(value) for value in re.findall(r"\d+", bounds)] return (x1 + x2) // 2, (y1 + y2) // 2 def find(root, *, resource_suffix=None, texts=()): for node in root.iter("node"): attributes = node.attrib if resource_suffix and attributes.get("resource-id", "").endswith(resource_suffix): return node text = attributes.get("text", "") description = attributes.get("content-desc", "") if any(value in (text, description) for value in texts): return node return None def tap(node, name): if node is None: raise RuntimeError(f"missing {name}") x, y = center(node.attrib["bounds"]) print(f"tap {name}: {x},{y}") adb("shell", "input", "tap", str(x), str(y)) time.sleep(2) def shortcut_exists(label): output = adb("shell", "dumpsys", "shortcut", capture=True) return f"shortLabel={label}," in output def select_devtools_socket(): pid = adb("shell", "pidof", PACKAGE, capture=True).strip().split()[0] sockets = adb("shell", "cat", "/proc/net/unix", capture=True) suffixed = f"chrome_devtools_remote_{pid}" cdp_adb.SOCKET_NAME = suffixed if f"@{suffixed}" in sockets else "chrome_devtools_remote" def navigate(url): pages = [target for target in cdp_adb.targets() if target.get("type") == "page"] for page in reversed(pages): cdp = cdp_adb.CDP(page) try: if cdp.evaluate("document.visibilityState") == "visible": cdp.command("Page.navigate", {"url": url}) return except Exception as error: print(f"Skipping unresponsive tab {page.get('url')}: {error}") finally: cdp.close() raise RuntimeError("No visible Chromium browser tab found") def install(url, label): if shortcut_exists(label): print(f"Already installed: {label}") return print(f"Installing Chromium shortcut: {label}") adb("shell", "am", "force-stop", PACKAGE) adb("shell", "am", "start", "-W", "-n", ACTIVITY, "-a", "android.intent.action.MAIN") time.sleep(4) select_devtools_socket() navigate(url) time.sleep(5) root = dump() tap(find(root, resource_suffix=":id/menu_button"), "menu") width = int(re.findall(r"\d+x\d+", adb("shell", "wm", "size", capture=True))[-1].split("x")[0]) for _ in range(8): root = dump() item = find(root, resource_suffix=":id/universal_install") if item is not None: tap(item, "Install and create shortcut") break adb("shell", "input", "swipe", str(width * 4 // 5), "850", str(width * 4 // 5), "300", "450") time.sleep(1) else: raise RuntimeError("Install and create shortcut menu item not found") root = dump() tap(find(root, resource_suffix=":id/option_install"), "Install option") root = dump() tap(find(root, resource_suffix=":id/positive_button", texts=("Install",)), "confirm install") time.sleep(5) root = dump() add = find(root, texts=("Add to home screen", "Add to Home screen")) if add is not None: tap(add, "Add to home screen") for _ in range(15): if shortcut_exists(label): print(f"Installed Chromium shortcut: {label}") return time.sleep(2) raise RuntimeError(f"Shortcut did not appear after installation: {label}") apps = [ (f"{BASE_URL}/demo-standalone.html", "E2E Standalone"), (f"{BASE_URL}/demo-fullscreen.html", "E2E Fullscreen"), (f"{BASE_URL}/demo-backswipe.html", "E2E BackSwipe"), (f"{BASE_URL}/demo-nav-contrast.html", "Nav Contrast"), (f"{BASE_URL}/manualpassword.html", "Password Test"), (f"{BASE_URL}/automation/pages/viewport-fit/index.html", "Fit Switch"), ] original_accelerometer_rotation = adb( "shell", "settings", "get", "system", "accelerometer_rotation", capture=True ).strip() original_user_rotation = adb( "shell", "settings", "get", "system", "user_rotation", capture=True ).strip() try: adb("shell", "settings", "put", "system", "accelerometer_rotation", "0") adb("shell", "settings", "put", "system", "user_rotation", "0") time.sleep(4) for app_url, app_label in apps: install(app_url, app_label) finally: adb( "shell", "settings", "put", "system", "accelerometer_rotation", original_accelerometer_rotation, ) adb("shell", "settings", "put", "system", "user_rotation", original_user_rotation)