mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
add device notifications to the daemon command
This commit is contained in:
parent
477530f309
commit
8bb8e1d9e7
@ -6,21 +6,14 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import '../android/adb.dart';
|
||||||
import '../base/logging.dart';
|
import '../base/logging.dart';
|
||||||
|
import '../device.dart';
|
||||||
import '../runner/flutter_command.dart';
|
import '../runner/flutter_command.dart';
|
||||||
import 'start.dart';
|
import 'start.dart';
|
||||||
import 'stop.dart';
|
import 'stop.dart';
|
||||||
|
|
||||||
const String protocolVersion = '0.0.1';
|
const String protocolVersion = '0.0.2';
|
||||||
|
|
||||||
/// A @domain annotation.
|
|
||||||
const String domain = 'domain';
|
|
||||||
|
|
||||||
/// A domain @command annotation.
|
|
||||||
const String command = 'command';
|
|
||||||
|
|
||||||
// TODO: Create a `device` domain in order to list devices and fire events when
|
|
||||||
// devices are added or removed.
|
|
||||||
|
|
||||||
/// A server process command. This command will start up a long-lived server.
|
/// A server process command. This command will start up a long-lived server.
|
||||||
/// It reads JSON-RPC based commands from stdin, executes them, and returns
|
/// It reads JSON-RPC based commands from stdin, executes them, and returns
|
||||||
@ -32,7 +25,6 @@ class DaemonCommand extends FlutterCommand {
|
|||||||
final String name = 'daemon';
|
final String name = 'daemon';
|
||||||
final String description = 'Run a persistent, JSON-RPC based server to communicate with devices.';
|
final String description = 'Run a persistent, JSON-RPC based server to communicate with devices.';
|
||||||
|
|
||||||
@override
|
|
||||||
Future<int> runInProject() async {
|
Future<int> runInProject() async {
|
||||||
print('Starting device daemon...');
|
print('Starting device daemon...');
|
||||||
|
|
||||||
@ -48,90 +40,101 @@ class DaemonCommand extends FlutterCommand {
|
|||||||
await downloadApplicationPackagesAndConnectToDevices();
|
await downloadApplicationPackagesAndConnectToDevices();
|
||||||
|
|
||||||
Daemon daemon = new Daemon(commandStream, (Map command) {
|
Daemon daemon = new Daemon(commandStream, (Map command) {
|
||||||
stdout.writeln('[${JSON.encode(command)}]');
|
stdout.writeln('[${JSON.encode(command, toEncodable: _jsonEncodeObject)}]');
|
||||||
}, daemonCommand: this);
|
}, daemonCommand: this);
|
||||||
|
|
||||||
return await daemon.onExit;
|
return await daemon.onExit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dynamic _jsonEncodeObject(dynamic object) {
|
||||||
|
if (object is Device)
|
||||||
|
return _deviceToMap(object);
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef void DispatchComand(Map command);
|
typedef void DispatchComand(Map<String, dynamic> command);
|
||||||
|
|
||||||
typedef Future<dynamic> CommandHandler(dynamic args);
|
typedef Future<dynamic> CommandHandler(dynamic args);
|
||||||
|
|
||||||
class Daemon {
|
class Daemon {
|
||||||
final DispatchComand sendCommand;
|
|
||||||
final DaemonCommand daemonCommand;
|
|
||||||
|
|
||||||
final Completer<int> _onExitCompleter = new Completer();
|
|
||||||
final Map<String, Domain> _domains = {};
|
|
||||||
|
|
||||||
Daemon(Stream<Map> commandStream, this.sendCommand, {this.daemonCommand}) {
|
Daemon(Stream<Map> commandStream, this.sendCommand, {this.daemonCommand}) {
|
||||||
// Set up domains.
|
// Set up domains.
|
||||||
_registerDomain(new DaemonDomain(this));
|
_registerDomain(new DaemonDomain(this));
|
||||||
_registerDomain(new AppDomain(this));
|
_registerDomain(new AppDomain(this));
|
||||||
|
_registerDomain(new DeviceDomain(this));
|
||||||
|
|
||||||
// Start listening.
|
// Start listening.
|
||||||
commandStream.listen(
|
commandStream.listen(
|
||||||
(Map command) => _handleCommand(command),
|
(Map request) => _handleRequest(request),
|
||||||
onDone: () => _onExitCompleter.complete(0)
|
onDone: () => _onExitCompleter.complete(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final DispatchComand sendCommand;
|
||||||
|
final DaemonCommand daemonCommand;
|
||||||
|
|
||||||
|
final Completer<int> _onExitCompleter = new Completer<int>();
|
||||||
|
final Map<String, Domain> _domainMap = <String, Domain>{};
|
||||||
|
|
||||||
void _registerDomain(Domain domain) {
|
void _registerDomain(Domain domain) {
|
||||||
_domains[domain.name] = domain;
|
_domainMap[domain.name] = domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> get onExit => _onExitCompleter.future;
|
Future<int> get onExit => _onExitCompleter.future;
|
||||||
|
|
||||||
void _handleCommand(Map command) {
|
void _handleRequest(Map request) {
|
||||||
// {id, event, params}
|
// {id, method, params}
|
||||||
var id = command['id'];
|
|
||||||
|
// [id] is an opaque type to us.
|
||||||
|
dynamic id = request['id'];
|
||||||
|
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
logging.severe('no id for command: $command');
|
logging.severe('no id for request: $request');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String event = command['event'];
|
String method = request['method'];
|
||||||
if (event.indexOf('.') == -1)
|
if (method.indexOf('.') == -1)
|
||||||
throw 'command not understood: $event';
|
throw 'method not understood: $method';
|
||||||
|
|
||||||
String prefix = event.substring(0, event.indexOf('.'));
|
String prefix = method.substring(0, method.indexOf('.'));
|
||||||
String name = event.substring(event.indexOf('.') + 1);
|
String name = method.substring(method.indexOf('.') + 1);
|
||||||
if (_domains[prefix] == null)
|
if (_domainMap[prefix] == null)
|
||||||
throw 'no domain for command: $command';
|
throw 'no domain for method: $method';
|
||||||
|
|
||||||
_domains[prefix].handleEvent(name, id, command['params']);
|
_domainMap[prefix].handleCommand(name, id, request['params']);
|
||||||
} catch (error, trace) {
|
} catch (error, trace) {
|
||||||
_send({'id': id, 'error': _toJsonable(error)});
|
_send({'id': id, 'error': _toJsonable(error)});
|
||||||
logging.warning('error handling ${command['event']}', error, trace);
|
logging.warning('error handling $request', error, trace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _send(Map map) => sendCommand(map);
|
void _send(Map map) => sendCommand(map);
|
||||||
|
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
|
_domainMap.values.forEach((Domain domain) => domain.dispose());
|
||||||
if (!_onExitCompleter.isCompleted)
|
if (!_onExitCompleter.isCompleted)
|
||||||
_onExitCompleter.complete(0);
|
_onExitCompleter.complete(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Domain {
|
abstract class Domain {
|
||||||
|
Domain(this.daemon, this.name);
|
||||||
|
|
||||||
final Daemon daemon;
|
final Daemon daemon;
|
||||||
final String name;
|
final String name;
|
||||||
final Map<String, CommandHandler> _handlers = {};
|
final Map<String, CommandHandler> _handlers = {};
|
||||||
|
|
||||||
Domain(this.daemon, this.name);
|
|
||||||
|
|
||||||
void registerHandler(String name, CommandHandler handler) {
|
void registerHandler(String name, CommandHandler handler) {
|
||||||
_handlers[name] = handler;
|
_handlers[name] = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
String toString() => name;
|
String toString() => name;
|
||||||
|
|
||||||
void handleEvent(String name, dynamic id, dynamic args) {
|
void handleCommand(String name, dynamic id, dynamic args) {
|
||||||
new Future.sync(() {
|
new Future.sync(() {
|
||||||
if (_handlers.containsKey(name))
|
if (_handlers.containsKey(name))
|
||||||
return _handlers[name](args);
|
return _handlers[name](args);
|
||||||
@ -148,24 +151,30 @@ abstract class Domain {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendEvent(String name, [dynamic args]) {
|
||||||
|
Map<String, dynamic> map = { 'method': name };
|
||||||
|
if (args != null)
|
||||||
|
map['params'] = _toJsonable(args);
|
||||||
|
_send(map);
|
||||||
|
}
|
||||||
|
|
||||||
void _send(Map map) => daemon._send(map);
|
void _send(Map map) => daemon._send(map);
|
||||||
|
|
||||||
|
void dispose() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This domain responds to methods like [version] and [shutdown].
|
/// This domain responds to methods like [version] and [shutdown].
|
||||||
@domain
|
|
||||||
class DaemonDomain extends Domain {
|
class DaemonDomain extends Domain {
|
||||||
DaemonDomain(Daemon daemon) : super(daemon, 'daemon') {
|
DaemonDomain(Daemon daemon) : super(daemon, 'daemon') {
|
||||||
registerHandler('version', version);
|
registerHandler('version', version);
|
||||||
registerHandler('shutdown', shutdown);
|
registerHandler('shutdown', shutdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
@command
|
Future<String> version(dynamic args) {
|
||||||
Future<dynamic> version(dynamic args) {
|
|
||||||
return new Future.value(protocolVersion);
|
return new Future.value(protocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@command
|
Future shutdown(dynamic args) {
|
||||||
Future<dynamic> shutdown(dynamic args) {
|
|
||||||
Timer.run(() => daemon.shutdown());
|
Timer.run(() => daemon.shutdown());
|
||||||
return new Future.value();
|
return new Future.value();
|
||||||
}
|
}
|
||||||
@ -175,14 +184,12 @@ class DaemonDomain extends Domain {
|
|||||||
///
|
///
|
||||||
/// It'll be extended to fire events for when applications start, stop, and
|
/// It'll be extended to fire events for when applications start, stop, and
|
||||||
/// log data.
|
/// log data.
|
||||||
@domain
|
|
||||||
class AppDomain extends Domain {
|
class AppDomain extends Domain {
|
||||||
AppDomain(Daemon daemon) : super(daemon, 'app') {
|
AppDomain(Daemon daemon) : super(daemon, 'app') {
|
||||||
registerHandler('start', start);
|
registerHandler('start', start);
|
||||||
registerHandler('stopAll', stopAll);
|
registerHandler('stopAll', stopAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
@command
|
|
||||||
Future<dynamic> start(dynamic args) {
|
Future<dynamic> start(dynamic args) {
|
||||||
// TODO: Add the ability to pass args: target, http, checked
|
// TODO: Add the ability to pass args: target, http, checked
|
||||||
StartCommand startComand = new StartCommand();
|
StartCommand startComand = new StartCommand();
|
||||||
@ -190,7 +197,6 @@ class AppDomain extends Domain {
|
|||||||
return startComand.runInProject().then((_) => null);
|
return startComand.runInProject().then((_) => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@command
|
|
||||||
Future<bool> stopAll(dynamic args) {
|
Future<bool> stopAll(dynamic args) {
|
||||||
StopCommand stopCommand = new StopCommand();
|
StopCommand stopCommand = new StopCommand();
|
||||||
stopCommand.inheritFromParent(daemon.daemonCommand);
|
stopCommand.inheritFromParent(daemon.daemonCommand);
|
||||||
@ -198,8 +204,138 @@ class AppDomain extends Domain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This domain lets callers list and monitor connected devices.
|
||||||
|
///
|
||||||
|
/// It exports a `getDevices()` call, as well as firing `device.added`,
|
||||||
|
/// `device.removed`, and `device.changed` events.
|
||||||
|
class DeviceDomain extends Domain {
|
||||||
|
DeviceDomain(Daemon daemon) : super(daemon, 'device') {
|
||||||
|
registerHandler('getDevices', getDevices);
|
||||||
|
|
||||||
|
_androidDeviceDiscovery = new AndroidDeviceDiscovery();
|
||||||
|
_androidDeviceDiscovery.onAdded.listen((Device device) {
|
||||||
|
sendEvent('device.added', _deviceToMap(device));
|
||||||
|
});
|
||||||
|
_androidDeviceDiscovery.onRemoved.listen((Device device) {
|
||||||
|
sendEvent('device.removed', _deviceToMap(device));
|
||||||
|
});
|
||||||
|
_androidDeviceDiscovery.onChanged.listen((Device device) {
|
||||||
|
sendEvent('device.changed', _deviceToMap(device));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidDeviceDiscovery _androidDeviceDiscovery;
|
||||||
|
|
||||||
|
Future<List<Device>> getDevices(dynamic args) {
|
||||||
|
List<Device> devices = <Device>[];
|
||||||
|
devices.addAll(_androidDeviceDiscovery.getDevices());
|
||||||
|
return new Future.value(devices);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_androidDeviceDiscovery.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AndroidDeviceDiscovery {
|
||||||
|
AndroidDeviceDiscovery() {
|
||||||
|
_initAdb();
|
||||||
|
|
||||||
|
if (_adb != null) {
|
||||||
|
_subscription = _adb.trackDevices().listen(_handleNewDevices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adb _adb;
|
||||||
|
StreamSubscription _subscription;
|
||||||
|
Map<String, AndroidDevice> _devices = new Map<String, AndroidDevice>();
|
||||||
|
|
||||||
|
StreamController<Device> addedController = new StreamController<Device>.broadcast();
|
||||||
|
StreamController<Device> removedController = new StreamController<Device>.broadcast();
|
||||||
|
StreamController<Device> changedController = new StreamController<Device>.broadcast();
|
||||||
|
|
||||||
|
List<Device> getDevices() => _devices.values.toList();
|
||||||
|
|
||||||
|
Stream<Device> get onAdded => addedController.stream;
|
||||||
|
Stream<Device> get onRemoved => removedController.stream;
|
||||||
|
Stream<Device> get onChanged => changedController.stream;
|
||||||
|
|
||||||
|
void _initAdb() {
|
||||||
|
if (_adb == null) {
|
||||||
|
_adb = new Adb(AndroidDevice.getAdbPath());
|
||||||
|
if (!_adb.exists())
|
||||||
|
_adb = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleNewDevices(List<AdbDevice> newDevices) {
|
||||||
|
List<AndroidDevice> currentDevices = new List.from(getDevices());
|
||||||
|
|
||||||
|
for (AdbDevice device in newDevices) {
|
||||||
|
AndroidDevice androidDevice = _devices[device.id];
|
||||||
|
|
||||||
|
if (androidDevice == null) {
|
||||||
|
// device added
|
||||||
|
androidDevice = new AndroidDevice(
|
||||||
|
id: device.id,
|
||||||
|
productID: device.productID,
|
||||||
|
modelID: device.modelID,
|
||||||
|
deviceCodeName: device.deviceCodeName,
|
||||||
|
connected: device.isAvailable
|
||||||
|
);
|
||||||
|
_devices[androidDevice.id] = androidDevice;
|
||||||
|
addedController.add(androidDevice);
|
||||||
|
} else {
|
||||||
|
currentDevices.remove(androidDevice);
|
||||||
|
|
||||||
|
// check state
|
||||||
|
if (androidDevice.isConnected() != device.isAvailable) {
|
||||||
|
androidDevice.setConnected(device.isAvailable);
|
||||||
|
changedController.add(androidDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// device removed
|
||||||
|
for (AndroidDevice device in currentDevices) {
|
||||||
|
_devices.remove(device.id);
|
||||||
|
|
||||||
|
// I don't know the purpose of this cache or if it's a good idea. We should
|
||||||
|
// probably have a DeviceManager singleton class to coordinate known devices
|
||||||
|
// and different device discovery mechanisms.
|
||||||
|
Device.removeFromCache(device.id);
|
||||||
|
|
||||||
|
removedController.add(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_subscription?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _deviceToMap(Device device) {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'id': device.id,
|
||||||
|
'platform': _enumToString(device.platform),
|
||||||
|
'available': device.isConnected()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take an enum value and get the best string representation of that.
|
||||||
|
///
|
||||||
|
/// toString() on enums returns 'EnumType.enumName'.
|
||||||
|
String _enumToString(dynamic enumValue) {
|
||||||
|
String str = '$enumValue';
|
||||||
|
if (str.contains('.'))
|
||||||
|
return str.substring(str.indexOf('.') + 1);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
dynamic _toJsonable(dynamic obj) {
|
dynamic _toJsonable(dynamic obj) {
|
||||||
if (obj is String || obj is int || obj is bool || obj is Map || obj is List || obj == null)
|
if (obj is String || obj is int || obj is bool || obj is Map || obj is List || obj == null)
|
||||||
return obj;
|
return obj;
|
||||||
|
if (obj is Device)
|
||||||
|
return obj;
|
||||||
return '$obj';
|
return '$obj';
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,14 @@ abstract class Device {
|
|||||||
return _deviceCache.putIfAbsent(id, () => constructor(id));
|
return _deviceCache.putIfAbsent(id, () => constructor(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void removeFromCache(String id) {
|
||||||
|
_deviceCache.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
Device._(this.id);
|
Device._(this.id);
|
||||||
|
|
||||||
|
String get name;
|
||||||
|
|
||||||
/// Install an app package on the current device
|
/// Install an app package on the current device
|
||||||
bool installApp(ApplicationPackage app);
|
bool installApp(ApplicationPackage app);
|
||||||
|
|
||||||
@ -532,20 +538,25 @@ class AndroidDevice extends Device {
|
|||||||
String modelID;
|
String modelID;
|
||||||
String deviceCodeName;
|
String deviceCodeName;
|
||||||
|
|
||||||
|
bool _connected;
|
||||||
String _adbPath;
|
String _adbPath;
|
||||||
String get adbPath => _adbPath;
|
String get adbPath => _adbPath;
|
||||||
bool _hasAdb = false;
|
bool _hasAdb = false;
|
||||||
bool _hasValidAndroid = false;
|
bool _hasValidAndroid = false;
|
||||||
|
|
||||||
factory AndroidDevice(
|
factory AndroidDevice({
|
||||||
{String id: null,
|
String id: null,
|
||||||
String productID: null,
|
String productID: null,
|
||||||
String modelID: null,
|
String modelID: null,
|
||||||
String deviceCodeName: null}) {
|
String deviceCodeName: null,
|
||||||
|
bool connected
|
||||||
|
}) {
|
||||||
AndroidDevice device = Device._unique(id ?? defaultDeviceID, (String id) => new AndroidDevice._(id));
|
AndroidDevice device = Device._unique(id ?? defaultDeviceID, (String id) => new AndroidDevice._(id));
|
||||||
device.productID = productID;
|
device.productID = productID;
|
||||||
device.modelID = modelID;
|
device.modelID = modelID;
|
||||||
device.deviceCodeName = deviceCodeName;
|
device.deviceCodeName = deviceCodeName;
|
||||||
|
if (connected != null)
|
||||||
|
device._connected = connected;
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,7 +564,7 @@ class AndroidDevice extends Device {
|
|||||||
/// we don't have to rely on the test setup having adb available to it.
|
/// we don't have to rely on the test setup having adb available to it.
|
||||||
static List<AndroidDevice> getAttachedDevices([AndroidDevice mockAndroid]) {
|
static List<AndroidDevice> getAttachedDevices([AndroidDevice mockAndroid]) {
|
||||||
List<AndroidDevice> devices = [];
|
List<AndroidDevice> devices = [];
|
||||||
String adbPath = (mockAndroid != null) ? mockAndroid.adbPath : _getAdbPath();
|
String adbPath = (mockAndroid != null) ? mockAndroid.adbPath : getAdbPath();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runCheckedSync([adbPath, 'version']);
|
runCheckedSync([adbPath, 'version']);
|
||||||
@ -623,7 +634,7 @@ class AndroidDevice extends Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AndroidDevice._(id) : super._(id) {
|
AndroidDevice._(id) : super._(id) {
|
||||||
_adbPath = _getAdbPath();
|
_adbPath = getAdbPath();
|
||||||
_hasAdb = _checkForAdb();
|
_hasAdb = _checkForAdb();
|
||||||
|
|
||||||
// Checking for [minApiName] only needs to be done if we are starting an
|
// Checking for [minApiName] only needs to be done if we are starting an
|
||||||
@ -655,7 +666,7 @@ class AndroidDevice extends Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _getAdbPath() {
|
static String getAdbPath() {
|
||||||
if (Platform.environment.containsKey('ANDROID_HOME')) {
|
if (Platform.environment.containsKey('ANDROID_HOME')) {
|
||||||
String androidHomeDir = Platform.environment['ANDROID_HOME'];
|
String androidHomeDir = Platform.environment['ANDROID_HOME'];
|
||||||
String adbPath1 = path.join(androidHomeDir, 'sdk', 'platform-tools', 'adb');
|
String adbPath1 = path.join(androidHomeDir, 'sdk', 'platform-tools', 'adb');
|
||||||
@ -782,6 +793,8 @@ class AndroidDevice extends Device {
|
|||||||
return CryptoUtils.bytesToHex(sha1.close());
|
return CryptoUtils.bytesToHex(sha1.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get name => modelID;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isAppInstalled(ApplicationPackage app) {
|
bool isAppInstalled(ApplicationPackage app) {
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
@ -992,8 +1005,11 @@ class AndroidDevice extends Device {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
bool isConnected() => _connected != null ? _connected : _hasValidAndroid;
|
||||||
bool isConnected() => _hasValidAndroid;
|
|
||||||
|
void setConnected(bool value) {
|
||||||
|
_connected = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeviceStore {
|
class DeviceStore {
|
||||||
|
@ -26,9 +26,9 @@ defineTests() {
|
|||||||
StreamController<Map> responses = new StreamController();
|
StreamController<Map> responses = new StreamController();
|
||||||
daemon = new Daemon(
|
daemon = new Daemon(
|
||||||
commands.stream,
|
commands.stream,
|
||||||
(Map result) => responses.add(result)
|
(Map<String, dynamic> result) => responses.add(result)
|
||||||
);
|
);
|
||||||
commands.add({'id': 0, 'event': 'daemon.version'});
|
commands.add({'id': 0, 'method': 'daemon.version'});
|
||||||
Map response = await responses.stream.first;
|
Map response = await responses.stream.first;
|
||||||
expect(response['id'], 0);
|
expect(response['id'], 0);
|
||||||
expect(response['result'], isNotEmpty);
|
expect(response['result'], isNotEmpty);
|
||||||
@ -40,9 +40,9 @@ defineTests() {
|
|||||||
StreamController<Map> responses = new StreamController();
|
StreamController<Map> responses = new StreamController();
|
||||||
daemon = new Daemon(
|
daemon = new Daemon(
|
||||||
commands.stream,
|
commands.stream,
|
||||||
(Map result) => responses.add(result)
|
(Map<String, dynamic> result) => responses.add(result)
|
||||||
);
|
);
|
||||||
commands.add({'id': 0, 'event': 'daemon.shutdown'});
|
commands.add({'id': 0, 'method': 'daemon.shutdown'});
|
||||||
return daemon.onExit.then((int code) {
|
return daemon.onExit.then((int code) {
|
||||||
expect(code, 0);
|
expect(code, 0);
|
||||||
});
|
});
|
||||||
@ -56,7 +56,7 @@ defineTests() {
|
|||||||
StreamController<Map> responses = new StreamController();
|
StreamController<Map> responses = new StreamController();
|
||||||
daemon = new Daemon(
|
daemon = new Daemon(
|
||||||
commands.stream,
|
commands.stream,
|
||||||
(Map result) => responses.add(result),
|
(Map<String, dynamic> result) => responses.add(result),
|
||||||
daemonCommand: command
|
daemonCommand: command
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -71,10 +71,23 @@ defineTests() {
|
|||||||
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
|
when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
|
||||||
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
|
when(mockDevices.iOSSimulator.stopApp(any)).thenReturn(false);
|
||||||
|
|
||||||
commands.add({'id': 0, 'event': 'app.stopAll'});
|
commands.add({'id': 0, 'method': 'app.stopAll'});
|
||||||
Map response = await responses.stream.first;
|
Map response = await responses.stream.first;
|
||||||
expect(response['id'], 0);
|
expect(response['id'], 0);
|
||||||
expect(response['result'], true);
|
expect(response['result'], true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('device.getDevices', () async {
|
||||||
|
StreamController<Map> commands = new StreamController();
|
||||||
|
StreamController<Map> responses = new StreamController();
|
||||||
|
daemon = new Daemon(
|
||||||
|
commands.stream,
|
||||||
|
(Map<String, dynamic> result) => responses.add(result)
|
||||||
|
);
|
||||||
|
commands.add({'id': 0, 'method': 'device.getDevices'});
|
||||||
|
Map response = await responses.stream.first;
|
||||||
|
expect(response['id'], 0);
|
||||||
|
expect(response['result'], isList);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,15 @@ import 'dart:io';
|
|||||||
|
|
||||||
Process daemon;
|
Process daemon;
|
||||||
|
|
||||||
|
// To use, start from the console and enter:
|
||||||
|
// version: print version
|
||||||
|
// shutdown: terminate the server
|
||||||
|
// start: start an app
|
||||||
|
// stopAll: stop any running app
|
||||||
|
// devices: list devices
|
||||||
|
|
||||||
main() async {
|
main() async {
|
||||||
daemon = await Process.start('dart', ['bin/flutter_tools.dart', 'daemon']);
|
daemon = await Process.start('flutter', ['daemon']);
|
||||||
print('daemon process started, pid: ${daemon.pid}');
|
print('daemon process started, pid: ${daemon.pid}');
|
||||||
|
|
||||||
daemon.stdout
|
daemon.stdout
|
||||||
@ -20,13 +27,15 @@ main() async {
|
|||||||
stdout.write('> ');
|
stdout.write('> ');
|
||||||
stdin.transform(UTF8.decoder).transform(const LineSplitter()).listen((String line) {
|
stdin.transform(UTF8.decoder).transform(const LineSplitter()).listen((String line) {
|
||||||
if (line == 'version' || line == 'v') {
|
if (line == 'version' || line == 'v') {
|
||||||
_send({'event': 'daemon.version'});
|
_send({'method': 'daemon.version'});
|
||||||
} else if (line == 'shutdown' || line == 'q') {
|
} else if (line == 'shutdown' || line == 'q') {
|
||||||
_send({'event': 'daemon.shutdown'});
|
_send({'method': 'daemon.shutdown'});
|
||||||
} else if (line == 'start') {
|
} else if (line == 'start') {
|
||||||
_send({'event': 'app.start'});
|
_send({'method': 'app.start'});
|
||||||
} else if (line == 'stopAll') {
|
} else if (line == 'stopAll') {
|
||||||
_send({'event': 'app.stopAll'});
|
_send({'method': 'app.stopAll'});
|
||||||
|
} else if (line == 'devices') {
|
||||||
|
_send({'method': 'device.getDevices'});
|
||||||
} else {
|
} else {
|
||||||
print('command not understood: ${line}');
|
print('command not understood: ${line}');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user