forked from flutter/flutter
/
device.dart
942 lines (792 loc) · 29.7 KB
/
device.dart
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
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
// Copyright 2014 The Flutter 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 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import 'android/android_device_discovery.dart';
import 'android/android_sdk.dart';
import 'android/android_workflow.dart';
import 'application_package.dart';
import 'artifacts.dart';
import 'base/config.dart';
import 'base/context.dart';
import 'base/file_system.dart';
import 'base/io.dart';
import 'base/logger.dart';
import 'base/platform.dart';
import 'base/user_messages.dart';
import 'base/utils.dart';
import 'build_info.dart';
import 'features.dart';
import 'fuchsia/fuchsia_device.dart';
import 'fuchsia/fuchsia_sdk.dart';
import 'fuchsia/fuchsia_workflow.dart';
import 'globals.dart' as globals;
import 'ios/devices.dart';
import 'ios/ios_workflow.dart';
import 'ios/simulators.dart';
import 'linux/linux_device.dart';
import 'macos/macos_device.dart';
import 'macos/xcode.dart';
import 'project.dart';
import 'tester/flutter_tester.dart';
import 'version.dart';
import 'web/web_device.dart';
import 'windows/windows_device.dart';
DeviceManager get deviceManager => context.get<DeviceManager>();
/// A description of the kind of workflow the device supports.
class Category {
const Category._(this.value);
static const Category web = Category._('web');
static const Category desktop = Category._('desktop');
static const Category mobile = Category._('mobile');
final String value;
@override
String toString() => value;
}
/// The platform sub-folder that a device type supports.
class PlatformType {
const PlatformType._(this.value);
static const PlatformType web = PlatformType._('web');
static const PlatformType android = PlatformType._('android');
static const PlatformType ios = PlatformType._('ios');
static const PlatformType linux = PlatformType._('linux');
static const PlatformType macos = PlatformType._('macos');
static const PlatformType windows = PlatformType._('windows');
static const PlatformType fuchsia = PlatformType._('fuchsia');
final String value;
@override
String toString() => value;
}
/// A class to get all available devices.
abstract class DeviceManager {
/// Constructing DeviceManagers is cheap; they only do expensive work if some
/// of their methods are called.
List<DeviceDiscovery> get deviceDiscoverers;
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';
Future<List<Device>> getDevicesById(String deviceId) async {
final List<Device> devices = await getAllConnectedDevices();
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) {
return <Device>[exactMatch];
}
// Match on a id or name starting with [deviceId].
return devices.where(startsWithDeviceId).toList();
}
/// Returns the list of connected devices, filtered by any user-specified device id.
Future<List<Device>> getDevices() {
return hasSpecifiedDeviceId
? getDevicesById(specifiedDeviceId)
: getAllConnectedDevices();
}
Iterable<DeviceDiscovery> get _platformDiscoverers {
return deviceDiscoverers.where((DeviceDiscovery discoverer) => discoverer.supportsPlatform);
}
/// Returns the list of all connected devices.
Future<List<Device>> getAllConnectedDevices() async {
final List<List<Device>> devices = await Future.wait<List<Device>>(<Future<List<Device>>>[
for (final DeviceDiscovery discoverer in _platformDiscoverers)
discoverer.devices,
]);
return devices.expand<Device>((List<Device> deviceList) => deviceList).toList();
}
/// Returns the list of all connected devices. Discards existing cache of devices.
Future<List<Device>> refreshAllConnectedDevices({ Duration timeout }) async {
final List<List<Device>> devices = await Future.wait<List<Device>>(<Future<List<Device>>>[
for (final DeviceDiscovery discoverer in _platformDiscoverers)
discoverer.discoverDevices(timeout: timeout),
]);
return devices.expand<Device>((List<Device> deviceList) => deviceList).toList();
}
/// Whether we're capable of listing any devices given the current environment configuration.
bool get canListAnything {
return _platformDiscoverers.any((DeviceDiscovery discoverer) => discoverer.canListAnything);
}
/// Get diagnostics about issues with any connected devices.
Future<List<String>> getDeviceDiagnostics() async {
return <String>[
for (final DeviceDiscovery discoverer in _platformDiscoverers)
...await discoverer.getDiagnostics(),
];
}
/// Find and return a list of devices based on the current project and environment.
///
/// Returns a list of devices specified by the user.
///
/// * If the user specified '-d all', then return all connected devices which
/// support the current project, except for fuchsia and web.
///
/// * If the user specified a device id, then do nothing as the list is already
/// filtered by [getDevices].
///
/// * If the user did not specify a device id and there is more than one
/// device connected, then filter out unsupported devices and prioritize
/// ephemeral devices.
Future<List<Device>> findTargetDevices(FlutterProject flutterProject) async {
List<Device> devices = await getDevices();
// Always remove web and fuchsia devices from `--all`. This setting
// currently requires devices to share a frontend_server and resident
// runnner instance. Both web and fuchsia require differently configured
// compilers, and web requires an entirely different resident runner.
if (hasSpecifiedAllDevices) {
devices = <Device>[
for (final Device device in devices)
if (await device.targetPlatform != TargetPlatform.fuchsia_arm64 &&
await device.targetPlatform != TargetPlatform.fuchsia_x64 &&
await device.targetPlatform != TargetPlatform.web_javascript)
device,
];
}
// If there is no specified device, the remove all devices which are not
// supported by the current application. For example, if there was no
// 'android' folder then don't attempt to launch with an Android device.
if (devices.length > 1 && !hasSpecifiedDeviceId) {
devices = <Device>[
for (final Device device in devices)
if (isDeviceSupportedForProject(device, flutterProject))
device,
];
} else if (devices.length == 1 &&
!hasSpecifiedDeviceId &&
!isDeviceSupportedForProject(devices.single, flutterProject)) {
// If there is only a single device but it is not supported, then return
// early.
return <Device>[];
}
// If there are still multiple devices and the user did not specify to run
// all, then attempt to prioritize ephemeral devices. For example, if the
// user only typed 'flutter run' and both an Android device and desktop
// device are availible, choose the Android device.
if (devices.length > 1 && !hasSpecifiedAllDevices) {
// Note: ephemeral is nullable for device types where this is not well
// defined.
if (devices.any((Device device) => device.ephemeral == true)) {
// if there is only one ephemeral device, get it
final List<Device> ephemeralDevices = devices
.where((Device device) => device.ephemeral == true)
.toList();
if (ephemeralDevices.length == 1){
devices = ephemeralDevices;
}
}
// If it was not able to prioritize a device. For example, if the user
// has two active Android devices running, then we request the user to
// choose one. If the user has two nonEphemeral devices running, we also
// request input to choose one.
if (devices.length > 1 && globals.stdio.stdinHasTerminal) {
globals.printStatus(globals.userMessages.flutterMultipleDevicesFound);
await Device.printDevices(devices);
final Device chosenDevice = await _chooseOneOfAvailableDevices(devices);
deviceManager.specifiedDeviceId = chosenDevice.id;
devices = <Device>[chosenDevice];
}
}
return devices;
}
Future<Device> _chooseOneOfAvailableDevices(List<Device> devices) async {
_displayDeviceOptions(devices);
final String userInput = await _readUserInput(devices.length);
return devices[int.parse(userInput)];
}
void _displayDeviceOptions(List<Device> devices) {
int count = 0;
for (final Device device in devices) {
globals.printStatus(userMessages.flutterChooseDevice(count, device.name, device.id));
count++;
}
}
Future<String> _readUserInput(int deviceCount) async {
globals.terminal.usesTerminalUi = true;
final String result = await globals.terminal.promptForCharInput(
<String>[ for (int i = 0; i < deviceCount; i++) '$i' ],
logger: globals.logger,
prompt: userMessages.flutterChooseOne);
return result;
}
/// Returns whether the device is supported for the project.
///
/// This exists to allow the check to be overridden for google3 clients.
bool isDeviceSupportedForProject(Device device, FlutterProject flutterProject) {
return device.isSupportedForProject(flutterProject);
}
}
class FlutterDeviceManager extends DeviceManager {
FlutterDeviceManager({
@required Logger logger,
@required Platform platform,
@required ProcessManager processManager,
@required FileSystem fileSystem,
@required AndroidSdk androidSdk,
@required FeatureFlags featureFlags,
@required IOSSimulatorUtils iosSimulatorUtils,
@required XCDevice xcDevice,
@required AndroidWorkflow androidWorkflow,
@required IOSWorkflow iosWorkflow,
@required FuchsiaWorkflow fuchsiaWorkflow,
@required FlutterVersion flutterVersion,
@required Config config,
@required Artifacts artifacts,
}) : deviceDiscoverers = <DeviceDiscovery>[
AndroidDevices(
logger: logger,
androidSdk: androidSdk,
androidWorkflow: androidWorkflow,
processManager: processManager,
),
IOSDevices(
platform: platform,
xcdevice: xcDevice,
iosWorkflow: iosWorkflow,
logger: logger,
),
IOSSimulators(
iosSimulatorUtils: iosSimulatorUtils,
),
FuchsiaDevices(
fuchsiaSdk: fuchsiaSdk,
logger: logger,
fuchsiaWorkflow: fuchsiaWorkflow,
platform: platform,
),
FlutterTesterDevices(
fileSystem: fileSystem,
flutterVersion: flutterVersion,
processManager: processManager,
config: config,
logger: logger,
artifacts: artifacts,
),
MacOSDevices(),
LinuxDevices(
platform: platform,
featureFlags: featureFlags,
),
WindowsDevices(),
WebDevices(
featureFlags: featureFlags,
fileSystem: fileSystem,
platform: platform,
processManager: processManager,
logger: logger,
),
];
@override
final List<DeviceDiscovery> deviceDiscoverers;
}
/// 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;
/// Return all connected devices, cached on subsequent calls.
Future<List<Device>> get devices;
/// Return all connected devices. Discards existing cache of devices.
Future<List<Device>> discoverDevices({ Duration timeout });
/// Gets a list of diagnostic messages pertaining to issues with any connected
/// devices (will be an empty list if there are no issues).
Future<List<String>> getDiagnostics() => Future<List<String>>.value(<String>[]);
}
/// A [DeviceDiscovery] implementation that uses polling to discover device adds
/// and removals.
abstract class PollingDeviceDiscovery extends DeviceDiscovery {
PollingDeviceDiscovery(this.name);
static const Duration _pollingInterval = Duration(seconds: 4);
static const Duration _pollingTimeout = Duration(seconds: 30);
final String name;
@protected
@visibleForTesting
ItemListNotifier<Device> deviceNotifier;
Timer _timer;
Future<List<Device>> pollingGetDevices({ Duration timeout });
Future<void> startPolling() async {
if (_timer == null) {
deviceNotifier ??= ItemListNotifier<Device>();
// Make initial population the default, fast polling timeout.
_timer = _initTimer(null);
}
}
Timer _initTimer(Duration pollingTimeout) {
return Timer(_pollingInterval, () async {
try {
final List<Device> devices = await pollingGetDevices(timeout: pollingTimeout);
deviceNotifier.updateWithNewList(devices);
} on TimeoutException {
globals.printTrace('Device poll timed out. Will retry.');
}
// Subsequent timeouts after initial population should wait longer.
_timer = _initTimer(_pollingTimeout);
});
}
Future<void> stopPolling() async {
_timer?.cancel();
_timer = null;
}
@override
Future<List<Device>> get devices async {
return _populateDevices();
}
@override
Future<List<Device>> discoverDevices({ Duration timeout }) async {
deviceNotifier = null;
return _populateDevices(timeout: timeout);
}
Future<List<Device>> _populateDevices({ Duration timeout }) async {
deviceNotifier ??= ItemListNotifier<Device>.from(await pollingGetDevices(timeout: timeout));
return deviceNotifier.items;
}
Stream<Device> get onAdded {
deviceNotifier ??= ItemListNotifier<Device>();
return deviceNotifier.onAdded;
}
Stream<Device> get onRemoved {
deviceNotifier ??= ItemListNotifier<Device>();
return deviceNotifier.onRemoved;
}
Future<void> dispose() async => await stopPolling();
@override
String toString() => '$name device discovery';
}
/// A device is a physical hardware that can run a flutter application.
///
/// This may correspond to a connected iOS or Android device, or represent
/// the host operating system in the case of Flutter Desktop.
abstract class Device {
Device(this.id, {
@required this.category,
@required this.platformType,
@required this.ephemeral,
});
final String id;
/// The [Category] for this device type.
final Category category;
/// The [PlatformType] for this device.
final PlatformType platformType;
/// Whether this is an ephemeral device.
final bool ephemeral;
String get name;
bool get supportsStartPaused => true;
/// Whether it is an emulated device running on localhost.
///
/// This may return `true` for certain physical Android devices, and is
/// generally only a best effort guess.
Future<bool> get isLocalEmulator;
/// The unique identifier for the emulator that corresponds to this device, or
/// null if it is not an emulator.
///
/// The ID returned matches that in the output of `flutter emulators`. Fetching
/// this name may require connecting to the device and if an error occurs null
/// will be returned.
Future<String> get emulatorId;
/// Whether this device can run the provided [buildMode].
///
/// For example, some emulator architectures cannot run profile or
/// release builds.
FutureOr<bool> supportsRuntimeMode(BuildMode buildMode) => true;
/// Whether the device is a simulator on a platform which supports hardware rendering.
// This is soft-deprecated since the logic is not correct expect for iOS simulators.
Future<bool> get supportsHardwareRendering async {
return true;
}
/// Whether the device is supported for the current project directory.
bool isSupportedForProject(FlutterProject flutterProject);
/// Check if a version of the given app is already installed.
///
/// Specify [userIdentifier] to check if installed for a particular user (Android only).
Future<bool> isAppInstalled(
covariant ApplicationPackage app, {
String userIdentifier,
});
/// Check if the latest build of the [app] is already installed.
Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app);
/// Install an app package on the current device.
///
/// Specify [userIdentifier] to install for a particular user (Android only).
Future<bool> installApp(
covariant ApplicationPackage app, {
String userIdentifier,
});
/// Uninstall an app package from the current device.
///
/// Specify [userIdentifier] to uninstall for a particular user,
/// defaults to all users (Android only).
Future<bool> uninstallApp(
covariant ApplicationPackage app, {
String userIdentifier,
});
/// 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.
///
/// If `includePastLogs` is true and the device type supports it, the log
/// reader will also include log messages from before the invocation time.
/// Defaults to false.
FutureOr<DeviceLogReader> getLogReader({
covariant ApplicationPackage app,
bool includePastLogs = false,
});
/// Get the port forwarder for this device.
DevicePortForwarder get portForwarder;
/// Clear the device's logs.
void clearLogs();
/// Optional device-specific artifact overrides.
OverrideArtifacts get artifactOverrides => null;
/// 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(
covariant ApplicationPackage package, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool ipv6 = false,
String userIdentifier,
});
/// Whether this device implements support for hot reload.
bool get supportsHotReload => true;
/// Whether this device implements support for hot restart.
bool get supportsHotRestart => true;
/// Whether flutter applications running on this device can be terminated
/// from the vmservice.
bool get supportsFlutterExit => true;
/// Whether the device supports taking screenshots of a running flutter
/// application.
bool get supportsScreenshot => false;
/// Whether the device supports the '--fast-start' development mode.
bool get supportsFastStart => false;
/// Stop an app package on the current device.
///
/// Specify [userIdentifier] to stop app installed to a profile (Android only).
Future<bool> stopApp(
covariant ApplicationPackage app, {
String userIdentifier,
});
/// Query the current application memory usage..
///
/// If the device does not support this callback, an empty map
/// is returned.
Future<MemoryInfo> queryMemoryInfo() {
return Future<MemoryInfo>.value(const MemoryInfo.empty());
}
Future<void> takeScreenshot(File outputFile) => Future<void>.error('unimplemented');
@nonVirtual
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => id.hashCode;
@nonVirtual
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is Device
&& other.id == 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 (final 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.category})',
device.id,
getNameForTargetPlatform(targetPlatform),
'${await device.sdkNameAndVersion}$supportIndicator',
]);
}
// Calculate column widths
final List<int> indices = List<int>.generate(table[0].length - 1, (int i) => i);
List<int> widths = indices.map<int>((int i) => 0).toList();
for (final List<String> row in table) {
widths = indices.map<int>((int i) => math.max(widths[i], row[i].length)).toList();
}
// Join columns into lines of text
for (final List<String> row in table) {
yield indices.map<String>((int i) => row[i].padRight(widths[i])).join(' • ') + ' • ${row.last}';
}
}
static Future<void> printDevices(List<Device> devices) async {
await descriptions(devices).forEach(globals.printStatus);
}
static List<String> devicesPlatformTypes(List<Device> devices) {
return devices
.map(
(Device d) => d.platformType.toString(),
).toSet().toList()..sort();
}
/// Convert the Device object to a JSON representation suitable for serialization.
Future<Map<String, Object>> toJson() async {
final bool isLocalEmu = await isLocalEmulator;
return <String, Object>{
'name': name,
'id': id,
'isSupported': isSupported(),
'targetPlatform': getNameForTargetPlatform(await targetPlatform),
'emulator': isLocalEmu,
'sdk': await sdkNameAndVersion,
'capabilities': <String, Object>{
'hotReload': supportsHotReload,
'hotRestart': supportsHotRestart,
'screenshot': supportsScreenshot,
'fastStart': supportsFastStart,
'flutterExit': supportsFlutterExit,
'hardwareRendering': isLocalEmu && await supportsHardwareRendering,
'startPaused': supportsStartPaused,
}
};
}
/// Clean up resources allocated by device
///
/// For example log readers or port forwarders.
Future<void> dispose();
}
/// Information about an application's memory usage.
abstract class MemoryInfo {
/// Const constructor to allow subclasses to be const.
const MemoryInfo();
/// Create a [MemoryInfo] object with no information.
const factory MemoryInfo.empty() = _NoMemoryInfo;
/// Convert the object to a JSON representation suitable for serialization.
Map<String, Object> toJson();
}
class _NoMemoryInfo implements MemoryInfo {
const _NoMemoryInfo();
@override
Map<String, Object> toJson() => <String, Object>{};
}
class DebuggingOptions {
DebuggingOptions.enabled(
this.buildInfo, {
this.startPaused = false,
this.disableServiceAuthCodes = false,
this.dartFlags = '',
this.enableSoftwareRendering = false,
this.skiaDeterministicRendering = false,
this.traceSkia = false,
this.traceAllowlist,
this.traceSystrace = false,
this.endlessTraceBuffer = false,
this.dumpSkpOnShaderCompilation = false,
this.cacheSkSL = false,
this.useTestFonts = false,
this.verboseSystemLogs = false,
this.hostVmServicePort,
this.deviceVmServicePort,
this.initializePlatform = true,
this.hostname,
this.port,
this.webEnableExposeUrl,
this.webUseSseForDebugProxy = true,
this.webRunHeadless = false,
this.webBrowserDebugPort,
this.webEnableExpressionEvaluation = false,
this.vmserviceOutFile,
this.fastStart = false,
}) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildInfo, {
this.initializePlatform = true,
this.port,
this.hostname,
this.webEnableExposeUrl,
this.webUseSseForDebugProxy = true,
this.webRunHeadless = false,
this.webBrowserDebugPort,
this.cacheSkSL = false,
this.traceAllowlist,
}) : debuggingEnabled = false,
useTestFonts = false,
startPaused = false,
dartFlags = '',
disableServiceAuthCodes = false,
enableSoftwareRendering = false,
skiaDeterministicRendering = false,
traceSkia = false,
traceSystrace = false,
endlessTraceBuffer = false,
dumpSkpOnShaderCompilation = false,
verboseSystemLogs = false,
hostVmServicePort = null,
deviceVmServicePort = null,
vmserviceOutFile = null,
fastStart = false,
webEnableExpressionEvaluation = false;
final bool debuggingEnabled;
final BuildInfo buildInfo;
final bool startPaused;
final String dartFlags;
final bool disableServiceAuthCodes;
final bool enableSoftwareRendering;
final bool skiaDeterministicRendering;
final bool traceSkia;
final String traceAllowlist;
final bool traceSystrace;
final bool endlessTraceBuffer;
final bool dumpSkpOnShaderCompilation;
final bool cacheSkSL;
final bool useTestFonts;
final bool verboseSystemLogs;
/// Whether to invoke webOnlyInitializePlatform in Flutter for web.
final bool initializePlatform;
final int hostVmServicePort;
final int deviceVmServicePort;
final String port;
final String hostname;
final bool webEnableExposeUrl;
final bool webUseSseForDebugProxy;
/// Whether to run the browser in headless mode.
///
/// Some CI environments do not provide a display and fail to launch the
/// browser with full graphics stack. Some browsers provide a special
/// "headless" mode that runs the browser with no graphics.
final bool webRunHeadless;
/// The port the browser should use for its debugging protocol.
final int webBrowserDebugPort;
/// Enable expression evaluation for web target
final bool webEnableExpressionEvaluation;
/// A file where the vmservice URL should be written after the application is started.
final String vmserviceOutFile;
final bool fastStart;
bool get hasObservatoryPort => hostVmServicePort != null;
}
class LaunchResult {
LaunchResult.succeeded({ this.observatoryUri }) : started = true;
LaunchResult.failed()
: started = false,
observatoryUri = null;
bool get hasObservatory => observatoryUri != null;
final bool started;
final Uri observatoryUri;
@override
String toString() {
final StringBuffer buf = StringBuffer('started=$started');
if (observatoryUri != null) {
buf.write(', observatory=$observatoryUri');
}
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 Process context;
@override
String toString() => 'ForwardedPort HOST:$hostPort to DEVICE:$devicePort';
/// Kill subprocess (if present) used in forwarding.
void dispose() {
if (context != null) {
context.kill();
}
}
}
/// 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 or zero, 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<void> unforward(ForwardedPort forwardedPort);
/// Cleanup allocated resources, like forwardedPorts
Future<void> dispose();
}
/// 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;
/// Some logs can be obtained from a VM service stream.
/// Set this after the VM services are connected.
vm_service.VmService connectedVMService;
@override
String toString() => name;
/// Process ID of the app on the device.
int appPid;
// Clean up resources allocated by log reader e.g. subprocesses
void dispose();
}
/// Describes an app running on the device.
class DiscoveredApp {
DiscoveredApp(this.id, this.observatoryPort);
final String id;
final int observatoryPort;
}
// An empty device log reader
class NoOpDeviceLogReader implements DeviceLogReader {
NoOpDeviceLogReader(this.name);
@override
final String name;
@override
int appPid;
@override
vm_service.VmService connectedVMService;
@override
Stream<String> get logLines => const Stream<String>.empty();
@override
void dispose() { }
}
// A portforwarder which does not support forwarding ports.
class NoOpDevicePortForwarder implements DevicePortForwarder {
const NoOpDevicePortForwarder();
@override
Future<int> forward(int devicePort, { int hostPort }) async => devicePort;
@override
List<ForwardedPort> get forwardedPorts => <ForwardedPort>[];
@override
Future<void> unforward(ForwardedPort forwardedPort) async { }
@override
Future<void> dispose() async { }
}