Files
gitea-migration/runners-conversion/periodVault/fix-android-emulator.sh

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 ""