forked from flutter/flutter
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdevice.dart
More file actions
411 lines (328 loc) · 12.3 KB
/
device.dart
File metadata and controls
411 lines (328 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
// Copyright 2015 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:math' as math;
import 'android/android_device.dart';
import 'application_package.dart';
import 'base/common.dart';
import 'base/context.dart';
import 'base/file_system.dart';
import 'base/port_scanner.dart';
import 'base/utils.dart';
import 'build_info.dart';
import 'globals.dart';
import 'ios/devices.dart';
import 'ios/simulators.dart';
DeviceManager get deviceManager => context[DeviceManager];
/// A class to get all available devices.
class DeviceManager {
/// Constructing DeviceManagers is cheap; they only do expensive work if some
/// of their methods are called.
DeviceManager() {
// Register the known discoverers.
_deviceDiscoverers.add(new AndroidDevices());
_deviceDiscoverers.add(new IOSDevices());
_deviceDiscoverers.add(new IOSSimulators());
}
final List<DeviceDiscovery> _deviceDiscoverers = <DeviceDiscovery>[];
String _specifiedDeviceId;
/// A user-specified device ID.
String get specifiedDeviceId {
if (_specifiedDeviceId == null || _specifiedDeviceId == 'all')
return null;
return _specifiedDeviceId;
}
set specifiedDeviceId(String id) {
_specifiedDeviceId = id;
}
/// True when the user has specified a single specific device.
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
/// True when the user has specified all devices by setting
/// specifiedDeviceId = 'all'.
bool get hasSpecifiedAllDevices => _specifiedDeviceId == 'all';
Stream<Device> getDevicesById(String deviceId) async* {
final List<Device> devices = await getAllConnectedDevices().toList();
deviceId = deviceId.toLowerCase();
bool exactlyMatchesDeviceId(Device device) =>
device.id.toLowerCase() == deviceId ||
device.name.toLowerCase() == deviceId;
bool startsWithDeviceId(Device device) =>
device.id.toLowerCase().startsWith(deviceId) ||
device.name.toLowerCase().startsWith(deviceId);
final Device exactMatch = devices.firstWhere(
exactlyMatchesDeviceId, orElse: () => null);
if (exactMatch != null) {
yield exactMatch;
return;
}
// Match on a id or name starting with [deviceId].
for (Device device in devices.where(startsWithDeviceId))
yield device;
}
/// Return the list of connected devices, filtered by any user-specified device id.
Stream<Device> getDevices() {
return hasSpecifiedDeviceId
? getDevicesById(specifiedDeviceId)
: getAllConnectedDevices();
}
/// Return the list of all connected devices.
Stream<Device> getAllConnectedDevices() {
return new Stream<Device>.fromIterable(_deviceDiscoverers
.where((DeviceDiscovery discoverer) => discoverer.supportsPlatform)
.expand((DeviceDiscovery discoverer) => discoverer.devices));
}
}
/// An abstract class to discover and enumerate a specific type of devices.
abstract class DeviceDiscovery {
bool get supportsPlatform;
/// Whether this device discovery is capable of listing any devices given the
/// current environment configuration.
bool get canListAnything;
List<Device> get devices;
}
/// A [DeviceDiscovery] implementation that uses polling to discover device adds
/// and removals.
abstract class PollingDeviceDiscovery extends DeviceDiscovery {
PollingDeviceDiscovery(this.name);
static const Duration _pollingDuration = const Duration(seconds: 4);
final String name;
ItemListNotifier<Device> _items;
Timer _timer;
List<Device> pollingGetDevices();
void startPolling() {
if (_timer == null) {
_items ??= new ItemListNotifier<Device>();
_timer = new Timer.periodic(_pollingDuration, (Timer timer) {
_items.updateWithNewList(pollingGetDevices());
});
}
}
void stopPolling() {
_timer?.cancel();
_timer = null;
}
@override
List<Device> get devices {
_items ??= new ItemListNotifier<Device>.from(pollingGetDevices());
return _items.items;
}
Stream<Device> get onAdded {
_items ??= new ItemListNotifier<Device>();
return _items.onAdded;
}
Stream<Device> get onRemoved {
_items ??= new ItemListNotifier<Device>();
return _items.onRemoved;
}
void dispose() => stopPolling();
@override
String toString() => '$name device discovery';
}
abstract class Device {
Device(this.id);
final String id;
String get name;
bool get supportsStartPaused => true;
/// Whether it is an emulated device running on localhost.
Future<bool> get isLocalEmulator;
/// Check if a version of the given app is already installed
Future<bool> isAppInstalled(ApplicationPackage app);
/// Check if the latest build of the [app] is already installed.
Future<bool> isLatestBuildInstalled(ApplicationPackage app);
/// Install an app package on the current device
Future<bool> installApp(ApplicationPackage app);
/// Uninstall an app package from the current device
Future<bool> uninstallApp(ApplicationPackage app);
/// Check if the device is supported by Flutter
bool isSupported();
// String meant to be displayed to the user indicating if the device is
// supported by Flutter, and, if not, why.
String supportMessage() => isSupported() ? "Supported" : "Unsupported";
/// The device's platform.
Future<TargetPlatform> get targetPlatform;
Future<String> get sdkNameAndVersion;
/// Get a log reader for this device.
/// If [app] is specified, this will return a log reader specific to that
/// application. Otherwise, a global log reader will be returned.
DeviceLogReader getLogReader({ApplicationPackage app});
/// Get the port forwarder for this device.
DevicePortForwarder get portForwarder;
Future<int> forwardPort(int devicePort, {int hostPort}) async {
try {
hostPort = await portForwarder
.forward(devicePort, hostPort: hostPort)
.timeout(const Duration(seconds: 60), onTimeout: () {
throw new ToolExit(
'Timeout while atempting to foward device port $devicePort');
});
printTrace('Forwarded host port $hostPort to device port $devicePort');
return hostPort;
} catch (e) {
throw new ToolExit(
'Unable to forward host port $hostPort to device port $devicePort: $e');
}
}
/// Clear the device's logs.
void clearLogs();
/// Start an app package on the current device.
///
/// [platformArgs] allows callers to pass platform-specific arguments to the
/// start call. The build mode is not used by all platforms.
Future<LaunchResult> startApp(
ApplicationPackage package,
BuildMode mode, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
String kernelPath,
bool prebuiltApplication: false,
bool applicationNeedsRebuild: false
});
/// Does this device implement support for hot reloading / restarting?
bool get supportsHotMode => true;
/// Stop an app package on the current device.
Future<bool> stopApp(ApplicationPackage app);
bool get supportsScreenshot => false;
Future<Null> takeScreenshot(File outputFile) => new Future<Null>.error('unimplemented');
/// Find the apps that are currently running on this device.
Future<List<DiscoveredApp>> discoverApps() =>
new Future<List<DiscoveredApp>>.value(<DiscoveredApp>[]);
@override
int get hashCode => id.hashCode;
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! Device)
return false;
return id == other.id;
}
@override
String toString() => name;
static Stream<String> descriptions(List<Device> devices) async* {
if (devices.isEmpty)
return;
// Extract device information
final List<List<String>> table = <List<String>>[];
for (Device device in devices) {
String supportIndicator = device.isSupported() ? '' : ' (unsupported)';
final TargetPlatform targetPlatform = await device.targetPlatform;
if (await device.isLocalEmulator) {
final String type = targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator';
supportIndicator += ' ($type)';
}
table.add(<String>[
device.name,
device.id,
'${getNameForTargetPlatform(targetPlatform)}',
'${await device.sdkNameAndVersion}$supportIndicator',
]);
}
// Calculate column widths
final List<int> indices = new List<int>.generate(table[0].length - 1, (int i) => i);
List<int> widths = indices.map((int i) => 0).toList();
for (List<String> row in table) {
widths = indices.map((int i) => math.max(widths[i], row[i].length)).toList();
}
// Join columns into lines of text
for (List<String> row in table) {
yield indices.map((int i) => row[i].padRight(widths[i])).join(' • ') + ' • ${row.last}';
}
}
static Future<Null> printDevices(List<Device> devices) async {
await descriptions(devices).forEach(printStatus);
}
}
class DebuggingOptions {
DebuggingOptions.enabled(this.buildMode, {
this.startPaused: false,
this.useTestFonts: false,
this.observatoryPort,
this.diagnosticPort
}) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildMode) :
debuggingEnabled = false,
useTestFonts = false,
startPaused = false,
observatoryPort = null,
diagnosticPort = null;
final bool debuggingEnabled;
final BuildMode buildMode;
final bool startPaused;
final bool useTestFonts;
final int observatoryPort;
final int diagnosticPort;
bool get hasObservatoryPort => observatoryPort != null;
/// Return the user specified observatory port. If that isn't available,
/// return [kDefaultObservatoryPort], or a port close to that one.
Future<int> findBestObservatoryPort() {
if (hasObservatoryPort)
return new Future<int>.value(observatoryPort);
return portScanner.findPreferredPort(observatoryPort ?? kDefaultObservatoryPort);
}
bool get hasDiagnosticPort => diagnosticPort != null;
/// Return the user specified diagnostic port. If that isn't available,
/// return [kDefaultDiagnosticPort], or a port close to that one.
Future<int> findBestDiagnosticPort() {
if (hasDiagnosticPort)
return new Future<int>.value(diagnosticPort);
return portScanner.findPreferredPort(diagnosticPort ?? kDefaultDiagnosticPort);
}
}
class LaunchResult {
LaunchResult.succeeded({ this.observatoryUri, this.diagnosticUri }) : started = true;
LaunchResult.failed() : started = false, observatoryUri = null, diagnosticUri = null;
bool get hasObservatory => observatoryUri != null;
final bool started;
final Uri observatoryUri;
final Uri diagnosticUri;
@override
String toString() {
final StringBuffer buf = new StringBuffer('started=$started');
if (observatoryUri != null)
buf.write(', observatory=$observatoryUri');
if (diagnosticUri != null)
buf.write(', diagnostic=$diagnosticUri');
return buf.toString();
}
}
class ForwardedPort {
ForwardedPort(this.hostPort, this.devicePort) : context = null;
ForwardedPort.withContext(this.hostPort, this.devicePort, this.context);
final int hostPort;
final int devicePort;
final dynamic context;
@override
String toString() => 'ForwardedPort HOST:$hostPort to DEVICE:$devicePort';
}
/// Forward ports from the host machine to the device.
abstract class DevicePortForwarder {
/// Returns a Future that completes with the current list of forwarded
/// ports for this device.
List<ForwardedPort> get forwardedPorts;
/// Forward [hostPort] on the host to [devicePort] on the device.
/// If [hostPort] is null, will auto select a host port.
/// Returns a Future that completes with the host port.
Future<int> forward(int devicePort, { int hostPort });
/// Stops forwarding [forwardedPort].
Future<Null> unforward(ForwardedPort forwardedPort);
}
/// Read the log for a particular device.
abstract class DeviceLogReader {
String get name;
/// A broadcast stream where each element in the string is a line of log output.
Stream<String> get logLines;
@override
String toString() => name;
/// Process ID of the app on the deivce.
int appPid;
}
/// Describes an app running on the device.
class DiscoveredApp {
DiscoveredApp(this.id, this.observatoryPort, this.diagnosticPort);
final String id;
final int observatoryPort;
final int diagnosticPort;
}