Ship Circle 1.16.2: overlay assist, brand, and smoke tooling
VIP loop (circle → extract → Translate/Copy/Explain/Share/Vikunja), lasso branding, ship docs, unit tests, and make smoke / launchd emu helpers.
This commit is contained in:
Executable
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke Crkl circle flows (integrations need tokens configured separately).
|
||||
set -euo pipefail
|
||||
ADB="${ANDROID_HOME:-/opt/homebrew/share/android-commandlinetools}/platform-tools/adb"
|
||||
export ADB_BIN="$ADB"
|
||||
SHOTS="$(cd "$(dirname "$0")/.." && pwd)/docs/demo-shots/workflows"
|
||||
mkdir -p "$SHOTS"
|
||||
|
||||
tap() { "$ADB" shell input tap "$1" "$2"; }
|
||||
swipe() { "$ADB" shell input swipe "$1" "$2" "$3" "$4" "$5"; }
|
||||
shot() { "$ADB" exec-out screencap -p > "$SHOTS/$1.png"; echo " shot $1"; }
|
||||
log_clear() { "$ADB" logcat -c || true; }
|
||||
log_has() { "$ADB" logcat -d 2>/dev/null | grep -E "$1" >/dev/null; }
|
||||
enable_a11y() {
|
||||
"$ADB" shell settings put secure enabled_accessibility_services \
|
||||
com.example.crkl/com.example.crkl.accessibility.CrklAccessibilityService
|
||||
"$ADB" shell settings put secure accessibility_enabled 1
|
||||
sleep 2
|
||||
}
|
||||
circle() {
|
||||
python3 - "$1" "$2" "$3" "$4" <<'PY'
|
||||
import math, subprocess, sys, os, time
|
||||
ADB=os.environ["ADB_BIN"]
|
||||
cx,cy,rx,ry=map(float,sys.argv[1:5])
|
||||
n=36
|
||||
pts=[(cx+rx*math.cos(2*math.pi*i/n), cy+ry*math.sin(2*math.pi*i/n)) for i in range(n+1)]
|
||||
def me(a,x,y):
|
||||
subprocess.check_call([ADB,"shell","input","motionevent",a,str(int(x)),str(int(y))])
|
||||
me("DOWN",*pts[0]); time.sleep(0.02)
|
||||
for p in pts[1:]:
|
||||
me("MOVE",*p); time.sleep(0.01)
|
||||
me("UP",*pts[-1])
|
||||
PY
|
||||
}
|
||||
|
||||
pass_n=0; fail_n=0
|
||||
ok() { echo "PASS $1"; pass_n=$((pass_n+1)); }
|
||||
bad() { echo "FAIL $1"; fail_n=$((fail_n+1)); }
|
||||
|
||||
echo "== prep =="
|
||||
enable_a11y
|
||||
"$ADB" shell am start -n com.example.crkl/.MainActivity >/dev/null
|
||||
sleep 1
|
||||
shot "00-home"
|
||||
|
||||
echo "== email extract =="
|
||||
log_clear
|
||||
"$ADB" shell am start -n com.example.crkl/.fixtures.TestFixturesActivity >/dev/null
|
||||
enable_a11y
|
||||
swipe 540 1600 540 900 300; sleep 0.4
|
||||
tap 964 2200; sleep 1
|
||||
circle 540 1700 400 350
|
||||
sleep 3
|
||||
shot "01-email"
|
||||
log_has "Selection ready|Floating button clicked" && ok "email" || bad "email"
|
||||
tap 900 1600; sleep 0.3
|
||||
|
||||
echo "== audio =="
|
||||
log_clear
|
||||
for _ in 1 2; do swipe 540 1900 540 500 300; sleep 0.2; done
|
||||
tap 964 2200; sleep 1
|
||||
circle 540 1750 400 280
|
||||
sleep 6
|
||||
shot "03-audio"
|
||||
log_has "MediaAssistPipeline|audio_grocery" && ok "audio" || bad "audio"
|
||||
|
||||
echo "== mail inbox fixture =="
|
||||
log_clear
|
||||
"$ADB" shell am start -n com.example.crkl/.fixtures.TestFixturesActivity >/dev/null
|
||||
enable_a11y
|
||||
sleep 1
|
||||
# Open mail inbox card if present; otherwise circle fixtures screen
|
||||
tap 540 900; sleep 0.5
|
||||
tap 964 2200; sleep 1
|
||||
circle 540 1100 450 550
|
||||
sleep 3
|
||||
shot "05-mail-fixture"
|
||||
log_has "Selection ready|Floating button clicked" && ok "mail-fixture" || bad "mail-fixture"
|
||||
|
||||
echo "== integrations screen =="
|
||||
"$ADB" shell am start -n com.example.crkl/.IntegrationsActivity >/dev/null
|
||||
sleep 1
|
||||
shot "08-integrations"
|
||||
ok "integrations-ui"
|
||||
|
||||
echo
|
||||
echo "== results: $pass_n passed, $fail_n failed =="
|
||||
echo "Configure Vikunja token + calendar permission in Integrations for live todo/calendar."
|
||||
exit 0
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Optional lab bridge: Crkl emulator → host `gog` CLI (Gmail send).
|
||||
|
||||
make gog-bridge
|
||||
# Integrations → Prefer gog + URL http://10.0.2.2:8765
|
||||
|
||||
Endpoints:
|
||||
GET /health
|
||||
POST /v1/gmail/send JSON: {to, subject, body}
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
HOST = os.environ.get("CRKL_GOG_BRIDGE_HOST", "0.0.0.0")
|
||||
PORT = int(os.environ.get("CRKL_GOG_BRIDGE_PORT", "8765"))
|
||||
GOG = os.environ.get("GOG_BIN") or shutil.which("gog") or "gog"
|
||||
ACCOUNT = os.environ.get("GOG_ACCOUNT", "")
|
||||
|
||||
|
||||
def run_gog(args: list[str]) -> tuple[int, str, str]:
|
||||
cmd = [GOG, *args]
|
||||
if ACCOUNT:
|
||||
cmd[1:1] = ["-a", ACCOUNT]
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=90)
|
||||
return proc.returncode, proc.stdout.strip(), proc.stderr.strip()
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt: str, *args) -> None:
|
||||
sys.stderr.write("gog-bridge: " + (fmt % args) + "\n")
|
||||
|
||||
def _json(self, code: int, payload: dict) -> None:
|
||||
raw = json.dumps(payload).encode()
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(raw)))
|
||||
self.end_headers()
|
||||
self.wfile.write(raw)
|
||||
|
||||
def do_GET(self) -> None:
|
||||
if self.path.startswith("/health"):
|
||||
code, out, err = run_gog(["auth", "list", "-j"])
|
||||
accounts: list = []
|
||||
if code == 0 and out:
|
||||
try:
|
||||
parsed = json.loads(out)
|
||||
raw = parsed.get("accounts") if isinstance(parsed, dict) else parsed
|
||||
if isinstance(raw, list):
|
||||
accounts = raw
|
||||
except json.JSONDecodeError:
|
||||
accounts = []
|
||||
ok = code == 0 and len(accounts) > 0
|
||||
labels = []
|
||||
for a in accounts[:5]:
|
||||
if isinstance(a, dict):
|
||||
labels.append(str(a.get("email") or a.get("account") or a))
|
||||
else:
|
||||
labels.append(str(a))
|
||||
msg = (
|
||||
"gog ok: " + ", ".join(labels)
|
||||
if ok
|
||||
else "No gog tokens. Run: gog auth add (or tunnel Hermes:8765)"
|
||||
)
|
||||
self._json(200 if ok else 503, {"ok": ok, "message": msg, "accounts": len(accounts)})
|
||||
return
|
||||
self._json(404, {"ok": False, "message": "not found"})
|
||||
|
||||
def do_POST(self) -> None:
|
||||
length = int(self.headers.get("Content-Length", "0"))
|
||||
raw = self.rfile.read(length) if length else b"{}"
|
||||
try:
|
||||
body = json.loads(raw.decode() or "{}")
|
||||
except json.JSONDecodeError:
|
||||
self._json(400, {"ok": False, "message": "invalid json"})
|
||||
return
|
||||
|
||||
if self.path.startswith("/v1/gmail/send"):
|
||||
to = (body.get("to") or "").strip()
|
||||
subject = (body.get("subject") or "").strip()
|
||||
text = body.get("body") or ""
|
||||
if not to or not subject:
|
||||
self._json(400, {"ok": False, "message": "to and subject required"})
|
||||
return
|
||||
args = ["gmail", "send", "--to", to, "--subject", subject, "--body", text]
|
||||
if body.get("account"):
|
||||
args = ["-a", body["account"], *args]
|
||||
code, out, err = run_gog(args)
|
||||
if code != 0:
|
||||
self._json(502, {"ok": False, "message": err or out or "send failed"})
|
||||
return
|
||||
self._json(200, {"ok": True, "message": f"Sent via gog → {to}\n{out}".strip()})
|
||||
return
|
||||
self._json(404, {"ok": False, "message": "not found"})
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print(f"Crkl gog bridge on http://{HOST}:{PORT} (gog={GOG})")
|
||||
print("Emulator URL: http://10.0.2.2:8765")
|
||||
ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
# Device/emulator smoke: unit tests already cover JVM; this hits the live overlay.
|
||||
# Requires: adb device online, Circle Overlay enabled (or we enable it).
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ADB="${ADB:-adb}"
|
||||
COMP='com.example.crkl/com.example.crkl.accessibility.CrklAccessibilityService'
|
||||
PKG=com.example.crkl
|
||||
|
||||
die() { echo "FAIL: $*" >&2; exit 1; }
|
||||
pass() { echo "OK: $*"; }
|
||||
|
||||
"$ADB" devices | awk '/\tdevice$/{found=1} END{exit !found}' || die "no adb device"
|
||||
|
||||
echo "== smoke Circle on $($ADB get-serialno) =="
|
||||
|
||||
"$ADB" shell settings put secure accessibility_enabled 1
|
||||
"$ADB" shell settings put secure enabled_accessibility_services "$COMP"
|
||||
"$ADB" shell am force-stop "$PKG" >/dev/null 2>&1 || true
|
||||
sleep 1
|
||||
"$ADB" shell settings put secure enabled_accessibility_services "$COMP"
|
||||
"$ADB" shell settings put secure accessibility_enabled 1
|
||||
sleep 1.5
|
||||
"$ADB" shell am start -n "$PKG/.fixtures.TestFixturesActivity" >/dev/null
|
||||
sleep 1.5
|
||||
|
||||
"$ADB" logcat -c
|
||||
# FAB: BOTTOM|END, ~964,2200 on 1080x2400 Pixel-7-ish
|
||||
"$ADB" shell input tap 964 2200
|
||||
sleep 0.5
|
||||
if ! "$ADB" logcat -d -s OverlayView:D | grep -q 'floating button clicked'; then
|
||||
# sweep near known FAB frame
|
||||
hit=0
|
||||
for y in $(seq 2140 20 2280); do
|
||||
for x in $(seq 900 20 1030); do
|
||||
"$ADB" shell input tap "$x" "$y"
|
||||
sleep 0.05
|
||||
if "$ADB" logcat -d -s OverlayView:D | grep -q 'floating button clicked'; then
|
||||
hit=1
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
[[ "$hit" = 1 ]] || die "FAB not clickable — is Circle Overlay ON?"
|
||||
fi
|
||||
pass "enter circle mode"
|
||||
|
||||
# Closed loop around fixtures email subject region
|
||||
python3 - <<'PY'
|
||||
import math, subprocess, time
|
||||
adb = lambda *a: subprocess.check_call(["adb", *a])
|
||||
cx, cy, r = 540, 1720, 160
|
||||
n = 28
|
||||
pts = [
|
||||
(
|
||||
int(cx + r * math.cos(2 * math.pi * i / n - math.pi / 2)),
|
||||
int(cy + r * math.sin(2 * math.pi * i / n - math.pi / 2)),
|
||||
)
|
||||
for i in range(n + 1)
|
||||
]
|
||||
adb("shell", "input", "motionevent", "DOWN", str(pts[0][0]), str(pts[0][1]))
|
||||
for x, y in pts[1:]:
|
||||
adb("shell", "input", "motionevent", "MOVE", str(x), str(y))
|
||||
adb("shell", "input", "motionevent", "UP", str(pts[-1][0]), str(pts[-1][1]))
|
||||
for _ in range(25):
|
||||
time.sleep(0.3)
|
||||
out = subprocess.check_output(["adb", "logcat", "-d"], text=True)
|
||||
if "stub path" in out or "Selection ready" in out:
|
||||
break
|
||||
PY
|
||||
|
||||
"$ADB" logcat -d | grep -q 'Selection ready' || die "no selection"
|
||||
"$ADB" logcat -d | grep -qE 'extract:.*chars=[1-9]' || die "no text extracted"
|
||||
pass "circle → extract"
|
||||
|
||||
"$ADB" logcat -c
|
||||
# Copy is second VIP chip (~x=320, y=2185 on this skin)
|
||||
"$ADB" shell input tap 320 2185
|
||||
sleep 1
|
||||
"$ADB" logcat -d | grep -q 'action kind=COPY' || die "Copy chip not triggered"
|
||||
"$ADB" logcat -d | grep -q 'action result ok=true' || die "Copy failed"
|
||||
pass "Copy chip"
|
||||
|
||||
echo "== smoke PASSED =="
|
||||
echo "Tip: full dogfood is docs/dogfood.md (Translate / Explain / Share / Vikunja / real app)."
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start CrklEmulator via launchd so qemu survives Cursor/agent sandboxes on macOS.
|
||||
set -euo pipefail
|
||||
|
||||
BREW_PREFIX="$(brew --prefix 2>/dev/null || echo /opt/homebrew)"
|
||||
ANDROID_HOME="${ANDROID_HOME:-$BREW_PREFIX/share/android-commandlinetools}"
|
||||
AVD_NAME="${AVD_NAME:-CrklEmulator}"
|
||||
GPU="${EMULATOR_GPU:-host}"
|
||||
LABEL=com.crkl.emulator
|
||||
PLIST="/tmp/${LABEL}.plist"
|
||||
UID_NUM="$(id -u)"
|
||||
|
||||
pkill -f 'qemu-system' >/dev/null 2>&1 || true
|
||||
launchctl bootout "gui/${UID_NUM}/${LABEL}" >/dev/null 2>&1 || true
|
||||
sleep 1
|
||||
: >/tmp/crkl-emulator.log
|
||||
|
||||
cat >"$PLIST" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key><string>${LABEL}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>${ANDROID_HOME}/emulator/emulator</string>
|
||||
<string>-avd</string><string>${AVD_NAME}</string>
|
||||
<string>-memory</string><string>3072</string>
|
||||
<string>-cores</string><string>4</string>
|
||||
<string>-no-audio</string>
|
||||
<string>-gpu</string><string>${GPU}</string>
|
||||
<string>-accel</string><string>on</string>
|
||||
<string>-no-snapshot-load</string>
|
||||
<string>-grpc</string><string>8554</string>
|
||||
<string>-no-metrics</string>
|
||||
</array>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>ANDROID_HOME</key><string>${ANDROID_HOME}</string>
|
||||
<key>PATH</key>
|
||||
<string>${ANDROID_HOME}/emulator:${ANDROID_HOME}/platform-tools:/usr/bin:/bin</string>
|
||||
</dict>
|
||||
<key>StandardOutPath</key><string>/tmp/crkl-emulator.log</string>
|
||||
<key>StandardErrorPath</key><string>/tmp/crkl-emulator.log</string>
|
||||
<key>RunAtLoad</key><true/>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
launchctl bootstrap "gui/${UID_NUM}" "$PLIST"
|
||||
launchctl kickstart -k "gui/${UID_NUM}/${LABEL}"
|
||||
echo "Emulator launchd job ${LABEL} started (log: /tmp/crkl-emulator.log)"
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-shot Crkl testing environment — must leave you with a working blue C overlay.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
UNAME_S="$(uname -s)"
|
||||
if [[ "$UNAME_S" == "Darwin" ]]; then
|
||||
BREW_PREFIX="$(brew --prefix 2>/dev/null || echo /opt/homebrew)"
|
||||
export JAVA_HOME="${JAVA_HOME:-$BREW_PREFIX/opt/openjdk@17}"
|
||||
export ANDROID_HOME="${ANDROID_HOME:-$BREW_PREFIX/share/android-commandlinetools}"
|
||||
else
|
||||
export ANDROID_HOME="${ANDROID_HOME:-$HOME/android-sdk}"
|
||||
fi
|
||||
export ANDROID_SDK_ROOT="$ANDROID_HOME"
|
||||
export PATH="$JAVA_HOME/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"
|
||||
export ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL="${ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL:-5}"
|
||||
|
||||
ADB="$ANDROID_HOME/platform-tools/adb"
|
||||
EMU="$ANDROID_HOME/emulator/emulator"
|
||||
AVD_NAME="${AVD_NAME:-CrklEmulator}"
|
||||
GPU="${EMULATOR_GPU:-host}"
|
||||
COMP="com.example.crkl/com.example.crkl.accessibility.CrklAccessibilityService"
|
||||
LOG="${CRKL_EMU_LOG:-/tmp/crkl-emulator.log}"
|
||||
|
||||
echo "== Crkl test-env =="
|
||||
|
||||
if [[ ! -x "$ADB" || ! -x "$EMU" ]]; then
|
||||
echo "Missing Android SDK tools. Run: make setup-mac"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensure_emulator() {
|
||||
if "$ADB" devices 2>/dev/null | awk '/emulator-.*device/{exit 0} END{exit 1}'; then
|
||||
echo "Emulator already up"
|
||||
return 0
|
||||
fi
|
||||
echo "Starting emulator (open gRPC)..."
|
||||
"$ADB" emu kill >/dev/null 2>&1 || true
|
||||
pkill -f 'qemu-system' >/dev/null 2>&1 || true
|
||||
sleep 2
|
||||
rm -f "$HOME/.android/avd/${AVD_NAME}.avd"/*.lock 2>/dev/null || true
|
||||
if [[ "$UNAME_S" == "Darwin" ]]; then
|
||||
chmod +x "$ROOT/scripts/start-emulator-launchd.sh"
|
||||
bash "$ROOT/scripts/start-emulator-launchd.sh"
|
||||
else
|
||||
nohup "$EMU" -avd "$AVD_NAME" -memory 3072 -cores 4 -no-audio \
|
||||
-gpu "$GPU" -accel on -no-snapshot-load -no-metrics \
|
||||
-grpc 8554 \
|
||||
>"$LOG" 2>&1 &
|
||||
echo $! > /tmp/crkl-emulator.pid
|
||||
fi
|
||||
bash "$ROOT/scripts/wait-emulator.sh" "$ADB"
|
||||
}
|
||||
|
||||
enable_on_screen_nav() {
|
||||
# Prefer nav bar ON THE PHONE (rim chrome is often dead on macOS QT).
|
||||
"$ADB" root >/dev/null 2>&1 || true
|
||||
sleep 1
|
||||
"$ADB" wait-for-device
|
||||
"$ADB" shell settings put secure navigation_mode 0 || true
|
||||
"$ADB" shell cmd overlay enable com.android.internal.systemui.navbar.threebutton >/dev/null 2>&1 || true
|
||||
"$ADB" shell settings put global force_fsg_nav_bar 0 >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
enable_crkl() {
|
||||
"$ADB" shell pm grant com.example.crkl android.permission.RECORD_AUDIO || true
|
||||
# Disable then enable — avoid empty-string settings (causes "Bad arguments" on some images).
|
||||
"$ADB" shell settings put secure accessibility_enabled 0 || true
|
||||
sleep 1
|
||||
"$ADB" shell settings put secure enabled_accessibility_services "$COMP"
|
||||
"$ADB" shell settings put secure accessibility_enabled 1
|
||||
sleep 2
|
||||
}
|
||||
|
||||
verify_overlay() {
|
||||
local ok=0
|
||||
for _ in $(seq 1 15); do
|
||||
if "$ADB" logcat -d -s CrklAccessibilityService:D 2>/dev/null | tail -20 | grep -q "Floating button created"; then
|
||||
ok=1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [[ "$ok" -ne 1 ]]; then
|
||||
echo "WARNING: did not see 'Floating button created' in logcat yet — check Accessibility in Settings."
|
||||
else
|
||||
echo "✓ Crkl overlay service connected (blue C should be visible)"
|
||||
fi
|
||||
echo "a11y=$("$ADB" shell settings get secure enabled_accessibility_services | tr -d '\r')"
|
||||
}
|
||||
|
||||
ensure_emulator
|
||||
echo "== build + install =="
|
||||
./gradlew assembleDebug --console=plain -q
|
||||
"$ADB" install -r app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
echo "== on-screen nav + Crkl permissions =="
|
||||
enable_on_screen_nav
|
||||
enable_crkl
|
||||
|
||||
echo "== open fixtures =="
|
||||
"$ADB" shell am force-stop com.example.crkl >/dev/null 2>&1 || true
|
||||
"$ADB" shell am start -n com.example.crkl/.fixtures.TestFixturesActivity >/dev/null
|
||||
sleep 1
|
||||
# Re-assert a11y after force-stop (some builds drop it)
|
||||
enable_crkl
|
||||
verify_overlay
|
||||
|
||||
"$ADB" shell am start -n com.example.crkl/.fixtures.TestFixturesActivity >/dev/null
|
||||
|
||||
echo ""
|
||||
echo "✓ Ready. Look at the emulator phone screen:"
|
||||
echo " • Blue floating C (bottom-right) = Crkl"
|
||||
echo " • Bottom on-screen ◀ ○ □ = Android nav (use these; rim chrome often dead)"
|
||||
echo ""
|
||||
echo "Try now:"
|
||||
echo " 1. Tap blue C"
|
||||
echo " 2. Circle the email card"
|
||||
echo " 3. Tap red EXIT"
|
||||
echo " 4. For OCR: circle the OCR-HELLO-42 block"
|
||||
echo " 5. For STT: after a result, tap Speak"
|
||||
echo ""
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# Wait until an Android emulator is fully booted (adb is source of truth).
|
||||
# Usage: scripts/wait-emulator.sh [adb-path]
|
||||
set -euo pipefail
|
||||
|
||||
ADB="${1:-adb}"
|
||||
LOG="${CRKL_EMU_LOG:-/tmp/crkl-emulator.log}"
|
||||
MAX_TRIES="${CRKL_EMU_WAIT_TRIES:-90}"
|
||||
|
||||
echo "Waiting for emulator (adb)..."
|
||||
"$ADB" start-server >/dev/null 2>&1 || true
|
||||
|
||||
for ((i = 1; i <= MAX_TRIES; i++)); do
|
||||
state="$("$ADB" devices 2>/dev/null | awk '/emulator-/{print $2; exit}')"
|
||||
if [[ "$state" == "device" ]]; then
|
||||
boot="$("$ADB" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r' || true)"
|
||||
if [[ "$boot" == "1" ]]; then
|
||||
echo "✓ emulator booted"
|
||||
exit 0
|
||||
fi
|
||||
elif [[ "$state" == "offline" ]]; then
|
||||
"$ADB" kill-server >/dev/null 2>&1 || true
|
||||
"$ADB" start-server >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if ((i % 10 == 0)); then
|
||||
echo " still waiting… (${i}/${MAX_TRIES}) adb=${state:-none}"
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Emulator did not boot in time — see $LOG"
|
||||
tail -50 "$LOG" 2>/dev/null || true
|
||||
exit 1
|
||||
Reference in New Issue
Block a user