mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
add a wrapper around the adb command
This commit is contained in:
parent
548dbf0870
commit
677a80c16b
223
packages/flutter_tools/lib/src/android/adb.dart
Normal file
223
packages/flutter_tools/lib/src/android/adb.dart
Normal file
@ -0,0 +1,223 @@
|
||||
// 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';
|
||||
import 'dart:io';
|
||||
|
||||
import '../base/logging.dart';
|
||||
import '../base/process.dart';
|
||||
|
||||
// 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 {
|
||||
static const int adbServerPort = 5037;
|
||||
|
||||
final String adbPath;
|
||||
|
||||
Adb(this.adbPath);
|
||||
|
||||
bool exists() {
|
||||
try {
|
||||
runCheckedSync([adbPath, 'version']);
|
||||
return true;
|
||||
} catch (_) {
|
||||
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.
|
||||
String getVersion() => runCheckedSync([adbPath, 'version']);
|
||||
|
||||
/// Starts the adb server. This will throw if there's an problem starting the
|
||||
/// adb server.
|
||||
void startServer() {
|
||||
runCheckedSync([adbPath, 'start-server']);
|
||||
}
|
||||
|
||||
/// Stops the adb server. This will throw if there's an problem stopping the
|
||||
/// adb server.
|
||||
void killServer() {
|
||||
runCheckedSync([adbPath, 'kill-server']);
|
||||
}
|
||||
|
||||
/// 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(
|
||||
(String deviceInfo) => new AdbDevice(deviceInfo)).toList();
|
||||
}
|
||||
|
||||
/// Listen to device activations and deactivations via the asb server's
|
||||
/// 'track-devices' command. Call cancel on the returned stream to stop
|
||||
/// listening.
|
||||
Stream<List<AdbDevice>> trackDevices() {
|
||||
StreamController<List<AdbDevice>> controller;
|
||||
Socket socket;
|
||||
bool isFirstNotification = true;
|
||||
|
||||
controller = new StreamController(
|
||||
onListen: () async {
|
||||
socket = await Socket.connect(InternetAddress.LOOPBACK_IP_V4, adbServerPort);
|
||||
logging.fine('--> host:track-devices');
|
||||
socket.add(_createAdbRequest('host:track-devices'));
|
||||
socket.listen((List<int> data) {
|
||||
String stringResult = new String.fromCharCodes(data);
|
||||
logging.fine('<-- ${stringResult.trim()}');
|
||||
_AdbServerResponse response = new _AdbServerResponse(
|
||||
stringResult,
|
||||
noStatus: !isFirstNotification
|
||||
);
|
||||
|
||||
String devicesText = response.message.trim();
|
||||
isFirstNotification = false;
|
||||
|
||||
if (devicesText.isEmpty) {
|
||||
controller.add(<AdbDevice>[]);
|
||||
} else {
|
||||
controller.add(devicesText.split('\n').map((String deviceInfo) {
|
||||
return new AdbDevice(deviceInfo);
|
||||
}).toList());
|
||||
}
|
||||
});
|
||||
socket.done.then((_) => controller.close());
|
||||
},
|
||||
onCancel: () => socket.destroy()
|
||||
);
|
||||
|
||||
return controller.stream;
|
||||
}
|
||||
|
||||
Future<String> _sendAdbServerCommand(String command) async {
|
||||
Socket socket = await Socket.connect(InternetAddress.LOOPBACK_IP_V4, adbServerPort);
|
||||
|
||||
try {
|
||||
logging.fine('--> $command');
|
||||
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);
|
||||
logging.fine('<-- ${stringResult.trim()}');
|
||||
return stringResult;
|
||||
} finally {
|
||||
socket.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AdbDevice {
|
||||
static final RegExp deviceRegex = new RegExp(r'^(\S+)\s+(\S+)(.*)');
|
||||
|
||||
/// Always non-null; something like `TA95000FQA`.
|
||||
String id;
|
||||
|
||||
/// device, offline, unauthorized.
|
||||
String status;
|
||||
|
||||
Map<String, String> _info = <String, String>{};
|
||||
|
||||
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'
|
||||
|
||||
Match match = deviceRegex.firstMatch(deviceInfo);
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool get isAvailable => status == 'device';
|
||||
|
||||
/// Device model; can be null. `XT1045`, `Nexus_7`
|
||||
String get modelID => _info['model'];
|
||||
|
||||
/// 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'];
|
||||
|
||||
operator==(other) => other is AdbDevice && other.id == id;
|
||||
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
String toString() {
|
||||
if (modelID == null) {
|
||||
return '$id ($status)';
|
||||
} else {
|
||||
return '$id ($status) - $modelID';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
List<int> result = new List<int>();
|
||||
result.addAll(prefix.codeUnits);
|
||||
result.addAll(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
class _AdbServerResponse {
|
||||
String status;
|
||||
String message;
|
||||
|
||||
_AdbServerResponse(String text, {bool noStatus: false}) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool get isOkay => status == 'OKAY';
|
||||
|
||||
bool get isFail => status == 'FAIL';
|
||||
}
|
Loading…
Reference in New Issue
Block a user