adb.dart 6.03 KB
Newer Older
1 2 3 4 5 6
// Copyright 2016 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';

7
import '../base/io.dart';
8
import '../base/process.dart';
9
import '../globals.dart';
10 11 12 13 14 15

// https://android.googlesource.com/platform/system/core/+/android-4.4_r1/adb/OVERVIEW.TXT
// https://android.googlesource.com/platform/system/core/+/android-4.4_r1/adb/SERVICES.TXT

/// A wrapper around the `adb` command-line tool and the adb server.
class Adb {
Devon Carew's avatar
Devon Carew committed
16 17
  Adb(this.adbPath);

18 19 20 21 22 23
  static const int adbServerPort = 5037;

  final String adbPath;

  bool exists() {
    try {
24
      runCheckedSync(<String>[adbPath, 'version']);
25
      return true;
Devon Carew's avatar
Devon Carew committed
26
    } catch (exception) {
27 28 29 30 31 32 33 34 35 36
      return false;
    }
  }

  /// Return the full text from `adb version`. E.g.,
  ///
  ///     Android Debug Bridge version 1.0.32
  ///     Revision eac51f2bb6a8-android
  ///
  /// This method throws if `adb version` fails.
37
  String getVersion() => runCheckedSync(<String>[adbPath, 'version']);
38 39 40 41

  /// Starts the adb server. This will throw if there's an problem starting the
  /// adb server.
  void startServer() {
42
    runCheckedSync(<String>[adbPath, 'start-server']);
43 44 45 46 47
  }

  /// Stops the adb server. This will throw if there's an problem stopping the
  /// adb server.
  void killServer() {
48
    runCheckedSync(<String>[adbPath, 'kill-server']);
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
  }

  /// Ask the ADB server for its internal version number.
  Future<String> getServerVersion() {
    return _sendAdbServerCommand('host:version').then((String response) {
      _AdbServerResponse adbResponse = new _AdbServerResponse(response);
      if (adbResponse.isOkay)
        return adbResponse.message;
      throw adbResponse.message;
    });
  }

  /// Queries the adb server for the list of connected adb devices.
  Future<List<AdbDevice>> listDevices() async {
    String stringResponse = await _sendAdbServerCommand('host:devices-l');
    _AdbServerResponse response = new _AdbServerResponse(stringResponse);
    if (response.isFail)
      throw response.message;
    String message = response.message.trim();
    if (message.isEmpty)
      return <AdbDevice>[];
    return message.split('\n').map(
Devon Carew's avatar
Devon Carew committed
71 72
      (String deviceInfo) => new AdbDevice(deviceInfo)
    ).toList();
73 74 75 76 77 78
  }

  Future<String> _sendAdbServerCommand(String command) async {
    Socket socket = await Socket.connect(InternetAddress.LOOPBACK_IP_V4, adbServerPort);

    try {
79
      printTrace('--> $command');
80 81 82 83 84 85
      socket.add(_createAdbRequest(command));
      List<List<int>> result = await socket.toList();
      List<int> data = result.fold(<int>[], (List<int> previous, List<int> element) {
        return previous..addAll(element);
      });
      String stringResult = new String.fromCharCodes(data);
86
      printTrace('<-- ${stringResult.trim()}');
87 88 89 90 91 92 93 94 95 96 97 98 99
      return stringResult;
    } finally {
      socket.destroy();
    }
  }
}

class AdbDevice {
  AdbDevice(String deviceInfo) {
    // 'TA95000FQA	device'
    // 'TA95000FQA             device usb:340787200X product:peregrine_retus model:XT1045 device:peregrine'
    // '015d172c98400a03       device usb:340787200X product:nakasi model:Nexus_7 device:grouper'

100
    Match match = kDeviceRegex.firstMatch(deviceInfo);
101 102 103 104 105 106 107 108 109 110 111 112 113
    id = match[1];
    status = match[2];

    String rest = match[3];
    if (rest != null && rest.isNotEmpty) {
      rest = rest.trim();
      for (String data in rest.split(' ')) {
        if (data.contains(':')) {
          List<String> fields = data.split(':');
          _info[fields[0]] = fields[1];
        }
      }
    }
114 115

    if (modelID != null)
116
      modelID = cleanAdbDeviceName(modelID);
117 118
  }

119
  static final RegExp kDeviceRegex = new RegExp(r'^(\S+)\s+(\S+)(.*)');
Devon Carew's avatar
Devon Carew committed
120 121 122 123 124 125 126 127 128

  /// Always non-null; something like `TA95000FQA`.
  String id;

  /// device, offline, unauthorized.
  String status;

  final Map<String, String> _info = <String, String>{};

129 130
  bool get isAvailable => status == 'device';

131 132 133 134
  bool get isUnauthorized => status == 'unauthorized';

  bool get isOffline => status == 'offline';

135 136 137
  /// Device model; can be null. `XT1045`, `Nexus_7`
  String get modelID => _info['model'];

138
  set modelID(String value) {
139 140 141
    _info['model'] = value;
  }

142 143 144 145 146 147
  /// Device code name; can be null. `peregrine`, `grouper`
  String get deviceCodeName => _info['device'];

  /// Device product; can be null. `peregrine_retus`, `nakasi`
  String get productID => _info['product'];

148
  @override
Devon Carew's avatar
Devon Carew committed
149 150 151 152 153 154 155 156
  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other is! AdbDevice)
      return false;
    final AdbDevice typedOther = other;
    return id == typedOther.id;
  }
157

158
  @override
159 160
  int get hashCode => id.hashCode;

161
  @override
162 163 164 165 166 167 168 169 170
  String toString() {
    if (modelID == null) {
      return '$id ($status)';
    } else {
      return '$id ($status) - $modelID';
    }
  }
}

171 172
final RegExp _whitespaceRegex = new RegExp(r'\s+');

173 174 175 176 177 178 179
String cleanAdbDeviceName(String name) {
  // Some emulators use `___` in the name as separators.
  name = name.replaceAll('___', ', ');

  // Convert `Nexus_7` / `Nexus_5X` style names to `Nexus 7` ones.
  name = name.replaceAll('_', ' ');

180 181
  name = name.replaceAll(_whitespaceRegex, ' ').trim();

182 183 184
  return name;
}

185 186 187 188 189
List<int> _createAdbRequest(String payload) {
  List<int> data = payload.codeUnits;

  // A 4-byte hexadecimal string giving the length of the payload.
  String prefix = data.length.toRadixString(16).padLeft(4, '0');
Devon Carew's avatar
Devon Carew committed
190
  List<int> result = <int>[];
191 192 193 194 195 196
  result.addAll(prefix.codeUnits);
  result.addAll(data);
  return result;
}

class _AdbServerResponse {
Devon Carew's avatar
Devon Carew committed
197
  _AdbServerResponse(String text, { bool noStatus: false }) {
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
    if (noStatus) {
      message = text;
    } else {
      status = text.substring(0, 4);
      message = text.substring(4);
    }

    // Instead of pulling the hex length out of the response (`000C`), we depend
    // on the incoming text being the full packet.
    if (message.isNotEmpty) {
      // Skip over the 4 byte hex length (`000C`).
      message = message.substring(4);
    }
  }

Devon Carew's avatar
Devon Carew committed
213 214 215
  String status;
  String message;

216 217 218 219
  bool get isOkay => status == 'OKAY';

  bool get isFail => status == 'FAIL';
}