diff --git a/packages/flutter_tools/lib/src/android/android_emulator.dart b/packages/flutter_tools/lib/src/android/android_emulator.dart index 3602aa0823..30dd0fc08b 100644 --- a/packages/flutter_tools/lib/src/android/android_emulator.dart +++ b/packages/flutter_tools/lib/src/android/android_emulator.dart @@ -8,6 +8,7 @@ import 'package:meta/meta.dart'; import '../android/android_sdk.dart'; import '../android/android_workflow.dart'; +import '../base/file_system.dart'; import '../base/process.dart'; import '../emulator.dart'; import 'android_sdk.dart'; @@ -24,12 +25,19 @@ class AndroidEmulators extends EmulatorDiscovery { } class AndroidEmulator extends Emulator { - AndroidEmulator( - String id - ) : super(id); + AndroidEmulator(String id, [this._properties]) + : super(id, _properties != null && _properties.isNotEmpty); + + Map _properties; @override - String get name => id; + String get name => _properties['hw.device.name']; + + @override + String get manufacturer => _properties['hw.device.manufacturer']; + + @override + String get label => _properties['avd.ini.displayname']; // @override // Future launch() async { @@ -41,20 +49,57 @@ class AndroidEmulator extends Emulator { /// Return the list of available emulator AVDs. List getEmulatorAvds() { final String emulatorPath = getEmulatorPath(androidSdk); - if (emulatorPath == null) + if (emulatorPath == null) { return []; - final String text = runSync([emulatorPath, '-list-avds']); + } + + final String listAvdsOutput = runSync([emulatorPath, '-list-avds']); + final List emulators = []; - parseEmulatorAvdOutput(text, emulators); + extractEmulatorAvdInfo(listAvdsOutput, emulators); return emulators; } /// Parse the given `emulator -list-avds` output in [text], and fill out the given list -/// of emulators. -@visibleForTesting -void parseEmulatorAvdOutput(String text, - List emulators) { - for (String line in text.trim().split('\n')) { - emulators.add(new AndroidEmulator(line)); +/// of emulators by reading information from the relevant ini files. +void extractEmulatorAvdInfo(String text, List emulators) { + for (String id in text.trim().split('\n')) { + emulators.add(_createEmulator(id)); } } + +AndroidEmulator _createEmulator(String id) { + id = id.trim(); + final File iniFile = fs.file(fs.path.join(getAvdPath(), '$id.ini')); + final Map ini = _parseIniLines(iniFile.readAsLinesSync()); + + if (ini['path'] != null) { + final File configFile = fs.file(fs.path.join(ini['path'], 'config.ini')); + if (configFile.existsSync()) { + final Map properties = _parseIniLines(configFile.readAsLinesSync()); + return new AndroidEmulator(id, properties); + } + } + + return new AndroidEmulator(id); +} + +// TODO: Tests +@visibleForTesting +Map _parseIniLines(List contents) { + final Map results = {}; + + final Iterable> properties = contents + .map((String l) => l.trim()) + .where((String l) => + l != '' && !l.startsWith('#')) // Strip blank lines/comments + .where((String l) => + l.contains('=')) // Discard anything that isn't simple name=value + .map((String l) => l.split('=')); // Split into name/value + + for (List property in properties) { + results[property[0].trim()] = property[1].trim(); + } + + return results; +} diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart index 1875c48db6..bc10b95d24 100644 --- a/packages/flutter_tools/lib/src/android/android_sdk.dart +++ b/packages/flutter_tools/lib/src/android/android_sdk.dart @@ -59,9 +59,9 @@ String getAdbPath([AndroidSdk existingSdk]) { } } -/// Locate ADB. Prefer to use one from an Android SDK, if we can locate that. -/// This should be used over accessing androidSdk.adbPath directly because it -/// will work for those users who have Android Platform Tools installed but +/// Locate 'emulator'. Prefer to use one from an Android SDK, if we can locate that. +/// This should be used over accessing androidSdk.emulatorPath directly because it +/// will work for those users who have Android Tools installed but /// not the full SDK. String getEmulatorPath([AndroidSdk existingSdk]) { if (existingSdk?.emulatorPath != null) @@ -76,6 +76,18 @@ String getEmulatorPath([AndroidSdk existingSdk]) { } } +/// Locate the path for storing AVD emulator images. Returns null if none found. +String getAvdPath() { + final List searchPaths = [ + platform.environment['ANDROID_AVD_HOME'], + fs.path.join(platform.environment['HOME'], '.android', 'avd'), + ]; + return searchPaths.where((String p) => p != null).firstWhere( + (String p) => fs.directory(p).existsSync(), + orElse: () => null, + ); +} + class AndroidSdk { AndroidSdk(this.directory, [this.ndkDirectory, this.ndkCompiler, this.ndkCompilerArgs]) { diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart index db40d65526..737eb1f8bd 100644 --- a/packages/flutter_tools/lib/src/android/android_workflow.dart +++ b/packages/flutter_tools/lib/src/android/android_workflow.dart @@ -43,7 +43,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow { bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty; @override - bool get canListEmulators => getEmulatorPath(androidSdk) != null; + bool get canListEmulators => getEmulatorPath(androidSdk) != null && getAvdPath() != null; static const String _kJdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/'; diff --git a/packages/flutter_tools/lib/src/emulator.dart b/packages/flutter_tools/lib/src/emulator.dart index 57c647a55f..af243ed3f9 100644 --- a/packages/flutter_tools/lib/src/emulator.dart +++ b/packages/flutter_tools/lib/src/emulator.dart @@ -115,11 +115,13 @@ abstract class EmulatorDiscovery { } abstract class Emulator { - Emulator(this.id); + Emulator(this.id, this.hasConfig); final String id; - - String get name => id; + final bool hasConfig; + String get name; + String get manufacturer; + String get label; @override int get hashCode => id.hashCode; @@ -145,6 +147,8 @@ abstract class Emulator { for (Emulator emulator in emulators) { table.add([ emulator.name, + emulator.manufacturer, + emulator.label, emulator.id, ]); } diff --git a/packages/flutter_tools/test/emulator_test.dart b/packages/flutter_tools/test/emulator_test.dart index 784d3266a4..d7e394596f 100644 --- a/packages/flutter_tools/test/emulator_test.dart +++ b/packages/flutter_tools/test/emulator_test.dart @@ -19,9 +19,9 @@ void main() { }); testUsingContext('getEmulatorsById', () async { - final _MockEmulator emulator1 = new _MockEmulator('Nexus_5'); - final _MockEmulator emulator2 = new _MockEmulator('Nexus_5X_API_27_x86'); - final _MockEmulator emulator3 = new _MockEmulator('iOS Simulator'); + final _MockEmulator emulator1 = new _MockEmulator('Nexus_5', 'Nexus 5', 'Google', ''); + final _MockEmulator emulator2 = new _MockEmulator('Nexus_5X_API_27_x86', 'Nexus 5X', 'Google', ''); + final _MockEmulator emulator3 = new _MockEmulator('iOS Simulator', 'iOS Simulator', 'Apple', ''); final List emulators = [emulator1, emulator2, emulator3]; final EmulatorManager emulatorManager = new TestEmulatorManager(emulators); @@ -50,8 +50,15 @@ class TestEmulatorManager extends EmulatorManager { } class _MockEmulator extends Emulator { - _MockEmulator(String id) : super(id); + _MockEmulator(String id, this.name, this.manufacturer, this.label) : super(id, true); @override - void noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); + final String name; + + @override + final String manufacturer; + + @override + final String label; + }