flutter/packages/flutter_tools/lib/src/device.dart
Jakob Andersen 7b2367ed5f Remove legacy .apk build. (#8793)
* Remove legacy .apk build.

Print out an error message telling the user to upgrade the project if
it's not Gradle-based. Removed all the obvious traces of the legacy
build.

Added support for Dart VM kernel snapshots in Gradle builds.

Fixed Android installs to verify that the app is actually installed, and
not just rely on the presence of the .sha1 file.
2017-03-20 11:05:55 +01:00

390 lines
12 KiB
Dart

// 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());
}
List<DeviceDiscovery> _deviceDiscoverers = <DeviceDiscovery>[];
/// A user-specified device ID.
String specifiedDeviceId;
bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
/// Return the devices with a name or id matching [deviceId].
/// This does a case insentitive compare with [deviceId].
Future<List<Device>> getDevicesById(String deviceId) async {
deviceId = deviceId.toLowerCase();
final List<Device> devices = await getAllConnectedDevices();
final Device device = devices.firstWhere(
(Device device) =>
device.id.toLowerCase() == deviceId ||
device.name.toLowerCase() == deviceId,
orElse: () => null);
if (device != null)
return <Device>[device];
// Match on a id or name starting with [deviceId].
return devices.where((Device device) {
return (device.id.toLowerCase().startsWith(deviceId) ||
device.name.toLowerCase().startsWith(deviceId));
}).toList();
}
/// Return the list of connected devices, filtered by any user-specified device id.
Future<List<Device>> getDevices() async {
if (specifiedDeviceId == null) {
return getAllConnectedDevices();
} else {
return getDevicesById(specifiedDeviceId);
}
}
/// Return the list of all connected devices.
Future<List<Device>> getAllConnectedDevices() async {
return _deviceDiscoverers
.where((DeviceDiscovery discoverer) => discoverer.supportsPlatform)
.expand((DeviceDiscovery discoverer) => discoverer.devices)
.toList();
}
}
/// An abstract class to discover and enumerate a specific type of devices.
abstract class DeviceDiscovery {
bool get supportsPlatform;
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) {
if (_items == 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 {
if (_items == null)
_items = new ItemListNotifier<Device>.from(pollingGetDevices());
return _items.items;
}
Stream<Device> get onAdded {
if (_items == null)
_items = new ItemListNotifier<Device>();
return _items.onAdded;
}
Stream<Device> get onRemoved {
if (_items == null)
_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.
bool get isLocalEmulator;
/// Check if a version of the given app is already installed
bool isAppInstalled(ApplicationPackage app);
/// Check if the latest build of the [app] is already installed.
bool isLatestBuildInstalled(ApplicationPackage app);
/// Install an app package on the current device
bool installApp(ApplicationPackage app);
/// Uninstall an app package from the current device
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";
TargetPlatform get targetPlatform;
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 Iterable<String> descriptions(List<Device> devices) {
if (devices.isEmpty)
return <String>[];
// Extract device information
final List<List<String>> table = <List<String>>[];
for (Device device in devices) {
String supportIndicator = device.isSupported() ? '' : ' (unsupported)';
if (device.isLocalEmulator) {
final String type = device.targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator';
supportIndicator += ' ($type)';
}
table.add(<String>[
device.name,
device.id,
'${getNameForTargetPlatform(device.targetPlatform)}',
'${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
return table.map((List<String> row) =>
indices.map((int i) => row[i].padRight(widths[i])).join('') +
'${row.last}');
}
static void printDevices(List<Device> devices) {
descriptions(devices).forEach(printStatus);
}
}
class DebuggingOptions {
DebuggingOptions.enabled(this.buildMode, {
this.startPaused: false,
this.observatoryPort,
this.diagnosticPort
}) : debuggingEnabled = true;
DebuggingOptions.disabled(this.buildMode) :
debuggingEnabled = false,
startPaused = false,
observatoryPort = null,
diagnosticPort = null;
final bool debuggingEnabled;
final BuildMode buildMode;
final bool startPaused;
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;
}
/// 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;
}