android_emulator.dart 4.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// 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';
11
import '../base/file_system.dart';
12 13
import '../base/io.dart';
import '../base/process_manager.dart';
14
import '../device.dart';
15 16 17 18 19 20 21 22
import '../emulator.dart';
import 'android_sdk.dart';

class AndroidEmulators extends EmulatorDiscovery {
  @override
  bool get supportsPlatform => true;

  @override
23
  bool get canListAnything => androidWorkflow.canListEmulators;
24 25 26 27 28 29

  @override
  Future<List<Emulator>> get emulators async => getEmulatorAvds();
}

class 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 36
  // Android Studio uses the ID with underscores replaced with spaces
  // 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 44 45 46 47 48
  @override
  Category get category => Category.mobile;

  @override
  PlatformType get platformType => PlatformType.android;

49 50
  String _prop(String name) => _properties != null ? _properties[name] : null;

51
  @override
52 53
  Future<void> launch() async {
    final Future<void> launchResult =
54 55
        processManager.run(<String>[getEmulatorPath(), '-avd', id])
            .then((ProcessResult runResult) {
56
              if (runResult.exitCode != 0) {
57
                throw '${runResult.stdout}\n${runResult.stderr}'.trimRight();
58 59
              }
            });
60 61 62 63
    // 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... :-/
64
    return Future.any<void>(<Future<void>>[
65
      launchResult,
66
      Future<void>.delayed(const Duration(seconds: 3)),
67
    ]);
68
  }
69 70 71 72 73
}

/// Return the list of available emulator AVDs.
List<AndroidEmulator> getEmulatorAvds() {
  final String emulatorPath = getEmulatorPath(androidSdk);
74
  if (emulatorPath == null) {
75
    return <AndroidEmulator>[];
76
  }
Danny Tuppeny's avatar
Danny Tuppeny committed
77

78
  final String listAvdsOutput = processManager.runSync(<String>[emulatorPath, '-list-avds']).stdout;
79

80
  final List<AndroidEmulator> emulators = <AndroidEmulator>[];
81 82 83
  if (listAvdsOutput != null) {
    extractEmulatorAvdInfo(listAvdsOutput, emulators);
  }
84
  return emulators;
85 86 87
}

/// Parse the given `emulator -list-avds` output in [text], and fill out the given list
88 89
/// of emulators by reading information from the relevant ini files.
void 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
  }
}

95
AndroidEmulator _loadEmulatorInfo(String id) {
96
  id = id.trim();
97 98 99 100 101 102 103 104 105 106 107
  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());
108
          return AndroidEmulator(id, properties);
109 110
        }
      }
111 112 113
    }
  }

114
  return AndroidEmulator(id);
115 116
}

117
@visibleForTesting
118
Map<String, String> parseIniLines(List<String> contents) {
119 120 121
  final Map<String, String> results = <String, String>{};

  final Iterable<List<String>> properties = contents
122
      .map<String>((String l) => l.trim())
Danny Tuppeny's avatar
Danny Tuppeny committed
123 124 125 126 127
      // 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
128
      .map<List<String>>((String l) => l.split('='));
129 130 131

  for (List<String> property in properties) {
    results[property[0].trim()] = property[1].trim();
132
  }
133 134

  return results;
135
}