mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

An improvement for #144634. A wirelessly connected device will displayed as `Target device 1 (wireless)` in various of places. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
402 lines
13 KiB
Dart
402 lines
13 KiB
Dart
// 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 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/android/android_device.dart';
|
|
import 'package:flutter_tools/src/android/java.dart';
|
|
import 'package:flutter_tools/src/application_package.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/build_info.dart';
|
|
import 'package:flutter_tools/src/commands/daemon.dart';
|
|
import 'package:flutter_tools/src/daemon.dart';
|
|
import 'package:flutter_tools/src/device.dart';
|
|
import 'package:flutter_tools/src/proxied_devices/devices.dart';
|
|
import 'package:flutter_tools/src/vmservice.dart';
|
|
import 'package:test/fake.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
import '../../src/fake_devices.dart';
|
|
import '../../src/fakes.dart';
|
|
|
|
void main() {
|
|
Daemon? daemon;
|
|
late NotifyingLogger notifyingLogger;
|
|
late BufferLogger bufferLogger;
|
|
late FakeAndroidDevice fakeDevice;
|
|
|
|
late FakeApplicationPackageFactory applicationPackageFactory;
|
|
late MemoryFileSystem memoryFileSystem;
|
|
late FakeProcessManager fakeProcessManager;
|
|
|
|
group('ProxiedDevices', () {
|
|
late DaemonConnection serverDaemonConnection;
|
|
late DaemonConnection clientDaemonConnection;
|
|
setUp(() {
|
|
bufferLogger = BufferLogger.test();
|
|
notifyingLogger = NotifyingLogger(verbose: false, parent: bufferLogger);
|
|
final FakeDaemonStreams serverDaemonStreams = FakeDaemonStreams();
|
|
serverDaemonConnection = DaemonConnection(
|
|
daemonStreams: serverDaemonStreams,
|
|
logger: bufferLogger,
|
|
);
|
|
final FakeDaemonStreams clientDaemonStreams = FakeDaemonStreams();
|
|
clientDaemonConnection = DaemonConnection(
|
|
daemonStreams: clientDaemonStreams,
|
|
logger: bufferLogger,
|
|
);
|
|
|
|
serverDaemonStreams.inputs.addStream(clientDaemonStreams.outputs.stream);
|
|
clientDaemonStreams.inputs.addStream(serverDaemonStreams.outputs.stream);
|
|
|
|
applicationPackageFactory = FakeApplicationPackageFactory();
|
|
memoryFileSystem = MemoryFileSystem();
|
|
fakeProcessManager = FakeProcessManager.empty();
|
|
});
|
|
|
|
tearDown(() async {
|
|
if (daemon != null) {
|
|
return daemon!.shutdown();
|
|
}
|
|
notifyingLogger.dispose();
|
|
await serverDaemonConnection.dispose();
|
|
await clientDaemonConnection.dispose();
|
|
});
|
|
|
|
testUsingContext('can list devices', () async {
|
|
daemon = Daemon(serverDaemonConnection, notifyingLogger: notifyingLogger);
|
|
fakeDevice = FakeAndroidDevice();
|
|
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
|
|
daemon!.deviceDomain.addDeviceDiscoverer(discoverer);
|
|
discoverer.addDevice(fakeDevice);
|
|
|
|
final ProxiedDevices proxiedDevices = ProxiedDevices(
|
|
clientDaemonConnection,
|
|
logger: bufferLogger,
|
|
);
|
|
|
|
final List<Device> devices = await proxiedDevices.discoverDevices();
|
|
expect(devices, hasLength(1));
|
|
final Device device = devices[0];
|
|
expect(device.id, fakeDevice.id);
|
|
expect(device.name, 'Proxied ${fakeDevice.name}');
|
|
expect(await device.targetPlatform, await fakeDevice.targetPlatform);
|
|
expect(await device.isLocalEmulator, await fakeDevice.isLocalEmulator);
|
|
});
|
|
|
|
testUsingContext('calls supportsRuntimeMode', () async {
|
|
daemon = Daemon(serverDaemonConnection, notifyingLogger: notifyingLogger);
|
|
fakeDevice = FakeAndroidDevice();
|
|
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
|
|
daemon!.deviceDomain.addDeviceDiscoverer(discoverer);
|
|
discoverer.addDevice(fakeDevice);
|
|
|
|
final ProxiedDevices proxiedDevices = ProxiedDevices(
|
|
clientDaemonConnection,
|
|
logger: bufferLogger,
|
|
);
|
|
|
|
final List<Device> devices = await proxiedDevices.devices();
|
|
expect(devices, hasLength(1));
|
|
final Device device = devices[0];
|
|
final bool supportsRuntimeMode = await device.supportsRuntimeMode(BuildMode.release);
|
|
expect(fakeDevice.supportsRuntimeModeCalledBuildMode, BuildMode.release);
|
|
expect(supportsRuntimeMode, true);
|
|
}, overrides: <Type, Generator>{Java: () => FakeJava()});
|
|
|
|
testUsingContext('redirects logs', () async {
|
|
daemon = Daemon(serverDaemonConnection, notifyingLogger: notifyingLogger);
|
|
fakeDevice = FakeAndroidDevice();
|
|
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
|
|
daemon!.deviceDomain.addDeviceDiscoverer(discoverer);
|
|
discoverer.addDevice(fakeDevice);
|
|
|
|
final ProxiedDevices proxiedDevices = ProxiedDevices(
|
|
clientDaemonConnection,
|
|
logger: bufferLogger,
|
|
);
|
|
|
|
final FakeDeviceLogReader fakeLogReader = FakeDeviceLogReader();
|
|
fakeDevice.logReader = fakeLogReader;
|
|
|
|
final List<Device> devices = await proxiedDevices.devices();
|
|
expect(devices, hasLength(1));
|
|
final Device device = devices[0];
|
|
final DeviceLogReader logReader = await device.getLogReader();
|
|
fakeLogReader.logLinesController.add('Some log line');
|
|
|
|
final String receivedLogLine = await logReader.logLines.first;
|
|
expect(receivedLogLine, 'Some log line');
|
|
|
|
// Now try to stop the log reader
|
|
expect(fakeLogReader.disposeCalled, false);
|
|
logReader.dispose();
|
|
await pumpEventQueue();
|
|
expect(fakeLogReader.disposeCalled, true);
|
|
});
|
|
testUsingContext(
|
|
'starts and stops app',
|
|
() async {
|
|
daemon = Daemon(serverDaemonConnection, notifyingLogger: notifyingLogger);
|
|
fakeDevice = FakeAndroidDevice();
|
|
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
|
|
daemon!.deviceDomain.addDeviceDiscoverer(discoverer);
|
|
discoverer.addDevice(fakeDevice);
|
|
|
|
final ProxiedDevices proxiedDevices = ProxiedDevices(
|
|
clientDaemonConnection,
|
|
logger: bufferLogger,
|
|
);
|
|
final FakePrebuiltApplicationPackage prebuiltApplicationPackage =
|
|
FakePrebuiltApplicationPackage();
|
|
final File dummyApplicationBinary = memoryFileSystem.file('/directory/dummy_file');
|
|
dummyApplicationBinary.parent.createSync();
|
|
dummyApplicationBinary.writeAsStringSync('dummy content');
|
|
prebuiltApplicationPackage.applicationPackage = dummyApplicationBinary;
|
|
|
|
final List<Device> devices = await proxiedDevices.devices();
|
|
expect(devices, hasLength(1));
|
|
final Device device = devices[0];
|
|
|
|
// Now try to start the app
|
|
final FakeApplicationPackage applicationPackage = FakeApplicationPackage();
|
|
applicationPackageFactory.applicationPackage = applicationPackage;
|
|
|
|
final Uri vmServiceUri = Uri.parse('http://127.0.0.1:12345/vmService');
|
|
fakeDevice.launchResult = LaunchResult.succeeded(vmServiceUri: vmServiceUri);
|
|
|
|
final LaunchResult launchResult = await device.startApp(
|
|
prebuiltApplicationPackage,
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
);
|
|
|
|
expect(launchResult.started, true);
|
|
// The returned vmServiceUri was a forwarded port, so we cannot compare them directly.
|
|
expect(launchResult.vmServiceUri!.path, vmServiceUri.path);
|
|
|
|
expect(
|
|
applicationPackageFactory.applicationBinaryRequested!.readAsStringSync(),
|
|
'dummy content',
|
|
);
|
|
expect(applicationPackageFactory.platformRequested, TargetPlatform.android_arm);
|
|
|
|
expect(fakeDevice.startAppPackage, applicationPackage);
|
|
|
|
// Now try to stop the app
|
|
final bool stopAppResult = await device.stopApp(prebuiltApplicationPackage);
|
|
expect(fakeDevice.stopAppPackage, applicationPackage);
|
|
expect(stopAppResult, true);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
Java: () => FakeJava(),
|
|
ApplicationPackageFactory: () => applicationPackageFactory,
|
|
FileSystem: () => memoryFileSystem,
|
|
ProcessManager: () => fakeProcessManager,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'takes screenshot',
|
|
() async {
|
|
daemon = Daemon(serverDaemonConnection, notifyingLogger: notifyingLogger);
|
|
fakeDevice = FakeAndroidDevice();
|
|
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
|
|
daemon!.deviceDomain.addDeviceDiscoverer(discoverer);
|
|
discoverer.addDevice(fakeDevice);
|
|
|
|
final ProxiedDevices proxiedDevices = ProxiedDevices(
|
|
clientDaemonConnection,
|
|
logger: bufferLogger,
|
|
);
|
|
|
|
final List<Device> devices = await proxiedDevices.devices();
|
|
expect(devices, hasLength(1));
|
|
final Device device = devices[0];
|
|
|
|
final List<int> screenshot = <int>[1, 2, 3, 4, 5];
|
|
fakeDevice.screenshot = screenshot;
|
|
|
|
final File screenshotOutputFile = memoryFileSystem.file('screenshot_file');
|
|
await device.takeScreenshot(screenshotOutputFile);
|
|
|
|
expect(await screenshotOutputFile.readAsBytes(), screenshot);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
Java: () => FakeJava(),
|
|
FileSystem: () => memoryFileSystem,
|
|
ProcessManager: () => fakeProcessManager,
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
class FakeDaemonStreams implements DaemonStreams {
|
|
final StreamController<DaemonMessage> inputs = StreamController<DaemonMessage>();
|
|
final StreamController<DaemonMessage> outputs = StreamController<DaemonMessage>();
|
|
|
|
@override
|
|
Stream<DaemonMessage> get inputStream {
|
|
return inputs.stream;
|
|
}
|
|
|
|
@override
|
|
void send(Map<String, dynamic> message, [List<int>? binary]) {
|
|
outputs.add(DaemonMessage(message, binary != null ? Stream<List<int>>.value(binary) : null));
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {
|
|
await inputs.close();
|
|
// In some tests, outputs have no listeners. We don't wait for outputs to close.
|
|
unawaited(outputs.close());
|
|
}
|
|
}
|
|
|
|
class FakeAndroidDevice extends Fake implements AndroidDevice {
|
|
@override
|
|
final String id = 'device';
|
|
|
|
@override
|
|
final String name = 'device';
|
|
|
|
@override
|
|
String get displayName => name;
|
|
|
|
@override
|
|
Future<String> get emulatorId async => 'device';
|
|
|
|
@override
|
|
Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm;
|
|
|
|
@override
|
|
Future<bool> get isLocalEmulator async => false;
|
|
|
|
@override
|
|
final Category category = Category.mobile;
|
|
|
|
@override
|
|
final PlatformType platformType = PlatformType.android;
|
|
|
|
@override
|
|
final bool ephemeral = false;
|
|
|
|
@override
|
|
bool get isConnected => true;
|
|
|
|
@override
|
|
final DeviceConnectionInterface connectionInterface = DeviceConnectionInterface.attached;
|
|
|
|
@override
|
|
Future<String> get sdkNameAndVersion async => 'Android 12';
|
|
|
|
@override
|
|
bool get supportsHotReload => true;
|
|
|
|
@override
|
|
bool get supportsHotRestart => true;
|
|
|
|
@override
|
|
bool get supportsScreenshot => true;
|
|
|
|
@override
|
|
bool get supportsFastStart => true;
|
|
|
|
@override
|
|
bool get supportsFlutterExit => true;
|
|
|
|
@override
|
|
Future<bool> get supportsHardwareRendering async => true;
|
|
|
|
@override
|
|
bool get supportsStartPaused => true;
|
|
|
|
BuildMode? supportsRuntimeModeCalledBuildMode;
|
|
@override
|
|
Future<bool> supportsRuntimeMode(BuildMode buildMode) async {
|
|
supportsRuntimeModeCalledBuildMode = buildMode;
|
|
return true;
|
|
}
|
|
|
|
late DeviceLogReader logReader;
|
|
@override
|
|
FutureOr<DeviceLogReader> getLogReader({ApplicationPackage? app, bool includePastLogs = false}) =>
|
|
logReader;
|
|
|
|
ApplicationPackage? startAppPackage;
|
|
late LaunchResult launchResult;
|
|
@override
|
|
Future<LaunchResult> startApp(
|
|
ApplicationPackage? package, {
|
|
String? mainPath,
|
|
String? route,
|
|
DebuggingOptions? debuggingOptions,
|
|
Map<String, Object?> platformArgs = const <String, Object>{},
|
|
bool prebuiltApplication = false,
|
|
bool ipv6 = false,
|
|
String? userIdentifier,
|
|
}) async {
|
|
startAppPackage = package;
|
|
return launchResult;
|
|
}
|
|
|
|
ApplicationPackage? stopAppPackage;
|
|
@override
|
|
Future<bool> stopApp(ApplicationPackage? app, {String? userIdentifier}) async {
|
|
stopAppPackage = app;
|
|
return true;
|
|
}
|
|
|
|
late List<int> screenshot;
|
|
@override
|
|
Future<void> takeScreenshot(File outputFile) {
|
|
return outputFile.writeAsBytes(screenshot);
|
|
}
|
|
}
|
|
|
|
class FakeDeviceLogReader implements DeviceLogReader {
|
|
final StreamController<String> logLinesController = StreamController<String>();
|
|
bool disposeCalled = false;
|
|
|
|
@override
|
|
Future<void> provideVmService(FlutterVmService? connectedVmService) async {}
|
|
|
|
@override
|
|
void dispose() {
|
|
disposeCalled = true;
|
|
}
|
|
|
|
@override
|
|
Stream<String> get logLines => logLinesController.stream;
|
|
|
|
@override
|
|
String get name => 'device';
|
|
}
|
|
|
|
class FakeApplicationPackageFactory implements ApplicationPackageFactory {
|
|
TargetPlatform? platformRequested;
|
|
File? applicationBinaryRequested;
|
|
ApplicationPackage? applicationPackage;
|
|
|
|
@override
|
|
Future<ApplicationPackage?> getPackageForPlatform(
|
|
TargetPlatform platform, {
|
|
BuildInfo? buildInfo,
|
|
File? applicationBinary,
|
|
}) async {
|
|
platformRequested = platform;
|
|
applicationBinaryRequested = applicationBinary;
|
|
return applicationPackage;
|
|
}
|
|
}
|
|
|
|
class FakeApplicationPackage extends Fake implements ApplicationPackage {}
|
|
|
|
class FakePrebuiltApplicationPackage extends Fake implements PrebuiltApplicationPackage {
|
|
@override
|
|
late File applicationPackage;
|
|
}
|