Files
openclaw/apps/android/scripts/voice-e2e.sh
2026-05-25 09:01:06 +05:30

227 lines
6.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
ANDROID_DIR="$ROOT_DIR/apps/android"
PACKAGE_NAME="ai.openclaw.app"
RECEIVER="$PACKAGE_NAME/.VoiceE2eReceiver"
RUN_ACTION="ai.openclaw.app.debug.RUN_VOICE_E2E"
OPEN_ACTION="ai.openclaw.app.debug.OPEN_VOICE_E2E"
PORT=18789
HOST="127.0.0.1"
MODE="both"
TRANSCRIPT="Reply exactly: Android voice e2e normal path ok."
REALTIME_ASSISTANT="Android realtime voice e2e relay path ok."
TIMEOUT_MS=60000
INSTALL=1
CONNECT=1
CLEANUP=0
START_GATEWAY=0
usage() {
cat <<'USAGE'
Usage: apps/android/scripts/voice-e2e.sh [options]
Options:
--mode normal|realtime|both Voice path to test. Default: both.
--transcript TEXT Synthetic user transcript for the voice turn.
--realtime-assistant TEXT Synthetic realtime assistant relay text.
--host HOST Gateway host visible from Android. Default: 127.0.0.1.
--port PORT Gateway port. Default: 18789.
--timeout-ms MS Per-mode timeout. Default: 60000.
--skip-install Reuse the installed debug app.
--no-connect Do not rewrite manual gateway settings.
--start-gateway Start a temporary local gateway with bws_get_secret.
--cleanup Stop voice capture after screenshots.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--mode)
MODE="$2"
shift 2
;;
--transcript)
TRANSCRIPT="$2"
shift 2
;;
--realtime-assistant)
REALTIME_ASSISTANT="$2"
shift 2
;;
--host)
HOST="$2"
shift 2
;;
--port)
PORT="$2"
shift 2
;;
--timeout-ms)
TIMEOUT_MS="$2"
shift 2
;;
--skip-install)
INSTALL=0
shift
;;
--no-connect)
CONNECT=0
shift
;;
--start-gateway)
START_GATEWAY=1
shift
;;
--cleanup)
CLEANUP=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
export JAVA_HOME="${JAVA_HOME:-/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home}"
export ANDROID_HOME="${ANDROID_HOME:-/opt/homebrew/share/android-commandlinetools}"
export ANDROID_SDK_ROOT="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
export PATH="/opt/homebrew/opt/openjdk@17/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
ARTIFACT_DIR="/tmp/openclaw-android-voice-e2e-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$ARTIFACT_DIR"
cleanup_gateway() {
if [[ -n "${GATEWAY_PID:-}" ]]; then
kill "$GATEWAY_PID" >/dev/null 2>&1 || true
fi
}
trap cleanup_gateway EXIT
if ! adb devices -l | awk 'NR > 1 && $2 == "device" { found = 1 } END { exit(found ? 0 : 1) }'; then
echo "no authorized Android device found" >&2
adb devices -l >&2
exit 1
fi
adb reverse "tcp:$PORT" "tcp:$PORT" >/dev/null
if [[ "$START_GATEWAY" -eq 1 ]]; then
if command -v bws_get_secret >/dev/null 2>&1; then
OPENCLAW_OPENAI_API_KEY="$(bws_get_secret OPENCLAW_OPENAI_API_KEY)"
else
OPENCLAW_OPENAI_API_KEY="$(zsh -ic 'bws_get_secret OPENCLAW_OPENAI_API_KEY')"
fi
(
cd "$ROOT_DIR"
OPENAI_API_KEY="$OPENCLAW_OPENAI_API_KEY" \
pnpm openclaw gateway run \
--port "$PORT" \
--auth none \
--bind loopback \
--force \
--allow-unconfigured \
--ws-log compact
) >"$ARTIFACT_DIR/gateway.log" 2>&1 &
GATEWAY_PID=$!
sleep 4
if ! kill -0 "$GATEWAY_PID" >/dev/null 2>&1; then
cat "$ARTIFACT_DIR/gateway.log" >&2
exit 1
fi
unset OPENCLAW_OPENAI_API_KEY
fi
if [[ "$INSTALL" -eq 1 ]]; then
(cd "$ANDROID_DIR" && ./gradlew :app:installPlayDebug)
fi
adb shell pm grant "$PACKAGE_NAME" android.permission.RECORD_AUDIO >/dev/null 2>&1 || true
adb shell am force-stop "$PACKAGE_NAME" >/dev/null
adb shell am start -a "$OPEN_ACTION" -n "$PACKAGE_NAME/.MainActivity" >/dev/null
adb logcat -c
run_mode() {
local test_mode="$1"
local result_name="$ARTIFACT_DIR/result-$test_mode.json"
local screenshot_name="$ARTIFACT_DIR/screen-$test_mode.png"
local transcript_base64
local realtime_assistant_base64
transcript_base64="$(printf '%s' "$TRANSCRIPT" | base64 | tr -d '\n')"
realtime_assistant_base64="$(printf '%s' "$REALTIME_ASSISTANT" | base64 | tr -d '\n')"
adb shell run-as "$PACKAGE_NAME" rm -f cache/voice_e2e_result.json >/dev/null 2>&1 || true
local no_connect_flag=true
if [[ "$CONNECT" -eq 1 ]]; then
no_connect_flag=false
fi
adb shell am broadcast \
-a "$RUN_ACTION" \
-n "$RECEIVER" \
--es mode "$test_mode" \
--ez noConnect "$no_connect_flag" \
--es host "$HOST" \
--ei port "$PORT" \
--ez tls false \
--el timeoutMs "$TIMEOUT_MS" \
--el connectTimeoutMs "$TIMEOUT_MS" \
--es transcriptBase64 "$transcript_base64" \
--es realtimeAssistantBase64 "$realtime_assistant_base64" >/dev/null
local deadline=$((SECONDS + TIMEOUT_MS / 1000 + 20))
local result=""
while [[ "$SECONDS" -lt "$deadline" ]]; do
result="$(adb shell run-as "$PACKAGE_NAME" cat cache/voice_e2e_result.json 2>/dev/null | tr -d '\r' || true)"
if [[ -n "$result" ]]; then
break
fi
sleep 1
done
if [[ -z "$result" ]]; then
echo "voice e2e $test_mode timed out waiting for result" >&2
exit 1
fi
printf '%s\n' "$result" >"$result_name"
adb exec-out screencap -p >"$screenshot_name"
if ! grep -q '"ok":true' "$result_name"; then
echo "voice e2e $test_mode failed: $result" >&2
exit 1
fi
}
case "$MODE" in
both)
run_mode normal
run_mode realtime
;;
normal|dictation)
run_mode normal
;;
realtime|talk)
run_mode realtime
;;
*)
echo "unknown mode: $MODE" >&2
exit 2
;;
esac
adb logcat -d -v time |
rg -i 'OpenClaw|TalkMode|MicCapture|AudioRecord|SpeechRecognizer|realtime|talk.session|appendAudio|transcript|Talk failed|Transcription failed|Speech network|VoiceE2E' |
tail -250 >"$ARTIFACT_DIR/logcat.txt" || true
if [[ "$CLEANUP" -eq 1 ]]; then
adb shell am broadcast -a "$RUN_ACTION" -n "$RECEIVER" --es mode stop >/dev/null
fi
echo "$ARTIFACT_DIR"