-
Notifications
You must be signed in to change notification settings - Fork 8
fix: remove dependency on avdmanager #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThe changes replace command-execution and JSON-parsing based device discovery for both Android Virtual Devices and iOS Simulators with direct filesystem-based approaches using INI and plist file parsing. Timing instrumentation is added to track device enumeration performance. Changes
Sequence Diagram(s)sequenceDiagram
participant caller as Caller
participant common as GetAllControllableDevices()
participant avd as getAVDDetails()
participant sim as GetSimulators()
participant fs as Filesystem
caller ->> common: Request all devices
rect rgb(200, 220, 255)
note over common: Android Discovery
common ->> avd: Start timing
avd ->> fs: Read ~/.android/avd/
fs -->> avd: List .ini files
avd ->> fs: Parse config.ini files
fs -->> avd: AVD metadata
avd -->> common: map[AvdId]AVDInfo
common ->> common: Stop timing (Android)
end
rect rgb(220, 200, 255)
note over common: iOS Simulator Discovery
common ->> sim: Start timing
sim ->> fs: Read CoreSimulator Devices
fs -->> sim: Device directories
sim ->> fs: Read device.plist files
fs -->> sim: Plist data (UDID, State)
sim -->> common: []Simulator
common ->> common: Stop timing (Simulators)
end
common ->> common: Log consolidated metrics
common -->> caller: All devices + timings
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
devices/simulator.go (2)
130-134: Consider graceful handling when CoreSimulator directory doesn't exist.On non-macOS systems or fresh macOS installs without Xcode, the
~/Library/Developer/CoreSimulator/Devicesdirectory won't exist. Returning an error may be too aggressive. Consider returning an empty slice with a verbose warning instead:devicesPath := filepath.Join(homeDir, "Library", "Developer", "CoreSimulator", "Devices") entries, err := os.ReadDir(devicesPath) if err != nil { + if os.IsNotExist(err) { + // CoreSimulator not installed or no simulators created yet + return []Simulator{}, nil + } return nil, fmt.Errorf("failed to read devices directory: %w", err) }
156-162: State mapping may be incomplete.The comment mentions state 1 and 3, but iOS simulators can have additional states (e.g., 2 for "Booting", 4 for "ShuttingDown"). While defaulting to "Shutdown" is a safe fallback, consider logging unknown states for debugging:
// convert state integer to string - // state 1 = Shutdown (offline) - // state 3 = Booted (online) + // Known states: 1 = Shutdown, 2 = Booting, 3 = Booted, 4 = ShuttingDown stateStr := "Shutdown" if device.State == 3 { stateStr = "Booted" + } else if device.State == 2 { + stateStr = "Booting" }devices/avd.go (1)
102-107:NameandDevicefields are set to the same value.Both fields are assigned
displayName, making one redundant. If they serve different purposes, consider populatingDevicewith the hardware device type (e.g., fromhw.device.namein config.ini) instead:+ hwDevice := configData.Section("").Key("hw.device.name").String() + if hwDevice == "" { + hwDevice = displayName + } + avdMap[avdName] = AVDInfo{ Name: displayName, - Device: displayName, + Device: hwDevice, APILevel: apiLevel, AvdId: avdId, }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (4)
devices/avd.go(2 hunks)devices/common.go(3 hunks)devices/simulator.go(4 hunks)go.mod(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
devices/common.go (4)
devices/android.go (1)
GetAndroidDevices(478-493)utils/logger.go (1)
Verbose(19-23)devices/ios.go (1)
ListIOSDevices(89-107)devices/simulator.go (1)
GetSimulators(124-175)
devices/avd.go (2)
utils/logger.go (1)
Verbose(19-23)devices/common.go (1)
ControllableDevice(26-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: lint
🔇 Additional comments (5)
devices/common.go (1)
62-139: LGTM! Clean timing instrumentation.The timing metrics implementation is well-structured: it tracks each device category separately, initializes counters correctly, and consolidates the output into a single verbose log message. The conditional formatting based on
includeOfflineavoids logging zeroed-out offline metrics when not requested.devices/simulator.go (1)
36-42: LGTM! Well-structured plist model.The
devicePliststruct correctly uses plist tags matching the CoreSimulator device.plist schema.devices/avd.go (2)
49-111: LGTM! Solid filesystem-based AVD discovery.The implementation correctly parses the two-level INI structure (
.android/avd/*.ini→path→config.ini), handles errors gracefully withcontinue, and extracts the necessary fields. This removes the dependency onavdmanagercommand execution.
148-153: Offline device ID usesavdNamewhich may not match expected format.The offline device is created with
id: avdName(the.inifilename without extension). When the device boots,adb devicestypically reports it asemulator-XXXX. This ID mismatch could cause issues when:
- The same device appears as two different devices (offline by avdName, online by emulator serial)
- Device-specific operations fail to find the offline device after boot
Consider documenting this expected behavior or using a consistent identifier scheme.
go.mod (1)
3-3: Go version 1.25.0 is valid and released.Go 1.25.0 was released on August 12, 2025, and as of November 2025, version 1.25.4 is the latest patch release in the 1.25 series. The concern about this being an unreleased future version is incorrect. Using Go 1.25.0 in go.mod will not cause build issues due to version availability.
| for avdName, info := range avdDetails { | ||
| // check if this AVD is already online by checking if AvdId is in online device IDs | ||
| // the avdName from the .ini file should match the device ID when online | ||
| _, isOnline := onlineDeviceIDs[info.AvdId] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Script executed:
#!/bin/bash
# First, let's examine the file structure and then read the relevant section
wc -l devices/avd.go
echo "---"
# Read around lines 128-131 with context
sed -n '100,160p' devices/avd.goRepository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
158 devices/avd.go
---
avdId := configData.Section("").Key("AvdId").String()
avdMap[avdName] = AVDInfo{
Name: displayName,
Device: displayName,
APILevel: apiLevel,
AvdId: avdId,
}
}
return avdMap, nil
}
// getOfflineAndroidEmulators returns a list of offline Android emulators (AVDs not currently running)
func getOfflineAndroidEmulators(onlineDeviceIDs map[string]bool) ([]ControllableDevice, error) {
var offlineDevices []ControllableDevice
// get detailed info about AVDs by reading .ini files directly
avdDetails, err := getAVDDetails()
if err != nil {
return offlineDevices, err
}
if len(avdDetails) == 0 {
return offlineDevices, nil
}
// create offline device entries for AVDs that are not running
for avdName, info := range avdDetails {
// check if this AVD is already online by checking if AvdId is in online device IDs
// the avdName from the .ini file should match the device ID when online
_, isOnline := onlineDeviceIDs[info.AvdId]
if !isOnline {
displayName := strings.ReplaceAll(avdName, "_", " ")
version := ""
if info.Device != "" {
// use device name if available (e.g., "pixel_6 (Google)")
displayName = info.Device
// clean up the device name
if idx := strings.Index(displayName, "("); idx > 0 {
displayName = strings.TrimSpace(displayName[:idx])
}
displayName = strings.ReplaceAll(displayName, "_", " ")
}
version = convertAPILevelToVersion(info.APILevel)
offlineDevices = append(offlineDevices, &AndroidDevice{
id: avdName,
name: displayName,
version: version,
state: "offline",
})
}
}
return offlineDevices, nil
}
</shell_result>
Script executed:
#!/bin/bash
# Search for where onlineDeviceIDs is created or used
rg -n "onlineDeviceIDs" devices/avd.go -A 2 -B 2Repository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
112-
113-// getOfflineAndroidEmulators returns a list of offline Android emulators (AVDs not currently running)
114:func getOfflineAndroidEmulators(onlineDeviceIDs map[string]bool) ([]ControllableDevice, error) {
115- var offlineDevices []ControllableDevice
116-
--
129- // check if this AVD is already online by checking if AvdId is in online device IDs
130- // the avdName from the .ini file should match the device ID when online
131: _, isOnline := onlineDeviceIDs[info.AvdId]
132-
133- if !isOnline {
</shell_result>
Script executed:
#!/bin/bash
# Look at the full avd.go file to understand the flow
cat -n devices/avd.goRepository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
1 package devices
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7
8 "github.com/mobile-next/mobilecli/utils"
9 "gopkg.in/ini.v1"
10 )
11
12 // AVDInfo represents information about an Android Virtual Device
13 type AVDInfo struct {
14 Name string
15 Device string
16 APILevel string
17 AvdId string
18 }
19
20 // apiLevelToVersion maps Android API levels to version strings
21 var apiLevelToVersion = map[string]string{
22 "36": "16.0",
23 "35": "15.0",
24 "34": "14.0",
25 "33": "13.0",
26 "32": "12.1", // Android 12L
27 "31": "12.0",
28 "30": "11.0",
29 "29": "10.0",
30 "28": "9.0",
31 "27": "8.1",
32 "26": "8.0",
33 "25": "7.1",
34 "24": "7.0",
35 "23": "6.0",
36 "22": "5.1",
37 "21": "5.0",
38 }
39
40 // convertAPILevelToVersion converts an API level to Android version string
41 func convertAPILevelToVersion(apiLevel string) string {
42 if version, ok := apiLevelToVersion[apiLevel]; ok {
43 return version
44 }
45 // if no mapping found, return the API level as-is
46 return apiLevel
47 }
48
49 // getAVDDetails retrieves AVD information by reading .ini files directly
50 func getAVDDetails() (map[string]AVDInfo, error) {
51 avdMap := make(map[string]AVDInfo)
52
53 homeDir, err := os.UserHomeDir()
54 if err != nil {
55 return avdMap, err
56 }
57
58 avdDir := filepath.Join(homeDir, ".android", "avd")
59 pattern := filepath.Join(avdDir, "*.ini")
60
61 matches, err := filepath.Glob(pattern)
62 if err != nil {
63 return avdMap, err
64 }
65
66 for _, iniFile := range matches {
67 // extract avd name from .ini filename
68 avdName := strings.TrimSuffix(filepath.Base(iniFile), ".ini")
69
70 // read the .ini file to get the path
71 iniConfig, err := ini.Load(iniFile)
72 if err != nil {
73 utils.Verbose("Failed to read %s: %v", iniFile, err)
74 continue
75 }
76
77 avdPath := iniConfig.Section("").Key("path").String()
78 if avdPath == "" {
79 continue
80 }
81
82 // read the config.ini inside the .avd directory
83 configPath := filepath.Join(avdPath, "config.ini")
84 configData, err := ini.Load(configPath)
85 if err != nil {
86 utils.Verbose("Failed to read %s: %v", configPath, err)
87 continue
88 }
89
90 displayName := configData.Section("").Key("avd.ini.displayname").String()
91 if displayName == "" {
92 continue
93 }
94
95 // extract API level from target (e.g., "android-31" -> "31")
96 target := configData.Section("").Key("target").String()
97 apiLevel := strings.TrimPrefix(target, "android-")
98
99 // get AvdId for matching with online devices
100 avdId := configData.Section("").Key("AvdId").String()
101
102 avdMap[avdName] = AVDInfo{
103 Name: displayName,
104 Device: displayName,
105 APILevel: apiLevel,
106 AvdId: avdId,
107 }
108 }
109
110 return avdMap, nil
111 }
112
113 // getOfflineAndroidEmulators returns a list of offline Android emulators (AVDs not currently running)
114 func getOfflineAndroidEmulators(onlineDeviceIDs map[string]bool) ([]ControllableDevice, error) {
115 var offlineDevices []ControllableDevice
116
117 // get detailed info about AVDs by reading .ini files directly
118 avdDetails, err := getAVDDetails()
119 if err != nil {
120 return offlineDevices, err
121 }
122
123 if len(avdDetails) == 0 {
124 return offlineDevices, nil
125 }
126
127 // create offline device entries for AVDs that are not running
128 for avdName, info := range avdDetails {
129 // check if this AVD is already online by checking if AvdId is in online device IDs
130 // the avdName from the .ini file should match the device ID when online
131 _, isOnline := onlineDeviceIDs[info.AvdId]
132
133 if !isOnline {
134 displayName := strings.ReplaceAll(avdName, "_", " ")
135 version := ""
136
137 if info.Device != "" {
138 // use device name if available (e.g., "pixel_6 (Google)")
139 displayName = info.Device
140 // clean up the device name
141 if idx := strings.Index(displayName, "("); idx > 0 {
142 displayName = strings.TrimSpace(displayName[:idx])
143 }
144 displayName = strings.ReplaceAll(displayName, "_", " ")
145 }
146 version = convertAPILevelToVersion(info.APILevel)
147
148 offlineDevices = append(offlineDevices, &AndroidDevice{
149 id: avdName,
150 name: displayName,
151 version: version,
152 state: "offline",
153 })
154 }
155 }
156
157 return offlineDevices, nil
158 }
</shell_result>
Script executed:
#!/bin/bash
# Search for calls to getOfflineAndroidEmulators and how onlineDeviceIDs is built
rg -n "getOfflineAndroidEmulators|onlineDeviceIDs" --type go -A 3 -B 3Repository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
devices/avd.go-110- return avdMap, nil
devices/avd.go-111-}
devices/avd.go-112-
devices/avd.go:113:// getOfflineAndroidEmulators returns a list of offline Android emulators (AVDs not currently running)
devices/avd.go:114:func getOfflineAndroidEmulators(onlineDeviceIDs map[string]bool) ([]ControllableDevice, error) {
devices/avd.go-115- var offlineDevices []ControllableDevice
devices/avd.go-116-
devices/avd.go-117- // get detailed info about AVDs by reading .ini files directly
--
devices/avd.go-128- for avdName, info := range avdDetails {
devices/avd.go-129- // check if this AVD is already online by checking if AvdId is in online device IDs
devices/avd.go-130- // the avdName from the .ini file should match the device ID when online
devices/avd.go:131: _, isOnline := onlineDeviceIDs[info.AvdId]
devices/avd.go-132-
devices/avd.go-133- if !isOnline {
devices/avd.go-134- displayName := strings.ReplaceAll(avdName, "_", " ")
--
devices/common.go-79- if includeOffline {
devices/common.go-80- startOfflineAndroid := time.Now()
devices/common.go-81- // build map of online device IDs for quick lookup
devices/common.go:82: onlineDeviceIDs := make(map[string]bool)
devices/common.go-83- for _, device := range androidDevices {
devices/common.go:84: onlineDeviceIDs[device.ID()] = true
devices/common.go-85- }
devices/common.go-86-
devices/common.go:87: offlineEmulators, err := getOfflineAndroidEmulators(onlineDeviceIDs)
devices/common.go-88- offlineAndroidDuration = time.Since(startOfflineAndroid).Milliseconds()
devices/common.go-89- if err != nil {
devices/common.go-90- utils.Verbose("Warning: Failed to get offline Android emulators: %v", err)
</shell_result>
Script executed:
#!/bin/bash
# Search for AndroidDevice type definition and ID() method
rg -n "type AndroidDevice|func.*ID\(\)" --type go -A 5 -B 2Repository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
devices/simulator.go-70-}
devices/simulator.go-71-
devices/simulator.go:72:func (s SimulatorDevice) ID() string { return s.UDID }
devices/simulator.go-73-func (s SimulatorDevice) Name() string { return s.Simulator.Name }
devices/simulator.go-74-func (s SimulatorDevice) Platform() string { return "ios" }
devices/simulator.go-75-func (s SimulatorDevice) DeviceType() string { return "simulator" }
devices/simulator.go-76-func (s SimulatorDevice) Version() string { return parseSimulatorVersion(s.Runtime) }
devices/simulator.go-77-func (s SimulatorDevice) State() string {
--
devices/ios.go-37-}
devices/ios.go-38-
devices/ios.go:39:func (d IOSDevice) ID() string {
devices/ios.go-40- return d.Udid
devices/ios.go-41-}
devices/ios.go-42-
devices/ios.go-43-func (d IOSDevice) Name() string {
devices/ios.go-44- return d.DeviceName
--
devices/android.go-20-
devices/android.go-21-// AndroidDevice implements the ControllableDevice interface for Android devices
devices/android.go:22:type AndroidDevice struct {
devices/android.go-23- id string
devices/android.go-24- name string
devices/android.go-25- version string
devices/android.go-26- state string // "online" or "offline"
devices/android.go-27- transportID string // adb transport ID (e.g., "emulator-5554"), only set for online devices
devices/android.go-28-}
devices/android.go-29-
devices/android.go:30:func (d *AndroidDevice) ID() string {
devices/android.go-31- return d.id
devices/android.go-32-}
devices/android.go-33-
devices/android.go-34-func (d *AndroidDevice) Name() string {
devices/android.go-35- return d.name
</shell_result>
Script executed:
#!/bin/bash
# Search for AndroidDevice creation and adb devices parsing
rg -n "&AndroidDevice{" --type go -A 10 -B 5Repository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 2
stderr:
regex parse error:
&AndroidDevice{
^
error: unclosed counted repetition
</shell_result>
Script executed:
#!/bin/bash
# Search for AndroidDevice instantiation
rg -n "AndroidDevice\{" --type go -A 10 -B 5Repository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
devices/android.go-420- if avdName != "" {
devices/android.go-421- deviceID = avdName
devices/android.go-422- }
devices/android.go-423- }
devices/android.go-424-
devices/android.go:425: devices = append(devices, &AndroidDevice{
devices/android.go-426- id: deviceID,
devices/android.go-427- transportID: transportID,
devices/android.go-428- name: getAndroidDeviceName(transportID),
devices/android.go-429- version: getAndroidDeviceVersion(transportID),
devices/android.go-430- state: "online",
devices/android.go-431- })
devices/android.go-432- }
devices/android.go-433- }
devices/android.go-434- }
devices/android.go-435-
--
devices/avd.go-143- }
devices/avd.go-144- displayName = strings.ReplaceAll(displayName, "_", " ")
devices/avd.go-145- }
devices/avd.go-146- version = convertAPILevelToVersion(info.APILevel)
devices/avd.go-147-
devices/avd.go:148: offlineDevices = append(offlineDevices, &AndroidDevice{
devices/avd.go-149- id: avdName,
devices/avd.go-150- name: displayName,
devices/avd.go-151- version: version,
devices/avd.go-152- state: "offline",
devices/avd.go-153- })
devices/avd.go-154- }
devices/avd.go-155- }
devices/avd.go-156-
devices/avd.go-157- return offlineDevices, nil
devices/avd.go-158-}
</shell_result>
Script executed:
#!/bin/bash
# Read context around AndroidDevice creation in android.go
sed -n '395,440p' devices/android.goRepository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
_, err := d.runAdbCommand(cmd...)
if err != nil {
return fmt.Errorf("failed to execute gesture action %s: %v", action.Type, err)
}
}
return nil
}
func parseAdbDevicesOutput(output string) []ControllableDevice {
var devices []ControllableDevice
lines := strings.Split(output, "\n")
for i := 1; i < len(lines); i++ {
line := strings.TrimSpace(lines[i])
parts := strings.Fields(line)
if len(parts) == 2 {
transportID := parts[0]
status := parts[1]
if status == "device" {
deviceID := transportID
// for emulators, use AVD name as the consistent ID
if strings.HasPrefix(transportID, "emulator-") {
avdName := getAVDName(transportID)
if avdName != "" {
deviceID = avdName
}
}
devices = append(devices, &AndroidDevice{
id: deviceID,
transportID: transportID,
name: getAndroidDeviceName(transportID),
version: getAndroidDeviceVersion(transportID),
state: "online",
})
}
}
}
return devices
}
// getAVDName returns the AVD name for an emulator, or empty string if not an emulator
func getAVDName(transportID string) string {
</shell_result>
Script executed:
#!/bin/bash
# Find getAVDName implementation
rg -n "func getAVDName" --type go -A 20Repository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
devices/android.go:440:func getAVDName(transportID string) string {
devices/android.go-441- avdCmd := exec.Command(getAdbPath(), "-s", transportID, "shell", "getprop", "ro.boot.qemu.avd_name")
devices/android.go-442- avdOutput, err := avdCmd.CombinedOutput()
devices/android.go-443- if err == nil && len(avdOutput) > 0 {
devices/android.go-444- avdName := strings.TrimSpace(string(avdOutput))
devices/android.go-445- return avdName
devices/android.go-446- }
devices/android.go-447- return ""
devices/android.go-448-}
devices/android.go-449-
devices/android.go-450-func getAndroidDeviceName(deviceID string) string {
devices/android.go-451- // try getting AVD name first (for emulators)
devices/android.go-452- avdName := getAVDName(deviceID)
devices/android.go-453- if avdName != "" {
devices/android.go-454- return strings.ReplaceAll(avdName, "_", " ")
devices/android.go-455- }
devices/android.go-456-
devices/android.go-457- // fall back to product model
devices/android.go-458- modelCmd := exec.Command(getAdbPath(), "-s", deviceID, "shell", "getprop", "ro.product.model")
devices/android.go-459- modelOutput, err := modelCmd.CombinedOutput()
devices/android.go-460- if err == nil && len(modelOutput) > 0 {
</shell_result>
Android emulator config.ini AvdId field format
<web_search_result>
The AvdId in config.ini is the AVD's identifier — typically the AVD directory/name (the name you gave the device, e.g. Pixel2 or Nexus_6P_API_28). It is a plain string (no special encoding) used as the emulator's ID and normally matches the .avd folder name. [1][2]
Sources:
- Android emulator AVD template config showing AvdId lines. [1]
- Example AVD config.ini from Stack Overflow showing AvdId usage. [2]
</web_search_result>
Script executed:
#!/bin/bash
# Trace the complete flow: how online devices are added to map vs how offline are checked
echo "=== Online device ID assignment (from android.go) ==="
sed -n '415,432p' devices/android.go
echo -e "\n=== Map population (from common.go) ==="
sed -n '79,90p' devices/common.go
echo -e "\n=== Offline lookup (from avd.go) ==="
sed -n '128,132p' devices/avd.goRepository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
=== Online device ID assignment (from android.go) ===
deviceID := transportID
// for emulators, use AVD name as the consistent ID
if strings.HasPrefix(transportID, "emulator-") {
avdName := getAVDName(transportID)
if avdName != "" {
deviceID = avdName
}
}
devices = append(devices, &AndroidDevice{
id: deviceID,
transportID: transportID,
name: getAndroidDeviceName(transportID),
version: getAndroidDeviceVersion(transportID),
state: "online",
})
}
=== Map population (from common.go) ===
if includeOffline {
startOfflineAndroid := time.Now()
// build map of online device IDs for quick lookup
onlineDeviceIDs := make(map[string]bool)
for _, device := range androidDevices {
onlineDeviceIDs[device.ID()] = true
}
offlineEmulators, err := getOfflineAndroidEmulators(onlineDeviceIDs)
offlineAndroidDuration = time.Since(startOfflineAndroid).Milliseconds()
if err != nil {
utils.Verbose("Warning: Failed to get offline Android emulators: %v", err)
=== Offline lookup (from avd.go) ===
for avdName, info := range avdDetails {
// check if this AVD is already online by checking if AvdId is in online device IDs
// the avdName from the .ini file should match the device ID when online
_, isOnline := onlineDeviceIDs[info.AvdId]
</shell_result>
Script executed:
#!/bin/bash
# Check the getAVDDetails function for how it handles missing AvdId
sed -n '49,111p' devices/avd.go | cat -nRepository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
1 // getAVDDetails retrieves AVD information by reading .ini files directly
2 func getAVDDetails() (map[string]AVDInfo, error) {
3 avdMap := make(map[string]AVDInfo)
4
5 homeDir, err := os.UserHomeDir()
6 if err != nil {
7 return avdMap, err
8 }
9
10 avdDir := filepath.Join(homeDir, ".android", "avd")
11 pattern := filepath.Join(avdDir, "*.ini")
12
13 matches, err := filepath.Glob(pattern)
14 if err != nil {
15 return avdMap, err
16 }
17
18 for _, iniFile := range matches {
19 // extract avd name from .ini filename
20 avdName := strings.TrimSuffix(filepath.Base(iniFile), ".ini")
21
22 // read the .ini file to get the path
23 iniConfig, err := ini.Load(iniFile)
24 if err != nil {
25 utils.Verbose("Failed to read %s: %v", iniFile, err)
26 continue
27 }
28
29 avdPath := iniConfig.Section("").Key("path").String()
30 if avdPath == "" {
31 continue
32 }
33
34 // read the config.ini inside the .avd directory
35 configPath := filepath.Join(avdPath, "config.ini")
36 configData, err := ini.Load(configPath)
37 if err != nil {
38 utils.Verbose("Failed to read %s: %v", configPath, err)
39 continue
40 }
41
42 displayName := configData.Section("").Key("avd.ini.displayname").String()
43 if displayName == "" {
44 continue
45 }
46
47 // extract API level from target (e.g., "android-31" -> "31")
48 target := configData.Section("").Key("target").String()
49 apiLevel := strings.TrimPrefix(target, "android-")
50
51 // get AvdId for matching with online devices
52 avdId := configData.Section("").Key("AvdId").String()
53
54 avdMap[avdName] = AVDInfo{
55 Name: displayName,
56 Device: displayName,
57 APILevel: apiLevel,
58 AvdId: avdId,
59 }
60 }
61
62 return avdMap, nil
63 }
</shell_result>
Android AVD config.ini AvdId field mandatory required always present
<web_search_result>
Short answer: No — AvdId in an AVD's config.ini is not strictly mandatory. The emulator's code accepts a missing config.ini (and older AVDs may omit AvdId), though modern AVD templates and tools usually write AvdId into config.ini and some external tools expect it. [1][2][3]
Sources:
- emulator/AVD code showing config.ini may be absent and is optional. [1]
- official AVD config.ini template including AvdId (so current tools typically include it). [2]
- avdmanager/AVD tooling docs (AVD layout and tools generate .ini files). [3]
</web_search_result>
Script executed:
#!/bin/bash
# Search for any tests or examples that might show AvdId usage
rg -n "AvdId" --type go -B 2 -A 2Repository: mobile-next/mobilecli
<shell_result>
Repository: mobile-next/mobilecli
Exit code: 0
stdout:
devices/avd.go-15- Device string
devices/avd.go-16- APILevel string
devices/avd.go:17: AvdId string
devices/avd.go-18-}
devices/avd.go-19-
--
devices/avd.go-97- apiLevel := strings.TrimPrefix(target, "android-")
devices/avd.go-98-
devices/avd.go:99: // get AvdId for matching with online devices
devices/avd.go:100: avdId := configData.Section("").Key("AvdId").String()
devices/avd.go-101-
devices/avd.go-102- avdMap[avdName] = AVDInfo{
--
devices/avd.go-104- Device: displayName,
devices/avd.go-105- APILevel: apiLevel,
devices/avd.go:106: AvdId: avdId,
devices/avd.go-107- }
devices/avd.go-108- }
--
devices/avd.go-127- // create offline device entries for AVDs that are not running
devices/avd.go-128- for avdName, info := range avdDetails {
devices/avd.go:129: // check if this AVD is already online by checking if AvdId is in online device IDs
devices/avd.go-130- // the avdName from the .ini file should match the device ID when online
devices/avd.go:131: _, isOnline := onlineDeviceIDs[info.AvdId]
devices/avd.go-132-
devices/avd.go-133- if !isOnline {
</shell_result>
Based on my verification, I've found that:
-
AvdId source: Line 100 in avd.go reads
AvdIdfrom config.ini using.String(), which returns an empty string if the key doesn't exist. -
Online device matching: Lines 415-421 in android.go show that online emulators use the AVD name from the
ro.boot.qemu.avd_nameproperty (retrieved via adb). -
The actual issue: According to Android documentation,
AvdIdis not mandatory in config.ini. Older AVDs may omit it entirely. When missing,info.AvdIdbecomes an empty string, causing the lookup at line 131 to fail. -
No fallback logic: The code doesn't fall back to
avdNameifAvdIdis empty, meaning emulators without theAvdIdfield will always appear offline.
Verify that AvdId is always populated; use avdName as fallback if missing.
The code matches offline emulators against online devices using info.AvdId from config.ini (line 131). However, AvdId is not mandatory in config.ini—older AVDs may omit it entirely. When missing, info.AvdId becomes an empty string, causing the online device lookup to fail. This results in emulators without an explicit AvdId field appearing offline even when running. Add a fallback to use avdName if AvdId is empty to ensure all AVDs are correctly matched against running devices.
🤖 Prompt for AI Agents
In devices/avd.go around lines 128-131, the code checks onlineDeviceIDs using
info.AvdId which can be an empty string because AvdId is optional in config.ini;
change the lookup to first use info.AvdId and if that is empty use the avdName
as a fallback (e.g., derive an idVar := info.AvdId; if idVar == "" { idVar =
avdName } and then check onlineDeviceIDs[idVar]) so emulators that omit AvdId
are correctly matched as online.
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.