Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ rest.
Idempotent. Installs cmdline-tools (via Homebrew if missing), accepts
licenses, installs platform/build-tools for the compileSdk, the emulator, the
API 37 Google Play phone image and API 36 Android TV image, creates
the two reusable AVDs, and writes
the two reusable AVDs (`--google-tv` adds the opt-in Google TV image and
AVD), and writes
`local.properties` (`sdk.dir` plus `putioSdkKotlinPath`, defaulting to the
sibling `../putio-sdk-kotlin` checkout, which must be cloned).

Expand Down
25 changes: 21 additions & 4 deletions docs/harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ Two reusable AVDs, arm64 on Apple Silicon (x86_64 elsewhere):
| `putio-phone` | pixel_7 | `android-37.0;google_apis_playstore` |
| `putio-tv` | tv_1080p | `android-36;android-tv` |

An opt-in Google TV AVD covers the Google TV launcher and Play surfaces. The
default bootstrap does not install its image or create it:

| AVD | Device profile | System image |
| --- | --- | --- |
| `putio-google-tv` | tv_1080p | `android-36;google-tv` |

```bash
./scripts/bootstrap.sh --google-tv # adds the image and AVD
./scripts/emulator.sh boot google-tv --headless
./scripts/emulator.sh stop google-tv
```

A fresh `putio-google-tv` boots into Google TV setup; finish or cancel it
before launcher proof. `scripts/prove.sh tv` still targets `putio-tv`; install
on the Google TV emulator with `adb -s <serial> install` for manual proof.

```bash
./scripts/emulator.sh boot phone --headless # prints serial; reuses if running
./scripts/emulator.sh status
Expand All @@ -37,11 +54,11 @@ category. The harness fails closed before install when that secure OAuth
transport is unavailable. TV and the CI managed device remain API 36.

Bootstrap never replaces a mismatched AVD. It still provisions the other
profile, then fails with an explicit command; stop and delete that exact
profiles, then fails with an explicit command; stop and delete that exact
profile yourself before rerunning bootstrap. The harness owns only
`putio-phone`, `putio-tv`, and the `--ephemeral` AVDs it creates; other AVDs on
the machine are left alone, even when `avdmanager list avd` reports them as
unloadable.
`putio-phone`, `putio-tv`, the opt-in `putio-google-tv`, and the
`--ephemeral` AVDs it creates; other AVDs on the machine are left alone, even
when `avdmanager list avd` reports them as unloadable.

## Launch Proof

Expand Down
33 changes: 23 additions & 10 deletions scripts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,23 @@
# Google Play phone image, the API 36 Android TV image, and both reusable
# AVDs. Writes local.properties.
# Idempotent: safe to re-run; already-installed packages are skipped.
#
# scripts/bootstrap.sh [--google-tv]
#
# --google-tv also installs the API 36 Google TV image and provisions the
# opt-in putio-google-tv AVD.

source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"

AVD_PROFILES=(phone tv)
while [[ $# -gt 0 ]]; do
case "$1" in
--google-tv) AVD_PROFILES=(phone tv google-tv) ;;
*) print_usage "${BASH_SOURCE[0]}"; exit 64 ;;
esac
shift
done

COMPILE_SDK_PLATFORM="platforms;android-37.0"
BUILD_TOOLS="build-tools;37.0.0"

Expand All @@ -32,8 +46,13 @@ SDKMANAGER="${SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager"
[[ -x "${SDKMANAGER}" ]] || die "sdkmanager missing under ${SDK_ROOT}"
log "SDK root: ${SDK_ROOT}"

SDK_PACKAGES=("platform-tools" "emulator" "${COMPILE_SDK_PLATFORM}" "${BUILD_TOOLS}")
for profile in "${AVD_PROFILES[@]}"; do
SDK_PACKAGES+=("$(image_for "${profile}")")
done

packages_missing=0
for pkg in "platform-tools" "emulator" "${COMPILE_SDK_PLATFORM}" "${BUILD_TOOLS}" "$(phone_image)" "$(tv_image)"; do
for pkg in "${SDK_PACKAGES[@]}"; do
[[ -d "${SDK_ROOT}/$(echo "${pkg}" | tr ';' '/')" ]] || packages_missing=1
done

Expand All @@ -44,15 +63,9 @@ if [[ "${packages_missing}" == "1" ]]; then
(yes || true) | "${SDKMANAGER}" --sdk_root="${SDK_ROOT}" --licenses >/dev/null

log "installing SDK packages (first run downloads several GB)"
"${SDKMANAGER}" --sdk_root="${SDK_ROOT}" --install \
"platform-tools" \
"emulator" \
"${COMPILE_SDK_PLATFORM}" \
"${BUILD_TOOLS}" \
"$(phone_image)" \
"$(tv_image)"
"${SDKMANAGER}" --sdk_root="${SDK_ROOT}" --install "${SDK_PACKAGES[@]}"

for pkg in "platform-tools" "emulator" "${COMPILE_SDK_PLATFORM}" "${BUILD_TOOLS}" "$(phone_image)" "$(tv_image)"; do
for pkg in "${SDK_PACKAGES[@]}"; do
[[ -d "${SDK_ROOT}/$(echo "${pkg}" | tr ';' '/')" ]] || die "package ${pkg} missing after install"
done
else
Expand Down Expand Up @@ -129,7 +142,7 @@ if [[ ! -d "${SDK_KOTLIN_PATH}" ]]; then
die "putio-sdk-kotlin checkout missing at ${SDK_KOTLIN_PATH}; run: git clone git@github.com:putdotio/putio-sdk-kotlin.git '${SDK_KOTLIN_PATH}' (or point putioSdkKotlinPath in local.properties at an existing checkout), then re-run bootstrap"
fi

provision_avds || exit 1
provision_avds "${AVD_PROFILES[@]}" || exit 1

log "bootstrap complete"
log "next: ./gradlew verify :app:assembleMobileProductionDebug :app:assembleTvProductionDebug"
17 changes: 9 additions & 8 deletions scripts/emulator.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#!/usr/bin/env bash
# Manage the repo's emulators: putio-phone and putio-tv.
# Manage the repo's emulators: putio-phone, putio-tv, and the opt-in
# putio-google-tv (provisioned by `scripts/bootstrap.sh --google-tv`).
#
# scripts/emulator.sh create <phone|tv> [--name NAME]
# scripts/emulator.sh boot <phone|tv> [--headless] [--name NAME]
# scripts/emulator.sh stop <phone|tv> [--name NAME]
# scripts/emulator.sh create <phone|tv|google-tv> [--name NAME]
# scripts/emulator.sh boot <phone|tv|google-tv> [--headless] [--name NAME]
# scripts/emulator.sh stop <phone|tv|google-tv> [--name NAME]
# scripts/emulator.sh stop <emulator-NNNN>
# scripts/emulator.sh delete <phone|tv> [--name NAME]
# scripts/emulator.sh delete <phone|tv|google-tv> [--name NAME]
# scripts/emulator.sh status
#
# Ownership contract:
Expand Down Expand Up @@ -35,7 +36,7 @@ parse_profile_args() {
HEADLESS="${PUTIO_EMULATOR_HEADLESS:-0}"
while [[ $# -gt 0 ]]; do
case "$1" in
phone|tv) PROFILE="$1" ;;
phone|tv|google-tv) PROFILE="$1" ;;
--name) NAME="${2:?--name requires a value}"; shift ;;
--headless) HEADLESS=1 ;;
*) die "unknown argument: $1" ;;
Expand Down Expand Up @@ -64,7 +65,7 @@ do_create() {
local image create_out
image="$(image_for "${PROFILE}")"
[[ -d "${SDK_ROOT}/$(echo "${image}" | tr ';' '/')" ]] || \
die "system image ${image} not installed; run scripts/bootstrap.sh"
die "system image ${image} not installed; run $(bootstrap_command_for "${PROFILE}")"
if avd_registered "${NAME}"; then
require_matching_avd_image
log "AVD ${NAME} already exists (${image})"
Expand Down Expand Up @@ -197,7 +198,7 @@ do_stop() {
[[ $# -eq 1 ]] || usage
serial="${target}"
;;
phone|tv)
phone|tv|google-tv)
parse_profile_args "$@"
serial="$(serial_for_avd "${NAME}")" || \
{ log "no running emulator for AVD ${NAME}"; return 0; }
Expand Down
26 changes: 20 additions & 6 deletions scripts/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ require_sdk_root() {
# system images are not published for API 37 yet, so TV remains on API 36.
PHONE_AVD="putio-phone"
TV_AVD="putio-tv"
GOOGLE_TV_AVD="putio-google-tv"
PHONE_API_LEVEL="37"
CHROME_PACKAGE="com.android.chrome"
AUTH_TAB_SERVICE_ACTION="android.support.customtabs.action.CustomTabsService"
Expand All @@ -78,19 +79,31 @@ sdk_arch() {

phone_image() { echo "system-images;android-37.0;google_apis_playstore;$(sdk_arch)"; }
tv_image() { echo "system-images;android-36;android-tv;$(sdk_arch)"; }
# Opt-in: only `scripts/bootstrap.sh --google-tv` installs and provisions it.
google_tv_image() { echo "system-images;android-36;google-tv;$(sdk_arch)"; }

# profile -> AVD name / image / avdmanager device id
avd_name_for() {
case "$1" in
phone) echo "${PHONE_AVD}" ;;
tv) echo "${TV_AVD}" ;;
*) die "unknown emulator profile '$1' (expected phone|tv)" ;;
google-tv) echo "${GOOGLE_TV_AVD}" ;;
*) die "unknown emulator profile '$1' (expected phone|tv|google-tv)" ;;
esac
}
image_for() {
case "$1" in
phone) phone_image ;;
tv) tv_image ;;
google-tv) google_tv_image ;;
*) die "unknown emulator profile '$1' (expected phone|tv|google-tv)" ;;
esac
}
Comment thread
Copilot marked this conversation as resolved.

bootstrap_command_for() {
case "$1" in
google-tv) echo "scripts/bootstrap.sh --google-tv" ;;
*) echo "scripts/bootstrap.sh" ;;
esac
}

Expand All @@ -111,23 +124,24 @@ avd_stop_command() {
avd_recreate_command() {
local profile="$1" name="$2"
if [[ "${name}" == "$(avd_name_for "${profile}")" ]]; then
echo "scripts/bootstrap.sh"
bootstrap_command_for "${profile}"
else
echo "scripts/emulator.sh create ${profile} --name ${name}"
fi
}
device_for() {
case "$1" in
phone) echo "pixel_7" ;;
tv) echo "tv_1080p" ;;
tv|google-tv) echo "tv_1080p" ;;
esac
}

# A refused profile keeps its recovery message and fails the run, but must not
# stop the other profile from being provisioned.
# stop the other profiles from being provisioned.
provision_avds() {
local profile failed=()
for profile in phone tv; do
local profile failed=() profiles=("$@")
[[ $# -gt 0 ]] || profiles=(phone tv)
for profile in "${profiles[@]}"; do
"${REPO_ROOT}/scripts/emulator.sh" create "${profile}" || failed+=("${profile}")
done
[[ ${#failed[@]} -eq 0 ]] || { log "ERROR: AVD provisioning failed for: ${failed[*]}"; return 1; }
Expand Down
47 changes: 47 additions & 0 deletions scripts/test-emulator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -577,4 +577,51 @@ grep -Fq "AVD provisioning failed for: phone" "${provision_out}" || \
[[ "$(<"${state}/avd-operations")" == "create:${PHONE_AVD}:${target_image}" ]] || \
fail "recovered provisioning touched the existing TV AVD"

# Google TV is opt-in: default provisioning never creates it, and its recovery
# commands point at the opt-in bootstrap.
google_tv_target_image="system-images;android-36;google-tv;$(sdk_arch)"
[[ "$(google_tv_image)" == "${google_tv_target_image}" ]] || \
fail "Google TV image is not pinned to API 36 Google TV"
reset_avd "${PHONE_AVD}" "${target_image}" 0
google_tv_out="${tmpdir}/google-tv.log"
if "${REPO_ROOT}/scripts/emulator.sh" create google-tv >"${google_tv_out}" 2>&1; then
fail "Google TV AVD was created without its system image"
fi
grep -Fq "system image ${google_tv_target_image} not installed; run scripts/bootstrap.sh --google-tv" \
"${google_tv_out}" || fail "missing Google TV image omitted the opt-in bootstrap command"
[[ ! -s "${state}/avd-operations" ]] || fail "missing Google TV image mutated state"

mkdir -p "${fake_sdk}/$(tr ';' '/' <<<"${google_tv_target_image}")"
(provision_avds) >"${provision_out}" 2>&1 || fail "default provisioning failed"
[[ "$(<"${state}/avd-operations")" == "create:${TV_AVD}:${tv_target_image}" ]] || \
fail "default provisioning did not keep to the phone and TV AVDs"

: > "${state}/avd-operations"
(provision_avds phone tv google-tv) >"${provision_out}" 2>&1 || \
fail "opt-in provisioning failed"
[[ "$(<"${state}/avd-operations")" == "create:${GOOGLE_TV_AVD}:${google_tv_target_image}" ]] || \
fail "opt-in provisioning did not create only the Google TV AVD"
[[ "$(avd_image_for_name "${GOOGLE_TV_AVD}")" == "${google_tv_target_image}" ]] || \
fail "Google TV AVD did not use the pinned Google TV image"

reset_avd "${GOOGLE_TV_AVD}" "${tv_target_image}" 0
if "${REPO_ROOT}/scripts/emulator.sh" create google-tv >"${google_tv_out}" 2>&1; then
fail "mismatched Google TV AVD was replaced"
fi
grep -Fq "explicitly run scripts/emulator.sh stop google-tv, then scripts/emulator.sh delete google-tv, then scripts/bootstrap.sh --google-tv" \
"${google_tv_out}" || fail "mismatched Google TV AVD omitted its opt-in recovery command"
[[ ! -s "${state}/avd-operations" ]] || fail "mismatched Google TV AVD refusal mutated state"

reset_avd "${GOOGLE_TV_AVD}" "${google_tv_target_image}" 0
reset_runtime
google_tv_serial="$(PUTIO_EMULATOR_BOOT_TIMEOUT=10 "${REPO_ROOT}/scripts/emulator.sh" boot google-tv --headless 2>"${google_tv_out}")" || \
fail "Google TV AVD did not boot"
[[ "${google_tv_serial}" == "emulator-5554" ]] || fail "Google TV boot printed '${google_tv_serial}'"
[[ "$(<"${state}/name")" == "${GOOGLE_TV_AVD}" ]] || fail "Google TV boot started the wrong AVD"
if grep -Eq 'ro\.build\.version\.sdk|com\.android\.chrome' "${state}/adb-calls"; then
fail "Google TV boot ran phone readiness checks"
fi
"${REPO_ROOT}/scripts/emulator.sh" stop google-tv >/dev/null 2>&1 || fail "could not stop the Google TV emulator"
[[ "$(<"${state}/running")" == "0" ]] || fail "Google TV stop left its emulator running"

echo "emulator contract tests passed"