245 lines
10 KiB
Bash
Executable File
245 lines
10 KiB
Bash
Executable File
#!/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 ""
|