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