#!/usr/bin/env bash # fix-android-emulator.sh — Install Android OS system image and fix/create phone or Wear OS AVD # Usage: ./scripts/fix-android-emulator.sh # Run when emulator fails with "No initial system image for this configuration". # Supports phone (default) and Wear OS emulators. Requires: Android SDK (ANDROID_HOME or # ~/Library/Android/sdk). Installs SDK command-line tools if missing. # # ENV VARs (defaults use latest SDK): # ANDROID_HOME SDK root (default: $HOME/Library/Android/sdk on macOS) # ANDROID_SDK_ROOT Same as ANDROID_HOME if set # ANDROID_EMULATOR_API_LEVEL API level, e.g. 35 or 30 (default: auto = latest from sdkmanager --list) # ANDROID_AVD_NAME AVD name to fix or create (default: phone, or wear when type=wearos) # ANDROID_EMULATOR_DEVICE Device profile for new AVDs (default: pixel_8 for phone, wear_os_square for Wear) # ANDROID_EMULATOR_TYPE phone (default) or wearos — which system image and device profile to use set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # --- Default ENV VARs: latest SDK --- # ANDROID_HOME: SDK root (default: $HOME/Library/Android/sdk on macOS) export ANDROID_HOME="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}" if [[ -z "$ANDROID_HOME" ]]; then if [[ -d "$HOME/Library/Android/sdk" ]]; then export ANDROID_HOME="$HOME/Library/Android/sdk" else echo -e "${RED}ERROR: ANDROID_HOME not set and ~/Library/Android/sdk not found.${NC}" echo "Set ANDROID_HOME to your Android SDK root, or install Android Studio / SDK." exit 1 fi fi # Emulator type: phone (default) or wearos — determines system image and default device profile EMULATOR_TYPE="${ANDROID_EMULATOR_TYPE:-phone}" EMULATOR_TYPE=$(echo "$EMULATOR_TYPE" | tr '[:upper:]' '[:lower:]') # AVD name and device profile (override with ANDROID_AVD_NAME / ANDROID_EMULATOR_DEVICE) if [[ "$EMULATOR_TYPE" == "wearos" ]]; then AVD_NAME="${ANDROID_AVD_NAME:-wear}" DEVICE_PROFILE="${ANDROID_EMULATOR_DEVICE:-wear_os_square}" else AVD_NAME="${ANDROID_AVD_NAME:-phone}" DEVICE_PROFILE="${ANDROID_EMULATOR_DEVICE:-pixel_8}" fi # --- Find or install SDK command-line tools (sdkmanager, avdmanager) --- SDKMANAGER="" AVDMANAGER="" for d in "$ANDROID_HOME/cmdline-tools/latest/bin" "$ANDROID_HOME/tools/bin"; do if [[ -x "$d/sdkmanager" ]]; then SDKMANAGER="$d/sdkmanager" AVDMANAGER="$d/avdmanager" break fi done if [[ -z "$SDKMANAGER" ]] && command -v sdkmanager &>/dev/null; then SDKMANAGER="sdkmanager" AVDMANAGER="avdmanager" fi install_cmdline_tools() { echo -e "${YELLOW}Downloading Android SDK command-line tools...${NC}" local zip_url="https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip" local zip_file="$PROJECT_ROOT/build/cmdlinetools.zip" local tmp_dir="$ANDROID_HOME/cmdline-tools" mkdir -p "$(dirname "$zip_file")" "$tmp_dir" if ! curl -fsSL -o "$zip_file" "$zip_url"; then echo -e "${RED}Download failed. Install command-line tools manually:${NC}" echo " Android Studio → Settings → Appearance & Behavior → System Settings → Android SDK" echo " → SDK Tools tab → check 'Android SDK Command-line Tools (latest)' → Apply" exit 1 fi (cd "$tmp_dir" && unzip -q -o "$zip_file" && mv cmdline-tools latest 2>/dev/null || true) rm -f "$zip_file" SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" AVDMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager" if [[ ! -x "$SDKMANAGER" ]]; then # Some zips unpack to cmdline-tools/ inside the zip if [[ -d "$tmp_dir/cmdline-tools" ]]; then mv "$tmp_dir/cmdline-tools" "$tmp_dir/latest" fi SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" AVDMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager" fi if [[ ! -x "$SDKMANAGER" ]]; then echo -e "${RED}Command-line tools install failed. Install from Android Studio SDK Manager.${NC}" exit 1 fi echo -e "${GREEN}Command-line tools installed.${NC}" } if [[ -z "$SDKMANAGER" ]] || [[ ! -x "$SDKMANAGER" ]]; then install_cmdline_tools fi # --- Ensure PATH for this script --- export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH" # --- Default to latest SDK system image (when ANDROID_EMULATOR_API_LEVEL unset) --- # Parses sdkmanager --list for highest API level with Google Play arm64-v8a image. set_latest_system_image() { local list_output list_output=$("$SDKMANAGER" --list 2>/dev/null) || true local best_api=0 local best_package="" local pkg api # Match package lines (path may be first column or whole line): system-images;android-NN;google_apis...;arm64-v8a while IFS= read -r line; do pkg=$(echo "$line" | sed -n 's/.*\(system-images;android-[0-9][0-9]*;google_apis[^;]*;arm64-v8a\).*/\1/p') [[ -z "$pkg" ]] && continue api=$(echo "$pkg" | sed 's/.*android-\([0-9][0-9]*\).*/\1/') if [[ "$api" =~ ^[0-9]+$ ]] && [[ "$api" -gt "$best_api" ]]; then best_api="$api" best_package="$pkg" fi done <<< "$list_output" if [[ -n "$best_package" ]] && [[ "$best_api" -gt 0 ]]; then ANDROID_EMULATOR_API_LEVEL="$best_api" SYSTEM_IMAGE_PACKAGE="$best_package" echo -e "${GREEN}Using latest SDK system image: API $best_api ($SYSTEM_IMAGE_PACKAGE)${NC}" fi } # Parses sdkmanager --list for highest API level with Wear OS image. # Matches: system-images;android-NN;wear;arm64-v8a or ...;google_apis;wear_os_arm64 set_latest_system_image_wear() { local list_output list_output=$("$SDKMANAGER" --list 2>/dev/null) || true local best_api=0 local best_package="" local pkg api while IFS= read -r line; do # Must be a system image line containing android-NN and wear (wear; or wear_os) [[ "$line" != *"system-images"* ]] && continue [[ "$line" != *"android-"* ]] && continue [[ "$line" != *"wear"* ]] && continue # Extract package: system-images;android-NN;... (semicolon-separated, may be first column) pkg=$(echo "$line" | sed -n 's/.*\(system-images;android-[0-9][0-9]*;[^;]*;[^;]*\).*/\1/p') [[ -z "$pkg" ]] && continue api=$(echo "$pkg" | sed 's/.*android-\([0-9][0-9]*\).*/\1/') if [[ "$api" =~ ^[0-9]+$ ]] && [[ "$api" -gt "$best_api" ]]; then best_api="$api" best_package="$pkg" fi done <<< "$list_output" if [[ -n "$best_package" ]] && [[ "$best_api" -gt 0 ]]; then ANDROID_EMULATOR_API_LEVEL="$best_api" SYSTEM_IMAGE_PACKAGE="$best_package" echo -e "${GREEN}Using latest Wear OS system image: API $best_api ($SYSTEM_IMAGE_PACKAGE)${NC}" fi } # If ANDROID_EMULATOR_API_LEVEL not set, detect latest from SDK (phone or Wear OS) if [[ -z "${ANDROID_EMULATOR_API_LEVEL:-}" ]]; then if [[ "$EMULATOR_TYPE" == "wearos" ]]; then set_latest_system_image_wear else set_latest_system_image fi fi # Fallback when detection didn't set a package (e.g. no sdkmanager list) API_LEVEL="${ANDROID_EMULATOR_API_LEVEL:-35}" if [[ -z "${SYSTEM_IMAGE_PACKAGE:-}" ]]; then if [[ "$EMULATOR_TYPE" == "wearos" ]]; then # Wear OS: images often at API 30; package format android-NN;wear;arm64-v8a WEAR_API="${ANDROID_EMULATOR_API_LEVEL:-30}" SYSTEM_IMAGE_PACKAGE="system-images;android-${WEAR_API};wear;arm64-v8a" API_LEVEL="$WEAR_API" elif [[ "$API_LEVEL" == "36" ]]; then SYSTEM_IMAGE_PACKAGE="system-images;android-36;google_apis_playstore_ps16k;arm64-v8a" else SYSTEM_IMAGE_PACKAGE="system-images;android-${API_LEVEL};google_apis_playstore;arm64-v8a" fi fi # --- Accept licenses (non-interactive) --- echo -e "${YELLOW}Accepting SDK licenses...${NC}" yes 2>/dev/null | "$SDKMANAGER" --licenses >/dev/null 2>&1 || true # --- Install system image --- echo -e "${YELLOW}Installing system image: $SYSTEM_IMAGE_PACKAGE${NC}" if ! "$SDKMANAGER" "$SYSTEM_IMAGE_PACKAGE"; then echo -e "${RED}Failed to install system image. Try a different API level:${NC}" echo " ANDROID_EMULATOR_API_LEVEL=34 $0" exit 1 fi # --- Verify image has system.img (path from package: a;b;c;d -> a/b/c/d) --- REL_IMAGE_DIR=$(echo "$SYSTEM_IMAGE_PACKAGE" | sed 's/;/\//g') IMAGE_DIR="$ANDROID_HOME/$REL_IMAGE_DIR" if [[ ! -f "$IMAGE_DIR/system.img" ]]; then echo -e "${RED}Installed image missing system.img at $IMAGE_DIR${NC}" exit 1 fi echo -e "${GREEN}System image OK: $IMAGE_DIR${NC}" # --- Resolve AVD directory (phone may point to e.g. Pixel_9_Pro.avd via .ini) --- AVD_INI="$HOME/.android/avd/${AVD_NAME}.ini" AVD_DIR="" if [[ -f "$AVD_INI" ]]; then AVD_PATH=$(grep "^path=" "$AVD_INI" 2>/dev/null | cut -d= -f2-) if [[ -n "$AVD_PATH" ]] && [[ -d "$AVD_PATH" ]]; then AVD_DIR="$AVD_PATH" fi fi if [[ -z "$AVD_DIR" ]]; then AVD_DIR="$HOME/.android/avd/${AVD_NAME}.avd" fi # Update existing AVD config to use the working system image if [[ -d "$AVD_DIR" ]] && [[ -f "$AVD_DIR/config.ini" ]] && [[ -f "$IMAGE_DIR/system.img" ]]; then CONFIG="$AVD_DIR/config.ini" if grep -q "image.sysdir" "$CONFIG"; then # Portable sed: write to temp then mv (macOS sed -i needs backup arg) sed "s|image.sysdir.1=.*|image.sysdir.1=$REL_IMAGE_DIR/|" "$CONFIG" > "${CONFIG}.tmp" mv "${CONFIG}.tmp" "$CONFIG" echo -e "${GREEN}Updated AVD config to use $REL_IMAGE_DIR${NC}" fi elif [[ ! -d "$AVD_DIR" ]]; then echo -e "${YELLOW}Creating AVD '$AVD_NAME' with device profile $DEVICE_PROFILE...${NC}" echo no | "$AVDMANAGER" create avd \ -n "$AVD_NAME" \ -k "$SYSTEM_IMAGE_PACKAGE" \ -d "$DEVICE_PROFILE" \ --force echo -e "${GREEN}AVD '$AVD_NAME' created.${NC}" fi echo "" echo -e "${GREEN}Done. Start the emulator with:${NC}" echo " emulator -avd $AVD_NAME" echo "" if [[ "$EMULATOR_TYPE" == "wearos" ]]; then echo "Fix Wear OS only: ANDROID_EMULATOR_TYPE=wearos $0" echo "Or fix both phone and Wear: $0 && ANDROID_EMULATOR_TYPE=wearos $0" else echo "Or run deploy: ./scripts/deploy-emulator.sh android" fi echo ""