#!/usr/bin/env python3 import os import re import subprocess import sys import time import xml.etree.ElementTree as ET sys.path.insert(0, os.path.dirname(__file__)) from cdp_adb import CDP, targets ENV = dict(os.environ, ADB_SERVER_SOCKET=os.environ.get("ADB_SERVER_SOCKET", "tcp:127.0.0.1:15037")) PACKAGE = "com.android.chrome" ACTIVITY = "com.android.chrome/com.google.android.apps.chrome.Main" 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(v) for v in re.findall(r"\d+", bounds)] return (x1 + x2) // 2, (y1 + y2) // 2 def tap(root, predicate, name): for node in root.iter("node"): if predicate(node.attrib): x, y = center(node.attrib["bounds"]) print(f"tap {name}: {x},{y}") adb("shell", "input", "tap", str(x), str(y)) time.sleep(2) return raise RuntimeError(f"missing {name}") def webapks(): output = adb("shell", "pm", "list", "packages", "org.chromium.webapk", capture=True) return {line.strip().removeprefix("package:") for line in output.splitlines() if line.strip()} def install(url, label): print(f"=== stock install {label}") before = webapks() adb("shell", "am", "force-stop", PACKAGE) adb("shell", "am", "start", "-W", "-n", ACTIVITY, "-a", "android.intent.action.MAIN") time.sleep(3) root = dump() if any(n.attrib.get("resource-id") == "android:id/button2" for n in root.iter("node")): tap(root, lambda a: a.get("resource-id") == "android:id/button2", "cancel default-browser prompt") root = dump() if any("Startbildschirm" in n.attrib.get("text", "") for n in root.iter("node")): adb("shell", "input", "tap", "20", "1000") time.sleep(1) pages = [t for t in targets() if t.get("type") == "page"] browser_page = None for page in pages: candidate = CDP(page) try: is_browser = candidate.evaluate("matchMedia('(display-mode: browser)').matches") except Exception: is_browser = False candidate.close() if is_browser: browser_page = page break if browser_page is None: raise RuntimeError("could not find stock Chrome browser tab") cdp = CDP(browser_page) cdp.command("Page.navigate", {"url": url}) cdp.close() time.sleep(5) root = dump() tap(root, lambda a: a.get("resource-id") == "com.android.chrome:id/menu_button", "menu") adb("shell", "input", "swipe", "500", "1800", "500", "900", "800") time.sleep(2) root = dump() tap(root, lambda a: "Startbildschirm hinzufügen" in a.get("text", "") or a.get("text", "").startswith("Installieren und"), "add/install menu item") root = dump() tap(root, lambda a: a.get("resource-id") == "com.android.chrome:id/option_text_install", "install option") root = dump() tap(root, lambda a: a.get("resource-id") == "com.android.chrome:id/positive_button", "confirm") for _ in range(45): new = webapks() - before if new: package = sorted(new)[0] print(f"installed {label}: {package}") return package time.sleep(2) raise RuntimeError(f"timed out installing {label}") apps = [ ("https://static.januschka.com/i-407420295/automation/pages/standalone/index.html", "standalone"), ("https://static.januschka.com/i-407420295/automation/pages/fullscreen/index.html", "fullscreen"), ("https://static.januschka.com/i-407420295/automation/pages/backswipe/index.html", "backswipe"), ("https://static.januschka.com/i-407420295/automation/pages/nav-contrast/index.html", "nav-contrast"), ("https://static.januschka.com/i-407420295/automation/pages/password/index.html", "password"), ("https://static.januschka.com/i-407420295/automation/pages/viewport-fit/index.html", "viewport-fit"), ] results = {} for url, label in apps: results[label] = install(url, label) output_path = os.environ.get("STOCK_WEBAPK_MAP", "/tmp/stock-webapks.txt") with open(output_path, "w") as output: for label, package in results.items(): output.write(f"{label.replace('-', '_')}={package}\n") print(f"Wrote WebAPK map: {output_path}")