1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
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 '../device.dart';
import '../emulator.dart';
import 'android_sdk.dart';
class AndroidEmulators extends EmulatorDiscovery {
@override
bool get supportsPlatform => true;
@override
bool get canListAnything => androidWorkflow.canListEmulators;
@override
Future<List<Emulator>> get emulators async => getEmulatorAvds();
}
class AndroidEmulator extends Emulator {
AndroidEmulator(String id, [this._properties])
: super(id, _properties != null && _properties.isNotEmpty);
final Map<String, String> _properties;
// Android Studio uses the ID with underscores replaced with spaces
// for the name if displayname is not set so we do the same.
@override
String get name => _prop('avd.ini.displayname') ?? id.replaceAll('_', ' ').trim();
@override
String get manufacturer => _prop('hw.device.manufacturer');
@override
Category get category => Category.mobile;
@override
PlatformType get platformType => PlatformType.android;
String _prop(String name) => _properties != null ? _properties[name] : null;
@override
Future<void> launch() async {
final Future<void> launchResult = processUtils.run(
<String>[getEmulatorPath(), '-avd', id],
throwOnError: true,
);
// The emulator continues running on a successful launch, so if it hasn't
// quit within 3 seconds we assume that's a success and just return. This
// means that on a slow machine, a failure that takes more than three
// seconds won't be recognized as such... :-/
return Future.any<void>(<Future<void>>[
launchResult,
Future<void>.delayed(const Duration(seconds: 3)),
]);
}
}
/// Return the list of available emulator AVDs.
List<AndroidEmulator> getEmulatorAvds() {
final String emulatorPath = getEmulatorPath(androidSdk);
if (emulatorPath == null) {
return <AndroidEmulator>[];
}
final String listAvdsOutput = processUtils.runSync(
<String>[emulatorPath, '-list-avds']).stdout.trim();
final List<AndroidEmulator> emulators = <AndroidEmulator>[];
if (listAvdsOutput != null) {
extractEmulatorAvdInfo(listAvdsOutput, emulators);
}
return emulators;
}
/// Parse the given `emulator -list-avds` output in [text], and fill out the given list
/// of emulators by reading information from the relevant ini files.
void extractEmulatorAvdInfo(String text, List<AndroidEmulator> emulators) {
for (String id in text.trim().split('\n').where((String l) => l != '')) {
emulators.add(_loadEmulatorInfo(id));
}
}
AndroidEmulator _loadEmulatorInfo(String id) {
id = id.trim();
final String avdPath = getAvdPath();
if (avdPath != null) {
final File iniFile = fs.file(fs.path.join(avdPath, '$id.ini'));
if (iniFile.existsSync()) {
final Map<String, String> 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<String, String> properties =
parseIniLines(configFile.readAsLinesSync());
return AndroidEmulator(id, properties);
}
}
}
}
return AndroidEmulator(id);
}
@visibleForTesting
Map<String, String> parseIniLines(List<String> contents) {
final Map<String, String> results = <String, String>{};
final Iterable<List<String>> properties = contents
.map<String>((String l) => l.trim())
// Strip blank lines/comments
.where((String l) => l != '' && !l.startsWith('#'))
// Discard anything that isn't simple name=value
.where((String l) => l.contains('='))
// Split into name/value
.map<List<String>>((String l) => l.split('='));
for (List<String> property in properties) {
results[property[0].trim()] = property[1].trim();
}
return results;
}