device.dart 27.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
Devon Carew's avatar
Devon Carew committed
6
import 'dart:math' as math;
7

8
import 'package:meta/meta.dart';
9
import 'package:vm_service/vm_service.dart' as vm_service;
10

11 12
import 'android/android_device_discovery.dart';
import 'android/android_workflow.dart';
13
import 'application_package.dart';
14
import 'artifacts.dart';
15
import 'base/context.dart';
16
import 'base/file_system.dart';
17
import 'base/io.dart';
18
import 'base/utils.dart';
19
import 'build_info.dart';
20
import 'features.dart';
21
import 'fuchsia/fuchsia_device.dart';
22 23
import 'fuchsia/fuchsia_sdk.dart';
import 'fuchsia/fuchsia_workflow.dart';
24
import 'globals.dart' as globals;
25 26
import 'ios/devices.dart';
import 'ios/simulators.dart';
27 28
import 'linux/linux_device.dart';
import 'macos/macos_device.dart';
29
import 'project.dart';
30
import 'tester/flutter_tester.dart';
31
import 'web/web_device.dart';
32
import 'windows/windows_device.dart';
33

34
DeviceManager get deviceManager => context.get<DeviceManager>();
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
/// 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;
}

68 69
/// A class to get all available devices.
class DeviceManager {
70

71
  /// Constructing DeviceManagers is cheap; they only do expensive work if some
72
  /// of their methods are called.
73 74
  List<DeviceDiscovery> get deviceDiscoverers => _deviceDiscoverers;
  final List<DeviceDiscovery> _deviceDiscoverers = List<DeviceDiscovery>.unmodifiable(<DeviceDiscovery>[
75 76 77 78 79 80
    AndroidDevices(
      logger: globals.logger,
      androidSdk: globals.androidSdk,
      androidWorkflow: androidWorkflow,
      processManager: globals.processManager,
    ),
81 82 83 84
    IOSDevices(
      platform: globals.platform,
      xcdevice: globals.xcdevice,
      iosWorkflow: globals.iosWorkflow,
85
      logger: globals.logger,
86
    ),
87
    IOSSimulators(iosSimulatorUtils: globals.iosSimulatorUtils),
88 89 90 91 92 93
    FuchsiaDevices(
      fuchsiaSdk: fuchsiaSdk,
      logger: globals.logger,
      fuchsiaWorkflow: fuchsiaWorkflow,
      platform: globals.platform,
    ),
94
    FlutterTesterDevices(),
95
    MacOSDevices(),
96 97 98 99
    LinuxDevices(
      platform: globals.platform,
      featureFlags: featureFlags,
    ),
100
    WindowsDevices(),
101 102 103 104 105
    WebDevices(
      featureFlags: featureFlags,
      fileSystem: globals.fs,
      platform: globals.platform,
      processManager: globals.processManager,
106
      logger: globals.logger,
107
    ),
108
  ]);
109

110 111
  String _specifiedDeviceId;

112
  /// A user-specified device ID.
113
  String get specifiedDeviceId {
114
    if (_specifiedDeviceId == null || _specifiedDeviceId == 'all') {
115
      return null;
116
    }
117 118
    return _specifiedDeviceId;
  }
119

120 121 122 123 124
  set specifiedDeviceId(String id) {
    _specifiedDeviceId = id;
  }

  /// True when the user has specified a single specific device.
125
  bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
126

127 128 129 130
  /// True when the user has specified all devices by setting
  /// specifiedDeviceId = 'all'.
  bool get hasSpecifiedAllDevices => _specifiedDeviceId == 'all';

131 132
  Future<List<Device>> getDevicesById(String deviceId) async {
    final List<Device> devices = await getAllConnectedDevices();
Devon Carew's avatar
Devon Carew committed
133
    deviceId = deviceId.toLowerCase();
134 135 136 137 138 139 140
    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);

141 142
    final Device exactMatch = devices.firstWhere(
        exactlyMatchesDeviceId, orElse: () => null);
143
    if (exactMatch != null) {
144
      return <Device>[exactMatch];
145
    }
146

147
    // Match on a id or name starting with [deviceId].
148
    return devices.where(startsWithDeviceId).toList();
Devon Carew's avatar
Devon Carew committed
149 150
  }

151
  /// Returns the list of connected devices, filtered by any user-specified device id.
152
  Future<List<Device>> getDevices() {
153 154 155
    return hasSpecifiedDeviceId
        ? getDevicesById(specifiedDeviceId)
        : getAllConnectedDevices();
156 157
  }

158
  Iterable<DeviceDiscovery> get _platformDiscoverers {
159
    return deviceDiscoverers.where((DeviceDiscovery discoverer) => discoverer.supportsPlatform);
160 161
  }

162
  /// Returns the list of all connected devices.
163 164 165 166 167 168 169
  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();
170
  }
171

172 173 174 175 176 177 178 179 180 181
  /// 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();
  }

182 183 184 185 186 187 188
  /// 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 {
189
    return <String>[
190
      for (final DeviceDiscovery discoverer in _platformDiscoverers)
191 192
        ...await discoverer.getDiagnostics(),
    ];
193
  }
194 195 196

  /// Find and return a list of devices based on the current project and environment.
  ///
197
  /// Returns a list of devices specified by the user.
198 199 200 201 202 203 204 205 206 207
  ///
  /// * 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.
208
  Future<List<Device>> findTargetDevices(FlutterProject flutterProject) async {
209
    List<Device> devices = await getDevices();
210

211 212 213 214 215 216
    // 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>[
217
        for (final Device device in devices)
218 219
          if (await device.targetPlatform != TargetPlatform.fuchsia_arm64 &&
              await device.targetPlatform != TargetPlatform.fuchsia_x64 &&
220
              await device.targetPlatform != TargetPlatform.web_javascript)
221
            device,
222 223 224 225 226 227 228 229
      ];
    }

    // 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>[
230
        for (final Device device in devices)
231
          if (isDeviceSupportedForProject(device, flutterProject))
232
            device,
233
      ];
234 235 236 237 238 239
    } 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>[];
240
    }
241

242 243 244 245 246
    // 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
    // use only typed 'flutter run' and both an Android device and desktop
    // device are availible, choose the Android device.
    if (devices.length > 1 && !hasSpecifiedAllDevices) {
247 248 249 250 251 252 253 254 255 256 257 258
      // Note: ephemeral is nullable for device types where this is not well
      // defined.
      if (devices.any((Device device) => device.ephemeral == true)) {
        devices = devices
            .where((Device device) => device.ephemeral == true)
            .toList();
      }
    }
    return devices;
  }

  /// Returns whether the device is supported for the project.
259
  ///
260
  /// This exists to allow the check to be overridden for google3 clients.
261 262 263
  bool isDeviceSupportedForProject(Device device, FlutterProject flutterProject) {
    return device.isSupportedForProject(flutterProject);
  }
264 265 266 267 268
}

/// An abstract class to discover and enumerate a specific type of devices.
abstract class DeviceDiscovery {
  bool get supportsPlatform;
269 270 271 272 273

  /// Whether this device discovery is capable of listing any devices given the
  /// current environment configuration.
  bool get canListAnything;

274
  /// Return all connected devices, cached on subsequent calls.
275
  Future<List<Device>> get devices;
276

277 278 279
  /// Return all connected devices. Discards existing cache of devices.
  Future<List<Device>> discoverDevices({ Duration timeout });

280 281
  /// Gets a list of diagnostic messages pertaining to issues with any connected
  /// devices (will be an empty list if there are no issues).
282
  Future<List<String>> getDiagnostics() => Future<List<String>>.value(<String>[]);
283 284
}

285 286 287 288 289
/// A [DeviceDiscovery] implementation that uses polling to discover device adds
/// and removals.
abstract class PollingDeviceDiscovery extends DeviceDiscovery {
  PollingDeviceDiscovery(this.name);

290 291
  static const Duration _pollingInterval = Duration(seconds: 4);
  static const Duration _pollingTimeout = Duration(seconds: 30);
292 293

  final String name;
294 295 296 297 298

  @protected
  @visibleForTesting
  ItemListNotifier<Device> deviceNotifier;

299
  Timer _timer;
300

301
  Future<List<Device>> pollingGetDevices({ Duration timeout });
302

303
  Future<void> startPolling() async {
304
    if (_timer == null) {
305
      deviceNotifier ??= ItemListNotifier<Device>();
306 307
      // Make initial population the default, fast polling timeout.
      _timer = _initTimer(null);
308 309 310
    }
  }

311
  Timer _initTimer(Duration pollingTimeout) {
312 313
    return Timer(_pollingInterval, () async {
      try {
314
        final List<Device> devices = await pollingGetDevices(timeout: pollingTimeout);
315
        deviceNotifier.updateWithNewList(devices);
316
      } on TimeoutException {
317
        globals.printTrace('Device poll timed out. Will retry.');
318
      }
319 320
      // Subsequent timeouts after initial population should wait longer.
      _timer = _initTimer(_pollingTimeout);
321 322 323
    });
  }

324
  Future<void> stopPolling() async {
325 326
    _timer?.cancel();
    _timer = null;
327 328
  }

329
  @override
330
  Future<List<Device>> get devices async {
331 332 333 334 335
    return _populateDevices();
  }

  @override
  Future<List<Device>> discoverDevices({ Duration timeout }) async {
336
    deviceNotifier = null;
337 338 339 340
    return _populateDevices(timeout: timeout);
  }

  Future<List<Device>> _populateDevices({ Duration timeout }) async {
341 342
    deviceNotifier ??= ItemListNotifier<Device>.from(await pollingGetDevices(timeout: timeout));
    return deviceNotifier.items;
343 344 345
  }

  Stream<Device> get onAdded {
346 347
    deviceNotifier ??= ItemListNotifier<Device>();
    return deviceNotifier.onAdded;
348 349 350
  }

  Stream<Device> get onRemoved {
351 352
    deviceNotifier ??= ItemListNotifier<Device>();
    return deviceNotifier.onRemoved;
353 354
  }

355
  Future<void> dispose() async => await stopPolling();
356

357
  @override
358 359 360
  String toString() => '$name device discovery';
}

361 362 363 364
/// 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.
365
abstract class Device {
366 367 368 369 370
  Device(this.id, {
    @required this.category,
    @required this.platformType,
    @required this.ephemeral,
  });
371

372
  final String id;
373

374 375 376 377 378 379 380 381 382
  /// 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;

383 384
  String get name;

385 386
  bool get supportsStartPaused => true;

387
  /// Whether it is an emulated device running on localhost.
388 389 390
  ///
  /// This may return `true` for certain physical Android devices, and is
  /// generally only a best effort guess.
391
  Future<bool> get isLocalEmulator;
392

393 394 395 396 397 398 399 400
  /// 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;

401 402 403 404 405 406
  /// 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;

407
  /// Whether the device is a simulator on a platform which supports hardware rendering.
408
  // This is soft-deprecated since the logic is not correct expect for iOS simulators.
409
  Future<bool> get supportsHardwareRendering async {
410
    return true;
411 412
  }

413 414 415
  /// Whether the device is supported for the current project directory.
  bool isSupportedForProject(FlutterProject flutterProject);

416 417 418 419 420 421 422
  /// 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,
  });
423

424
  /// Check if the latest build of the [app] is already installed.
425
  Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app);
426

427 428 429 430 431 432 433
  /// 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,
  });
434

435 436 437 438 439 440 441 442
  /// 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,
  });
443

444 445 446 447 448
  /// 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.
449
  String supportMessage() => isSupported() ? 'Supported' : 'Unsupported';
450

451 452
  /// The device's platform.
  Future<TargetPlatform> get targetPlatform;
453

454
  Future<String> get sdkNameAndVersion;
455

456
  /// Get a log reader for this device.
457 458
  ///
  /// If `app` is specified, this will return a log reader specific to that
459
  /// application. Otherwise, a global log reader will be returned.
460 461 462 463 464 465 466 467
  ///
  /// 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,
  });
468

469 470 471
  /// Get the port forwarder for this device.
  DevicePortForwarder get portForwarder;

472 473
  /// Clear the device's logs.
  void clearLogs();
474

475 476 477
  /// Optional device-specific artifact overrides.
  OverrideArtifacts get artifactOverrides => null;

478 479 480
  /// Start an app package on the current device.
  ///
  /// [platformArgs] allows callers to pass platform-specific arguments to the
481
  /// start call. The build mode is not used by all platforms.
Devon Carew's avatar
Devon Carew committed
482
  Future<LaunchResult> startApp(
483
    covariant ApplicationPackage package, {
484 485
    String mainPath,
    String route,
Devon Carew's avatar
Devon Carew committed
486
    DebuggingOptions debuggingOptions,
487
    Map<String, dynamic> platformArgs,
488 489
    bool prebuiltApplication = false,
    bool ipv6 = false,
490
    String userIdentifier,
491
  });
492

493 494 495 496 497
  /// Whether this device implements support for hot reload.
  bool get supportsHotReload => true;

  /// Whether this device implements support for hot restart.
  bool get supportsHotRestart => true;
498

499 500
  /// Whether flutter applications running on this device can be terminated
  /// from the vmservice.
501
  bool get supportsFlutterExit => true;
502

503 504
  /// Whether the device supports taking screenshots of a running flutter
  /// application.
Devon Carew's avatar
Devon Carew committed
505 506
  bool get supportsScreenshot => false;

507 508 509
  /// Whether the device supports the '--fast-start' development mode.
  bool get supportsFastStart => false;

510
  /// Stop an app package on the current device.
511 512 513 514 515 516
  ///
  /// Specify [userIdentifier] to stop app installed to a profile (Android only).
  Future<bool> stopApp(
    covariant ApplicationPackage app, {
    String userIdentifier,
  });
517

518 519 520 521 522 523 524 525
  /// 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());
  }

526
  Future<void> takeScreenshot(File outputFile) => Future<void>.error('unimplemented');
Devon Carew's avatar
Devon Carew committed
527

528
  @nonVirtual
529
  @override
530
  // ignore: avoid_equals_and_hash_code_on_mutable_classes
531 532
  int get hashCode => id.hashCode;

533
  @nonVirtual
534
  @override
535
  // ignore: avoid_equals_and_hash_code_on_mutable_classes
536
  bool operator ==(Object other) {
537
    if (identical(this, other)) {
538
      return true;
539
    }
540 541
    return other is Device
        && other.id == id;
542 543
  }

544
  @override
545
  String toString() => name;
Devon Carew's avatar
Devon Carew committed
546

547
  static Stream<String> descriptions(List<Device> devices) async* {
548
    if (devices.isEmpty) {
549
      return;
550
    }
Devon Carew's avatar
Devon Carew committed
551

552
    // Extract device information
553
    final List<List<String>> table = <List<String>>[];
554
    for (final Device device in devices) {
Devon Carew's avatar
Devon Carew committed
555
      String supportIndicator = device.isSupported() ? '' : ' (unsupported)';
556 557 558
      final TargetPlatform targetPlatform = await device.targetPlatform;
      if (await device.isLocalEmulator) {
        final String type = targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator';
559 560
        supportIndicator += ' ($type)';
      }
561
      table.add(<String>[
562
        '${device.name} (${device.category})',
563
        device.id,
564
        getNameForTargetPlatform(targetPlatform),
565
        '${await device.sdkNameAndVersion}$supportIndicator',
566 567 568 569
      ]);
    }

    // Calculate column widths
570
    final List<int> indices = List<int>.generate(table[0].length - 1, (int i) => i);
571
    List<int> widths = indices.map<int>((int i) => 0).toList();
572
    for (final List<String> row in table) {
573
      widths = indices.map<int>((int i) => math.max(widths[i], row[i].length)).toList();
574 575 576
    }

    // Join columns into lines of text
577
    for (final List<String> row in table) {
578
      yield indices.map<String>((int i) => row[i].padRight(widths[i])).join(' • ') + ' • ${row.last}';
579
    }
580 581
  }

582
  static Future<void> printDevices(List<Device> devices) async {
583
    await descriptions(devices).forEach(globals.printStatus);
Devon Carew's avatar
Devon Carew committed
584
  }
585

586 587 588 589 590 591 592
  static List<String> devicesPlatformTypes(List<Device> devices) {
    return devices
        .map(
          (Device d) => d.platformType.toString(),
        ).toSet().toList()..sort();
  }

593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
  /// 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,
      }
    };
  }

615 616 617
  /// Clean up resources allocated by device
  ///
  /// For example log readers or port forwarders.
618
  Future<void> dispose();
619 620
}

621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639
/// 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>{};
}

Devon Carew's avatar
Devon Carew committed
640
class DebuggingOptions {
641 642
  DebuggingOptions.enabled(
    this.buildInfo, {
643
    this.startPaused = false,
644
    this.disableServiceAuthCodes = false,
645
    this.dartFlags = '',
646 647 648
    this.enableSoftwareRendering = false,
    this.skiaDeterministicRendering = false,
    this.traceSkia = false,
649
    this.traceAllowlist,
650
    this.traceSystrace = false,
651
    this.endlessTraceBuffer = false,
652
    this.dumpSkpOnShaderCompilation = false,
653
    this.cacheSkSL = false,
654
    this.useTestFonts = false,
655
    this.verboseSystemLogs = false,
656 657
    this.hostVmServicePort,
    this.deviceVmServicePort,
658
    this.initializePlatform = true,
659 660
    this.hostname,
    this.port,
661
    this.webEnableExposeUrl,
662
    this.webUseSseForDebugProxy = true,
663 664
    this.webRunHeadless = false,
    this.webBrowserDebugPort,
665
    this.webEnableExpressionEvaluation = false,
666
    this.vmserviceOutFile,
667
    this.fastStart = false,
Devon Carew's avatar
Devon Carew committed
668 669
   }) : debuggingEnabled = true;

670 671 672 673 674
  DebuggingOptions.disabled(this.buildInfo, {
      this.initializePlatform = true,
      this.port,
      this.hostname,
      this.webEnableExposeUrl,
675
      this.webUseSseForDebugProxy = true,
676 677
      this.webRunHeadless = false,
      this.webBrowserDebugPort,
678
      this.cacheSkSL = false,
679
      this.traceAllowlist,
680
    }) : debuggingEnabled = false,
681 682
      useTestFonts = false,
      startPaused = false,
683
      dartFlags = '',
684
      disableServiceAuthCodes = false,
685 686 687 688
      enableSoftwareRendering = false,
      skiaDeterministicRendering = false,
      traceSkia = false,
      traceSystrace = false,
689
      endlessTraceBuffer = false,
690
      dumpSkpOnShaderCompilation = false,
691
      verboseSystemLogs = false,
692 693
      hostVmServicePort = null,
      deviceVmServicePort = null,
694
      vmserviceOutFile = null,
695 696
      fastStart = false,
      webEnableExpressionEvaluation = false;
Devon Carew's avatar
Devon Carew committed
697 698 699

  final bool debuggingEnabled;

700
  final BuildInfo buildInfo;
Devon Carew's avatar
Devon Carew committed
701
  final bool startPaused;
702
  final String dartFlags;
703
  final bool disableServiceAuthCodes;
704
  final bool enableSoftwareRendering;
705
  final bool skiaDeterministicRendering;
706
  final bool traceSkia;
707
  final String traceAllowlist;
708
  final bool traceSystrace;
709
  final bool endlessTraceBuffer;
710
  final bool dumpSkpOnShaderCompilation;
711
  final bool cacheSkSL;
712
  final bool useTestFonts;
713
  final bool verboseSystemLogs;
714 715
  /// Whether to invoke webOnlyInitializePlatform in Flutter for web.
  final bool initializePlatform;
716 717
  final int hostVmServicePort;
  final int deviceVmServicePort;
718 719
  final String port;
  final String hostname;
720
  final bool webEnableExposeUrl;
721
  final bool webUseSseForDebugProxy;
722 723 724 725 726 727 728 729 730 731 732

  /// 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;

733 734 735
  /// Enable expression evaluation for web target
  final bool webEnableExpressionEvaluation;

736
  /// A file where the vmservice URL should be written after the application is started.
737
  final String vmserviceOutFile;
738
  final bool fastStart;
Devon Carew's avatar
Devon Carew committed
739

740
  bool get hasObservatoryPort => hostVmServicePort != null;
Devon Carew's avatar
Devon Carew committed
741 742 743
}

class LaunchResult {
744
  LaunchResult.succeeded({ this.observatoryUri }) : started = true;
745 746 747
  LaunchResult.failed()
    : started = false,
      observatoryUri = null;
Devon Carew's avatar
Devon Carew committed
748

749
  bool get hasObservatory => observatoryUri != null;
Devon Carew's avatar
Devon Carew committed
750 751

  final bool started;
752
  final Uri observatoryUri;
Devon Carew's avatar
Devon Carew committed
753 754 755

  @override
  String toString() {
756
    final StringBuffer buf = StringBuffer('started=$started');
757
    if (observatoryUri != null) {
758
      buf.write(', observatory=$observatoryUri');
759
    }
Devon Carew's avatar
Devon Carew committed
760 761 762 763
    return buf.toString();
  }
}

764
class ForwardedPort {
765 766
  ForwardedPort(this.hostPort, this.devicePort) : context = null;
  ForwardedPort.withContext(this.hostPort, this.devicePort, this.context);
767 768 769

  final int hostPort;
  final int devicePort;
770
  final Process context;
771

772
  @override
773
  String toString() => 'ForwardedPort HOST:$hostPort to DEVICE:$devicePort';
774 775 776

  /// Kill subprocess (if present) used in forwarding.
  void dispose() {
777 778
    if (context != null) {
      context.kill();
779 780
    }
  }
781 782 783 784 785 786 787 788 789
}

/// 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.
790
  /// If [hostPort] is null or zero, will auto select a host port.
791
  /// Returns a Future that completes with the host port.
792
  Future<int> forward(int devicePort, { int hostPort });
793 794

  /// Stops forwarding [forwardedPort].
795
  Future<void> unforward(ForwardedPort forwardedPort);
796 797

  /// Cleanup allocated resources, like forwardedPorts
798
  Future<void> dispose();
799 800
}

Devon Carew's avatar
Devon Carew committed
801
/// Read the log for a particular device.
Devon Carew's avatar
Devon Carew committed
802 803 804
abstract class DeviceLogReader {
  String get name;

Devon Carew's avatar
Devon Carew committed
805 806
  /// A broadcast stream where each element in the string is a line of log output.
  Stream<String> get logLines;
Devon Carew's avatar
Devon Carew committed
807

808 809
  /// Some logs can be obtained from a VM service stream.
  /// Set this after the VM services are connected.
810
  vm_service.VmService connectedVMService;
811

812
  @override
Devon Carew's avatar
Devon Carew committed
813
  String toString() => name;
814

815
  /// Process ID of the app on the device.
816
  int appPid;
817 818

  // Clean up resources allocated by log reader e.g. subprocesses
819
  void dispose();
Devon Carew's avatar
Devon Carew committed
820
}
821 822 823

/// Describes an app running on the device.
class DiscoveredApp {
824
  DiscoveredApp(this.id, this.observatoryPort);
825 826 827
  final String id;
  final int observatoryPort;
}
828 829 830 831 832 833 834 835 836 837 838

// An empty device log reader
class NoOpDeviceLogReader implements DeviceLogReader {
  NoOpDeviceLogReader(this.name);

  @override
  final String name;

  @override
  int appPid;

839
  @override
840
  vm_service.VmService connectedVMService;
841

842 843
  @override
  Stream<String> get logLines => const Stream<String>.empty();
844 845 846

  @override
  void dispose() { }
847 848 849 850 851 852 853
}

// A portforwarder which does not support forwarding ports.
class NoOpDevicePortForwarder implements DevicePortForwarder {
  const NoOpDevicePortForwarder();

  @override
854
  Future<int> forward(int devicePort, { int hostPort }) async => devicePort;
855 856 857 858 859

  @override
  List<ForwardedPort> get forwardedPorts => <ForwardedPort>[];

  @override
860
  Future<void> unforward(ForwardedPort forwardedPort) async { }
861 862 863

  @override
  Future<void> dispose() async { }
864
}