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