Skip to content

Commit 6149ca0

Browse files
committed
Fixed API name parsing
1 parent e5e681a commit 6149ca0

File tree

1 file changed

+161
-10
lines changed

1 file changed

+161
-10
lines changed

avdmanager/AndroidEmulatorManager.swift

Lines changed: 161 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,24 +129,85 @@ class AndroidEmulatorManager: ObservableObject {
129129

130130
var emulators: [(name: String, device: String, apiLevel: String, target: String)] = []
131131

132+
// Try to get detailed info using avdmanager list avd
133+
let avdManagerOutput = await executeCommand("avdmanager", arguments: ["list", "avd"])
134+
let avdDetails = parseAVDManagerOutput(avdManagerOutput)
135+
132136
for line in lines {
133137
let name = line.trimmingCharacters(in: .whitespacesAndNewlines)
134138

135-
// Try to get config info from the AVD directory
136-
let configPath = "\(NSHomeDirectory())/.android/avd/\(name).avd/config.ini"
137-
let configContent = await readFile(at: configPath)
138-
139-
let device = extractValue(from: configContent, key: "hw.device.name") ??
140-
extractValue(from: configContent, key: "hw.device.manufacturer") ?? "Unknown Device"
141-
let apiLevel = extractValue(from: configContent, key: "image.sysdir.1")?.components(separatedBy: "/").last ?? "Unknown API"
142-
let target = extractValue(from: configContent, key: "target") ?? "Unknown Target"
143-
144-
emulators.append((name: name, device: device, apiLevel: apiLevel, target: target))
139+
// First try to get info from avdmanager command
140+
if let avdInfo = avdDetails[name] {
141+
emulators.append((
142+
name: name,
143+
device: avdInfo.device,
144+
apiLevel: avdInfo.apiLevel,
145+
target: avdInfo.target
146+
))
147+
} else {
148+
// Fallback to config file parsing
149+
let configPath = "\(NSHomeDirectory())/.android/avd/\(name).avd/config.ini"
150+
let configContent = await readFile(at: configPath)
151+
152+
let device = extractDeviceName(from: configContent)
153+
let apiLevel = extractAPILevel(from: configContent, avdName: name)
154+
let target = extractTarget(from: configContent, avdName: name)
155+
156+
emulators.append((name: name, device: device, apiLevel: apiLevel, target: target))
157+
}
145158
}
146159

147160
return emulators
148161
}
149162

163+
private func parseAVDManagerOutput(_ output: String) -> [String: (device: String, apiLevel: String, target: String)] {
164+
var avdDetails: [String: (device: String, apiLevel: String, target: String)] = [:]
165+
166+
let lines = output.components(separatedBy: .newlines)
167+
var currentAVD: String?
168+
var currentDevice = "Unknown Device"
169+
var currentAPI = "Unknown"
170+
var currentTarget = "Android"
171+
172+
for line in lines {
173+
let trimmedLine = line.trimmingCharacters(in: .whitespacesAndNewlines)
174+
175+
if trimmedLine.hasPrefix("Name:") {
176+
currentAVD = String(trimmedLine.dropFirst("Name:".count)).trimmingCharacters(in: .whitespacesAndNewlines)
177+
} else if trimmedLine.hasPrefix("Device:") {
178+
currentDevice = String(trimmedLine.dropFirst("Device:".count)).trimmingCharacters(in: .whitespacesAndNewlines)
179+
} else if trimmedLine.hasPrefix("Target:") {
180+
let targetString = String(trimmedLine.dropFirst("Target:".count)).trimmingCharacters(in: .whitespacesAndNewlines)
181+
currentTarget = targetString
182+
183+
// Extract API level from target string (e.g., "Google APIs (Google Inc.) - API Level 33")
184+
if let apiRange = targetString.range(of: "API Level ") {
185+
let apiSubstring = targetString[apiRange.upperBound...]
186+
let apiComponents = apiSubstring.components(separatedBy: " ")
187+
if let firstComponent = apiComponents.first, Int(firstComponent) != nil {
188+
currentAPI = firstComponent
189+
}
190+
}
191+
} else if trimmedLine.contains("---------") && currentAVD != nil {
192+
// End of current AVD block
193+
if let avdName = currentAVD {
194+
avdDetails[avdName] = (device: currentDevice, apiLevel: currentAPI, target: currentTarget)
195+
}
196+
currentAVD = nil
197+
currentDevice = "Unknown Device"
198+
currentAPI = "Unknown"
199+
currentTarget = "Android"
200+
}
201+
}
202+
203+
// Handle last AVD if no separator line at the end
204+
if let avdName = currentAVD {
205+
avdDetails[avdName] = (device: currentDevice, apiLevel: currentAPI, target: currentTarget)
206+
}
207+
208+
return avdDetails
209+
}
210+
150211
private func getRunningEmulatorNames() async -> Set<String> {
151212
// Method 1: Check running processes for emulator commands
152213
let processOutput = await executeCommand("ps", arguments: ["aux"])
@@ -256,6 +317,96 @@ class AndroidEmulatorManager: ObservableObject {
256317
return nil
257318
}
258319

320+
private func extractDeviceName(from content: String) -> String {
321+
// Try different device name keys
322+
if let deviceName = extractValue(from: content, key: "hw.device.name") {
323+
return deviceName
324+
}
325+
if let deviceManufacturer = extractValue(from: content, key: "hw.device.manufacturer") {
326+
return deviceManufacturer
327+
}
328+
if let avdDisplayName = extractValue(from: content, key: "avd.ini.displayname") {
329+
return avdDisplayName
330+
}
331+
return "Unknown Device"
332+
}
333+
334+
private func extractAPILevel(from content: String, avdName: String) -> String {
335+
// Method 1: Try to extract from image.sysdir.1
336+
if let sysDir = extractValue(from: content, key: "image.sysdir.1") {
337+
let components = sysDir.components(separatedBy: "/")
338+
for component in components {
339+
if component.hasPrefix("android-") {
340+
let apiString = String(component.dropFirst("android-".count))
341+
if Int(apiString) != nil {
342+
return apiString
343+
}
344+
}
345+
}
346+
}
347+
348+
// Method 2: Try target
349+
if let target = extractValue(from: content, key: "target") {
350+
if target.hasPrefix("android-") {
351+
let apiString = String(target.dropFirst("android-".count))
352+
if Int(apiString) != nil {
353+
return apiString
354+
}
355+
}
356+
}
357+
358+
// Method 3: Try tag.id combined with tag.display
359+
if let tagId = extractValue(from: content, key: "tag.id"),
360+
let tagDisplay = extractValue(from: content, key: "tag.display") {
361+
if tagDisplay.contains("API") {
362+
let components = tagDisplay.components(separatedBy: " ")
363+
for component in components {
364+
if Int(component) != nil {
365+
return component
366+
}
367+
}
368+
}
369+
}
370+
371+
// Method 4: Try PlayStore tag approach
372+
if let playStoreTag = extractValue(from: content, key: "PlayStore.enabled") {
373+
// If PlayStore is enabled, it's likely a Google APIs version
374+
if let target = extractValue(from: content, key: "target") {
375+
return target.replacingOccurrences(of: "android-", with: "")
376+
}
377+
}
378+
379+
return "Unknown"
380+
}
381+
382+
private func extractTarget(from content: String, avdName: String) -> String {
383+
// Try to get a human-readable target name
384+
if let target = extractValue(from: content, key: "target") {
385+
if target.hasPrefix("android-") {
386+
let apiLevel = String(target.dropFirst("android-".count))
387+
388+
// Check if it has Google APIs
389+
if let tagId = extractValue(from: content, key: "tag.id") {
390+
if tagId.contains("google_apis") {
391+
return "Android \(apiLevel) (Google APIs)"
392+
} else if tagId.contains("playstore") || tagId.contains("google_apis_playstore") {
393+
return "Android \(apiLevel) (Google Play)"
394+
}
395+
}
396+
397+
return "Android \(apiLevel)"
398+
}
399+
return target
400+
}
401+
402+
// Fallback to tag display if available
403+
if let tagDisplay = extractValue(from: content, key: "tag.display") {
404+
return tagDisplay
405+
}
406+
407+
return "Android"
408+
}
409+
259410
@discardableResult
260411
private func executeCommand(_ command: String, arguments: [String] = [], background: Bool = false) async -> String {
261412
return await withCheckedContinuation { continuation in

0 commit comments

Comments
 (0)