Refactor `DeviceManager.findTargetDevices()` and `FlutterCommand.findAllTargetDevices()`, and add a flag to not show prompt. (#112223)

diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index 020efc5..36069c2 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -205,7 +205,6 @@
         ),
         fuchsiaSdk: globals.fuchsiaSdk!,
         operatingSystemUtils: globals.os,
-        terminal: globals.terminal,
         customDevicesConfig: globals.customDevicesConfig,
       ),
       DevtoolsLauncher: () => DevtoolsServerLauncher(
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 9eeef33..9e6017e 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -9,13 +9,10 @@
 
 import 'application_package.dart';
 import 'artifacts.dart';
-import 'base/common.dart';
 import 'base/context.dart';
 import 'base/dds.dart';
 import 'base/file_system.dart';
 import 'base/logger.dart';
-import 'base/terminal.dart';
-import 'base/user_messages.dart' hide userMessages;
 import 'base/utils.dart';
 import 'build_info.dart';
 import 'devfs.dart';
@@ -83,15 +80,9 @@
 abstract class DeviceManager {
   DeviceManager({
     required Logger logger,
-    required Terminal terminal,
-    required UserMessages userMessages,
-  }) : _logger = logger,
-       _terminal = terminal,
-       _userMessages = userMessages;
+  }) : _logger = logger;
 
   final Logger _logger;
-  final Terminal _terminal;
-  final UserMessages _userMessages;
 
   /// Constructing DeviceManagers is cheap; they only do expensive work if some
   /// of their methods are called.
@@ -219,7 +210,12 @@
     ];
   }
 
-  /// Find and return a list of devices based on the current project and environment.
+  /// Find and return all target [Device]s based upon currently connected
+  /// devices, the current project, and criteria entered by the user on
+  /// the command line.
+  ///
+  /// If no device can be found that meets specified criteria,
+  /// then print an error message and return null.
   ///
   /// Returns a list of devices specified by the user.
   ///
@@ -233,9 +229,13 @@
   /// device connected, then filter out unsupported devices and prioritize
   /// ephemeral devices.
   ///
-  /// * If [flutterProject] is null, then assume the project supports all
-  /// device types.
-  Future<List<Device>> findTargetDevices(FlutterProject? flutterProject, { Duration? timeout }) async {
+  /// * If [promptUserToChooseDevice] is true, and there are more than one
+  /// device after the aforementioned filters, and the user is connected to a
+  /// terminal, then show a prompt asking the user to choose one.
+  Future<List<Device>> findTargetDevices(
+    FlutterProject? flutterProject, {
+    Duration? timeout,
+  }) async {
     if (timeout != null) {
       // Reset the cache with the specified timeout.
       await refreshAllConnectedDevices(timeout: timeout);
@@ -244,97 +244,56 @@
     List<Device> devices = (await getDevices())
         .where((Device device) => device.isSupported()).toList();
 
-    // Always remove web and fuchsia devices from `--all`. This setting
-    // currently requires devices to share a frontend_server and resident
-    // runner instance. Both web and fuchsia require differently configured
-    // compilers, and web requires an entirely different resident runner.
     if (hasSpecifiedAllDevices) {
+      // User has specified `--device all`.
+      //
+      // Always remove web and fuchsia devices from `--all`. This setting
+      // currently requires devices to share a frontend_server and resident
+      // runner instance. Both web and fuchsia require differently configured
+      // compilers, and web requires an entirely different resident runner.
       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)
+              await device.targetPlatform != TargetPlatform.web_javascript &&
+              isDeviceSupportedForProject(device, flutterProject))
             device,
       ];
-    }
+    } else if (!hasSpecifiedDeviceId) {
+      // User did not specify the 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) {
+      // 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.
       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 available, 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 (devices.length > 1) {
+        // 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 available, choose the Android device.
 
-            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 && _terminal.stdinHasTerminal) {
-        _logger.printStatus(_userMessages.flutterMultipleDevicesFound);
-        await Device.printDevices(devices, _logger);
-        final Device chosenDevice = await _chooseOneOfAvailableDevices(devices);
-        specifiedDeviceId = chosenDevice.id;
-        devices = <Device>[chosenDevice];
+        // Note: ephemeral is nullable for device types where this is not well
+        // defined.
+        final List<Device> ephemeralDevices = <Device>[
+          for (final Device device in devices)
+            if (device.ephemeral == true)
+              device,
+        ];
+
+        if (ephemeralDevices.length == 1) {
+          devices = ephemeralDevices;
+        }
       }
     }
+
     return devices;
   }
 
-  Future<Device> _chooseOneOfAvailableDevices(List<Device> devices) async {
-    _displayDeviceOptions(devices);
-    final String userInput =  await _readUserInput(devices.length);
-    if (userInput.toLowerCase() == 'q') {
-      throwToolExit('');
-    }
-    return devices[int.parse(userInput) - 1];
-  }
-
-  void _displayDeviceOptions(List<Device> devices) {
-    int count = 1;
-    for (final Device device in devices) {
-      _logger.printStatus(_userMessages.flutterChooseDevice(count, device.name, device.id));
-      count++;
-    }
-  }
-
-  Future<String> _readUserInput(int deviceCount) async {
-    _terminal.usesTerminalUi = true;
-    final String result = await _terminal.promptForCharInput(
-      <String>[ for (int i = 0; i < deviceCount; i++) '${i + 1}', 'q', 'Q'],
-      displayAcceptedCharacters: false,
-      logger: _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. If
diff --git a/packages/flutter_tools/lib/src/flutter_device_manager.dart b/packages/flutter_tools/lib/src/flutter_device_manager.dart
index f30a0da..4741119 100644
--- a/packages/flutter_tools/lib/src/flutter_device_manager.dart
+++ b/packages/flutter_tools/lib/src/flutter_device_manager.dart
@@ -11,6 +11,7 @@
 import 'base/file_system.dart';
 import 'base/os.dart';
 import 'base/platform.dart';
+import 'base/user_messages.dart';
 import 'custom_devices/custom_device.dart';
 import 'custom_devices/custom_devices_config.dart';
 import 'device.dart';
@@ -51,10 +52,9 @@
     required Artifacts artifacts,
     required MacOSWorkflow macOSWorkflow,
     required FuchsiaSdk fuchsiaSdk,
-    required super.userMessages,
+    required UserMessages userMessages,
     required OperatingSystemUtils operatingSystemUtils,
     required WindowsWorkflow windowsWorkflow,
-    required super.terminal,
     required CustomDevicesConfig customDevicesConfig,
   }) : deviceDiscoverers =  <DeviceDiscovery>[
     AndroidDevices(
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 41eee55..b6c628b 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -1413,52 +1413,105 @@
       timeout: deviceDiscoveryTimeout,
     );
 
-    if (devices.isEmpty && deviceManager.hasSpecifiedDeviceId) {
-      globals.printStatus(userMessages.flutterNoMatchingDevice(deviceManager.specifiedDeviceId!));
-      final List<Device> allDevices = await deviceManager.getAllConnectedDevices();
-      if (allDevices.isNotEmpty) {
-        globals.printStatus('');
-        globals.printStatus('The following devices were found:');
-        await Device.printDevices(allDevices, globals.logger);
-      }
-      return null;
-    } else if (devices.isEmpty) {
-      if (deviceManager.hasSpecifiedAllDevices) {
-        globals.printStatus(userMessages.flutterNoDevicesFound);
-      } else {
-        globals.printStatus(userMessages.flutterNoSupportedDevices);
-      }
-      final List<Device> unsupportedDevices = await deviceManager.getDevices();
-      if (unsupportedDevices.isNotEmpty) {
-        final StringBuffer result = StringBuffer();
-        result.writeln(userMessages.flutterFoundButUnsupportedDevices);
-        result.writeAll(
-          (await Device.descriptions(unsupportedDevices))
-              .map((String desc) => desc)
-              .toList(),
-          '\n',
-        );
-        result.writeln();
-        result.writeln(userMessages.flutterMissPlatformProjects(
-          Device.devicesPlatformTypes(unsupportedDevices),
-        ));
-        globals.printStatus(result.toString());
-      }
-      return null;
-    } else if (devices.length > 1 && !deviceManager.hasSpecifiedAllDevices) {
+    if (devices.isEmpty) {
       if (deviceManager.hasSpecifiedDeviceId) {
-       globals.printStatus(userMessages.flutterFoundSpecifiedDevices(devices.length, deviceManager.specifiedDeviceId!));
+        globals.logger.printStatus(userMessages.flutterNoMatchingDevice(deviceManager.specifiedDeviceId!));
+        final List<Device> allDevices = await deviceManager.getAllConnectedDevices();
+        if (allDevices.isNotEmpty) {
+          globals.logger.printStatus('');
+          globals.logger.printStatus('The following devices were found:');
+          await Device.printDevices(allDevices, globals.logger);
+        }
+        return null;
+      } else if (deviceManager.hasSpecifiedAllDevices) {
+        globals.logger.printStatus(userMessages.flutterNoDevicesFound);
+        await _printUnsupportedDevice(deviceManager);
+        return null;
       } else {
-        globals.printStatus(userMessages.flutterSpecifyDeviceWithAllOption);
-        devices = await deviceManager.getAllConnectedDevices();
+        globals.logger.printStatus(userMessages.flutterNoSupportedDevices);
+        await _printUnsupportedDevice(deviceManager);
+        return null;
       }
-      globals.printStatus('');
-      await Device.printDevices(devices, globals.logger);
-      return null;
+    } else if (devices.length > 1) {
+      if (deviceManager.hasSpecifiedDeviceId) {
+        globals.logger.printStatus(userMessages.flutterFoundSpecifiedDevices(devices.length, deviceManager.specifiedDeviceId!));
+        return null;
+      } else if (!deviceManager.hasSpecifiedAllDevices) {
+        if (globals.terminal.stdinHasTerminal) {
+          // If DeviceManager 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.
+          globals.logger.printStatus(userMessages.flutterMultipleDevicesFound);
+          await Device.printDevices(devices, globals.logger);
+          final Device chosenDevice = await _chooseOneOfAvailableDevices(devices);
+
+          // Update the [DeviceManager.specifiedDeviceId] so that we will not be prompted again.
+          deviceManager.specifiedDeviceId = chosenDevice.id;
+
+          devices = <Device>[chosenDevice];
+        } else {
+          // Show an error message asking the user to specify `-d all` if they
+          // want to run on multiple devices.
+          final List<Device> allDevices = await deviceManager.getAllConnectedDevices();
+          globals.logger.printStatus(userMessages.flutterSpecifyDeviceWithAllOption);
+          globals.logger.printStatus('');
+          await Device.printDevices(allDevices, globals.logger);
+          return null;
+        }
+      }
     }
+
     return devices;
   }
 
+  Future<void> _printUnsupportedDevice(DeviceManager deviceManager) async {
+    final List<Device> unsupportedDevices = await deviceManager.getDevices();
+    if (unsupportedDevices.isNotEmpty) {
+      final StringBuffer result = StringBuffer();
+      result.writeln(userMessages.flutterFoundButUnsupportedDevices);
+      result.writeAll(
+        (await Device.descriptions(unsupportedDevices))
+            .map((String desc) => desc)
+            .toList(),
+        '\n',
+      );
+      result.writeln();
+      result.writeln(userMessages.flutterMissPlatformProjects(
+        Device.devicesPlatformTypes(unsupportedDevices),
+      ));
+      globals.logger.printStatus(result.toString());
+    }
+  }
+
+  Future<Device> _chooseOneOfAvailableDevices(List<Device> devices) async {
+    _displayDeviceOptions(devices);
+    final String userInput =  await _readUserInput(devices.length);
+    if (userInput.toLowerCase() == 'q') {
+      throwToolExit('');
+    }
+    return devices[int.parse(userInput) - 1];
+  }
+
+  void _displayDeviceOptions(List<Device> devices) {
+    int count = 1;
+    for (final Device device in devices) {
+      globals.logger.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 + 1}', 'q', 'Q'],
+      displayAcceptedCharacters: false,
+      logger: globals.logger,
+      prompt: userMessages.flutterChooseOne,
+    );
+    return result;
+  }
+
   /// Find and return the target [Device] based upon currently connected
   /// devices and criteria entered by the user on the command line.
   /// If a device cannot be found that meets specified criteria,
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
index 0610be0..4c7482b 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -46,6 +46,7 @@
   group('attach', () {
     late StreamLogger logger;
     late FileSystem testFileSystem;
+    late TestDeviceManager testDeviceManager;
 
     setUp(() {
       Cache.disableLocking();
@@ -57,6 +58,7 @@
       );
       testFileSystem.directory('lib').createSync();
       testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync();
+      testDeviceManager = TestDeviceManager(logger: BufferLogger.test());
     });
 
     group('with one device and no specified target file', () {
@@ -92,7 +94,7 @@
           },
         );
 
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
         final Completer<void> completer = Completer<void>();
         final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
           if (message == '[verbose] Observatory URL on device: http://127.0.0.1:$devicePort') {
@@ -113,6 +115,7 @@
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
         Logger: () => logger,
+        DeviceManager: () => testDeviceManager,
         MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(
           mdnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}),
           logger: logger,
@@ -126,7 +129,7 @@
           fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort');
           return fakeLogReader;
         };
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
         final Completer<void> completer = Completer<void>();
         final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
           if (message == '[verbose] Observatory URL on device: http://127.0.0.1:$devicePort') {
@@ -147,6 +150,7 @@
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
         Logger: () => logger,
+        DeviceManager: () => testDeviceManager,
       });
 
       testUsingContext('Fails with tool exit on bad Observatory uri', () async {
@@ -156,12 +160,13 @@
           fakeLogReader.dispose();
           return fakeLogReader;
         };
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
         expect(() => createTestCommandRunner(AttachCommand()).run(<String>['attach']), throwsToolExit());
       }, overrides: <Type, Generator>{
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
         Logger: () => logger,
+        DeviceManager: () => testDeviceManager,
       });
 
       testUsingContext('accepts filesystem parameters', () async {
@@ -170,7 +175,7 @@
           fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort');
           return fakeLogReader;
         };
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
 
         const String filesystemScheme = 'foo';
         const String filesystemRoot = '/build-output/';
@@ -221,10 +226,11 @@
       }, overrides: <Type, Generator>{
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
+        DeviceManager: () => testDeviceManager,
       });
 
       testUsingContext('exits when ipv6 is specified and debug-port is not', () async {
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
 
         final AttachCommand command = AttachCommand();
         await expectLater(
@@ -237,6 +243,7 @@
       }, overrides: <Type, Generator>{
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
+        DeviceManager: () => testDeviceManager,
       },);
 
       testUsingContext('exits when observatory-port is specified and debug-port is not', () async {
@@ -245,7 +252,7 @@
           fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort');
           return fakeLogReader;
         };
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
 
         final AttachCommand command = AttachCommand();
         await expectLater(
@@ -258,6 +265,7 @@
       }, overrides: <Type, Generator>{
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
+        DeviceManager: () => testDeviceManager,
       },);
     });
 
@@ -276,7 +284,7 @@
       });
 
       testUsingContext('succeeds in ipv4 mode', () async {
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
 
         final Completer<void> completer = Completer<void>();
         final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
@@ -298,10 +306,11 @@
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
         Logger: () => logger,
+        DeviceManager: () => testDeviceManager,
       });
 
       testUsingContext('succeeds in ipv6 mode', () async {
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
 
         final Completer<void> completer = Completer<void>();
         final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
@@ -324,10 +333,11 @@
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
         Logger: () => logger,
+        DeviceManager: () => testDeviceManager,
       });
 
       testUsingContext('skips in ipv4 mode with a provided observatory port', () async {
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
 
         final Completer<void> completer = Completer<void>();
         final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
@@ -359,10 +369,11 @@
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
         Logger: () => logger,
+        DeviceManager: () => testDeviceManager,
       });
 
       testUsingContext('skips in ipv6 mode with a provided observatory port', () async {
-        testDeviceManager.addDevice(device);
+        testDeviceManager.devices = <Device>[device];
 
         final Completer<void> completer = Completer<void>();
         final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
@@ -395,6 +406,7 @@
         FileSystem: () => testFileSystem,
         ProcessManager: () => FakeProcessManager.any(),
         Logger: () => logger,
+        DeviceManager: () => testDeviceManager,
       });
     });
 
@@ -408,11 +420,12 @@
     }, overrides: <Type, Generator>{
       FileSystem: () => testFileSystem,
       ProcessManager: () => FakeProcessManager.any(),
+      DeviceManager: () => testDeviceManager,
     });
 
     testUsingContext('fails when targeted device is not Android with --device-user', () async {
       final FakeIOSDevice device = FakeIOSDevice();
-      testDeviceManager.addDevice(device);
+      testDeviceManager.devices = <Device>[device];
       expect(createTestCommandRunner(AttachCommand()).run(<String>[
         'attach',
         '--device-user',
@@ -421,12 +434,15 @@
     }, overrides: <Type, Generator>{
       FileSystem: () => testFileSystem,
       ProcessManager: () => FakeProcessManager.any(),
+      DeviceManager: () => testDeviceManager,
     });
 
     testUsingContext('exits when multiple devices connected', () async {
       final AttachCommand command = AttachCommand();
-      testDeviceManager.addDevice(FakeAndroidDevice(id: 'xx1'));
-      testDeviceManager.addDevice(FakeAndroidDevice(id: 'yy2'));
+      testDeviceManager.devices = <Device>[
+        FakeAndroidDevice(id: 'xx1'),
+        FakeAndroidDevice(id: 'yy2'),
+      ];
       await expectLater(
         createTestCommandRunner(command).run(<String>['attach']),
         throwsToolExit(),
@@ -438,6 +454,8 @@
     }, overrides: <Type, Generator>{
       FileSystem: () => testFileSystem,
       ProcessManager: () => FakeProcessManager.any(),
+      DeviceManager: () => testDeviceManager,
+      AnsiTerminal: () => FakeTerminal(stdinHasTerminal: false),
     });
 
     testUsingContext('Catches service disappeared error', () async {
@@ -457,7 +475,7 @@
         throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kServiceDisappeared, '');
       };
 
-      testDeviceManager.addDevice(device);
+      testDeviceManager.devices = <Device>[device];
       testFileSystem.file('lib/main.dart').createSync();
 
       final AttachCommand command = AttachCommand(hotRunnerFactory: hotRunnerFactory);
@@ -467,6 +485,7 @@
     }, overrides: <Type, Generator>{
       FileSystem: () => testFileSystem,
       ProcessManager: () => FakeProcessManager.any(),
+      DeviceManager: () => testDeviceManager,
     });
 
     testUsingContext('Does not catch generic RPC error', () async {
@@ -487,7 +506,7 @@
         throw vm_service.RPCError('flutter._listViews', RPCErrorCodes.kInvalidParams, '');
       };
 
-      testDeviceManager.addDevice(device);
+      testDeviceManager.devices = <Device>[device];
       testFileSystem.file('lib/main.dart').createSync();
 
       final AttachCommand command = AttachCommand(hotRunnerFactory: hotRunnerFactory);
@@ -497,6 +516,7 @@
     }, overrides: <Type, Generator>{
       FileSystem: () => testFileSystem,
       ProcessManager: () => FakeProcessManager.any(),
+      DeviceManager: () => testDeviceManager,
     });
   });
 }
@@ -791,6 +811,9 @@
 
   @override
   Category get category => Category.mobile;
+
+  @override
+  bool get ephemeral => true;
 }
 
 // Unfortunately Device, despite not being immutable, has an `operator ==`.
@@ -840,6 +863,12 @@
 
   @override
   final PlatformType platformType = PlatformType.ios;
+
+  @override
+  bool isSupported() => true;
+
+  @override
+  bool isSupportedForProject(FlutterProject project) => true;
 }
 
 class FakeMDnsClient extends Fake implements MDnsClient {
@@ -887,3 +916,24 @@
   @override
   void stop() {}
 }
+
+class TestDeviceManager extends DeviceManager {
+  TestDeviceManager({required this.logger}) : super(logger: logger);
+  List<Device> devices = <Device>[];
+
+  final BufferLogger logger;
+
+  @override
+  List<DeviceDiscovery> get deviceDiscoverers {
+    final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
+    devices.forEach(discoverer.addDevice);
+    return <DeviceDiscovery>[discoverer];
+  }
+}
+
+class FakeTerminal extends Fake implements AnsiTerminal {
+  FakeTerminal({this.stdinHasTerminal = true});
+
+  @override
+  final bool stdinHasTerminal;
+}
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart
index 46bfb7f..cc0e3d4 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/devices_test.dart
@@ -6,8 +6,6 @@
 
 import 'package:flutter_tools/src/android/android_sdk.dart';
 import 'package:flutter_tools/src/artifacts.dart';
-import 'package:flutter_tools/src/base/terminal.dart';
-import 'package:flutter_tools/src/base/user_messages.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/commands/devices.dart';
 import 'package:flutter_tools/src/device.dart';
@@ -133,7 +131,7 @@
 }
 
 class _FakeDeviceManager extends DeviceManager {
-  _FakeDeviceManager() : super(logger: testLogger, terminal: Terminal.test(), userMessages: userMessages);
+  _FakeDeviceManager() : super(logger: testLogger);
 
   @override
   Future<List<Device>> getAllConnectedDevices() =>
@@ -153,7 +151,7 @@
 }
 
 class NoDevicesManager extends DeviceManager {
-  NoDevicesManager() : super(logger: testLogger, terminal: Terminal.test(), userMessages: userMessages);
+  NoDevicesManager() : super(logger: testLogger);
 
   @override
   Future<List<Device>> getAllConnectedDevices() async => <Device>[];
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
index 966d552..224d18c 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
@@ -328,7 +328,7 @@
   Future<List<Device>> getDevices() async => devices;
 
   @override
-  Future<List<Device>> findTargetDevices(FlutterProject? flutterProject, {Duration? timeout}) async => devices;
+  Future<List<Device>> findTargetDevices(FlutterProject? flutterProject, {Duration? timeout, bool promptUserToChooseDevice = true}) async => devices;
 }
 
 class FailingFakeFlutterDriverFactory extends Fake implements FlutterDriverFactory {
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
index 8ad2c87..ecdac20 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -44,11 +44,11 @@
   });
 
   group('run', () {
-    late FakeDeviceManager mockDeviceManager;
+    late TestDeviceManager testDeviceManager;
     late FileSystem fileSystem;
 
     setUp(() {
-      mockDeviceManager = FakeDeviceManager();
+      testDeviceManager = TestDeviceManager(logger: BufferLogger.test());
       fileSystem = MemoryFileSystem.test();
     });
 
@@ -166,9 +166,7 @@
 
       testUsingContext('exits with a user message when no supported devices attached', () async {
         final RunCommand command = RunCommand();
-        mockDeviceManager
-          ..devices = <Device>[]
-          ..targetDevices = <Device>[];
+        testDeviceManager.devices = <Device>[];
 
         await expectLater(
           () => createTestCommandRunner(command).run(<String>[
@@ -184,7 +182,7 @@
           containsIgnoringWhitespace(userMessages.flutterNoSupportedDevices),
         );
       }, overrides: <Type, Generator>{
-        DeviceManager: () => mockDeviceManager,
+        DeviceManager: () => testDeviceManager,
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
         Cache: () => Cache.test(processManager: FakeProcessManager.any()),
@@ -193,9 +191,9 @@
       testUsingContext('exits and lists available devices when specified device not found', () async {
         final RunCommand command = RunCommand();
         final FakeDevice device = FakeDevice(isLocalEmulator: true);
-        mockDeviceManager
+        testDeviceManager
           ..devices = <Device>[device]
-          ..hasSpecifiedDeviceId = true;
+          ..specifiedDeviceId = 'invalid-device-id';
 
         await expectLater(
               () => createTestCommandRunner(command).run(<String>[
@@ -211,7 +209,7 @@
         expect(testLogger.statusText, contains('The following devices were found:'));
         expect(testLogger.statusText, contains('FakeDevice (mobile) • fake_device • ios •  (simulator)'));
       }, overrides: <Type, Generator>{
-        DeviceManager: () => mockDeviceManager,
+        DeviceManager: () => testDeviceManager,
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
         Cache: () => Cache.test(processManager: FakeProcessManager.any()),
@@ -220,9 +218,7 @@
       testUsingContext('fails when targeted device is not Android with --device-user', () async {
         final FakeDevice device = FakeDevice(isLocalEmulator: true);
 
-        mockDeviceManager
-          ..devices = <Device>[device]
-          ..targetDevices = <Device>[device];
+        testDeviceManager.devices = <Device>[device];
 
         final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates();
         await expectLater(createTestCommandRunner(command).run(<String>[
@@ -234,7 +230,7 @@
       }, overrides: <Type, Generator>{
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
-        DeviceManager: () => mockDeviceManager,
+        DeviceManager: () => testDeviceManager,
         Stdio: () => FakeStdio(),
         Cache: () => Cache.test(processManager: FakeProcessManager.any()),
       });
@@ -242,9 +238,7 @@
       testUsingContext('succeeds when targeted device is an Android device with --device-user', () async {
         final FakeDevice device = FakeDevice(isLocalEmulator: true, platformType: PlatformType.android);
 
-        mockDeviceManager
-          ..devices = <Device>[device]
-          ..targetDevices = <Device>[device];
+        testDeviceManager.devices = <Device>[device];
 
         final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates();
         await createTestCommandRunner(command).run(<String>[
@@ -257,7 +251,7 @@
       }, overrides: <Type, Generator>{
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
-        DeviceManager: () => mockDeviceManager,
+        DeviceManager: () => testDeviceManager,
         Stdio: () => FakeStdio(),
         Cache: () => Cache.test(processManager: FakeProcessManager.any()),
       });
@@ -285,9 +279,7 @@
           processManager: FakeProcessManager.any(),
         );
 
-        mockDeviceManager
-          ..devices = <Device>[device]
-          ..targetDevices = <Device>[device];
+        testDeviceManager.devices = <Device>[device];
 
         final RunCommand command = RunCommand();
         await expectLater(createTestCommandRunner(command).run(<String>[
@@ -297,7 +289,7 @@
       }, overrides: <Type, Generator>{
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
-        DeviceManager: () => mockDeviceManager,
+        DeviceManager: () => testDeviceManager,
         Stdio: () => FakeStdio(),
         Cache: () => Cache.test(processManager: FakeProcessManager.any()),
       });
@@ -327,9 +319,7 @@
           processManager: FakeProcessManager.any(),
         );
 
-        mockDeviceManager
-          ..devices = <Device>[device]
-          ..targetDevices = <Device>[device];
+        testDeviceManager.devices = <Device>[device];
 
         final RunCommand command = RunCommand();
         await expectLater(createTestCommandRunner(command).run(<String>[
@@ -339,17 +329,20 @@
       }, overrides: <Type, Generator>{
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
-        DeviceManager: () => mockDeviceManager,
+        DeviceManager: () => testDeviceManager,
         Stdio: () => FakeStdio(),
         Cache: () => Cache.test(processManager: FakeProcessManager.any()),
       });
 
       testUsingContext('shows unsupported devices when no supported devices are found',  () async {
         final RunCommand command = RunCommand();
-        final FakeDevice mockDevice = FakeDevice(targetPlatform: TargetPlatform.android_arm, isLocalEmulator: true, sdkNameAndVersion: 'api-14');
-        mockDeviceManager
-          ..devices = <Device>[mockDevice]
-          ..targetDevices = <Device>[];
+        final FakeDevice mockDevice = FakeDevice(
+          targetPlatform: TargetPlatform.android_arm,
+          isLocalEmulator: true,
+          sdkNameAndVersion: 'api-14',
+          isSupported: false,
+        );
+        testDeviceManager.devices = <Device>[mockDevice];
 
         await expectLater(
           () => createTestCommandRunner(command).run(<String>[
@@ -377,7 +370,7 @@
           ),
         );
       }, overrides: <Type, Generator>{
-        DeviceManager: () => mockDeviceManager,
+        DeviceManager: () => testDeviceManager,
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
         Cache: () => Cache.test(processManager: FakeProcessManager.any()),
@@ -389,13 +382,7 @@
           sdkNameAndVersion: 'iOS 13',
         )..startAppSuccess = false;
 
-        mockDeviceManager
-          ..devices = <Device>[
-            mockDevice,
-          ]
-          ..targetDevices = <Device>[
-            mockDevice,
-          ];
+        testDeviceManager.devices = <Device>[mockDevice];
 
         // Causes swift to be detected in the analytics.
         fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true);
@@ -412,7 +399,7 @@
       }, overrides: <Type, Generator>{
         Artifacts: () => artifacts,
         Cache: () => Cache.test(processManager: FakeProcessManager.any()),
-        DeviceManager: () => mockDeviceManager,
+        DeviceManager: () => testDeviceManager,
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
         Usage: () => usage,
@@ -423,13 +410,7 @@
         final FakeDevice mockDevice = FakeDevice(sdkNameAndVersion: 'iOS 13')
           ..startAppSuccess = false;
 
-        mockDeviceManager
-          ..devices = <Device>[
-            mockDevice,
-          ]
-          ..targetDevices = <Device>[
-            mockDevice,
-          ];
+        testDeviceManager.devices = <Device>[mockDevice];
 
         // Causes swift to be detected in the analytics.
         fs.currentDirectory.childDirectory('ios').childFile('AppDelegate.swift').createSync(recursive: true);
@@ -451,7 +432,7 @@
         AnsiTerminal: () => fakeTerminal,
         Artifacts: () => artifacts,
         Cache: () => Cache.test(processManager: FakeProcessManager.any()),
-        DeviceManager: () => mockDeviceManager,
+        DeviceManager: () => testDeviceManager,
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),
         Stdio: () => FakeStdio(),
@@ -462,9 +443,7 @@
         testUsingContext('enables multidex by default', () async {
           final DaemonCapturingRunCommand command = DaemonCapturingRunCommand();
           final FakeDevice device = FakeDevice();
-          mockDeviceManager
-          ..devices = <Device>[device]
-          ..targetDevices = <Device>[device];
+          testDeviceManager.devices = <Device>[device];
 
           await expectLater(
                 () => createTestCommandRunner(command).run(<String>[
@@ -480,7 +459,7 @@
         }, overrides: <Type, Generator>{
           Artifacts: () => artifacts,
           Cache: () => Cache.test(processManager: FakeProcessManager.any()),
-          DeviceManager: () => mockDeviceManager,
+          DeviceManager: () => testDeviceManager,
           FileSystem: () => fs,
           ProcessManager: () => FakeProcessManager.any(),
           Usage: () => usage,
@@ -491,9 +470,7 @@
         testUsingContext('can disable multidex with --no-multidex', () async {
           final DaemonCapturingRunCommand command = DaemonCapturingRunCommand();
           final FakeDevice device = FakeDevice();
-          mockDeviceManager
-          ..devices = <Device>[device]
-          ..targetDevices = <Device>[device];
+          testDeviceManager.devices = <Device>[device];
 
           await expectLater(
                 () => createTestCommandRunner(command).run(<String>[
@@ -510,7 +487,7 @@
         }, overrides: <Type, Generator>{
           Artifacts: () => artifacts,
           Cache: () => Cache.test(processManager: FakeProcessManager.any()),
-          DeviceManager: () => mockDeviceManager,
+          DeviceManager: () => testDeviceManager,
           FileSystem: () => fs,
           ProcessManager: () => FakeProcessManager.any(),
           Usage: () => usage,
@@ -521,9 +498,7 @@
         testUsingContext('can pass --device-user', () async {
           final DaemonCapturingRunCommand command = DaemonCapturingRunCommand();
           final FakeDevice device = FakeDevice(platformType: PlatformType.android);
-          mockDeviceManager
-          ..devices = <Device>[device]
-          ..targetDevices = <Device>[device];
+          testDeviceManager.devices = <Device>[device];
 
           await expectLater(
                 () => createTestCommandRunner(command).run(<String>[
@@ -541,7 +516,7 @@
         }, overrides: <Type, Generator>{
           Artifacts: () => artifacts,
           Cache: () => Cache.test(processManager: FakeProcessManager.any()),
-          DeviceManager: () => mockDeviceManager,
+          DeviceManager: () => testDeviceManager,
           FileSystem: () => fs,
           ProcessManager: () => FakeProcessManager.any(),
           Usage: () => usage,
@@ -621,21 +596,21 @@
     });
 
     testUsingContext('should only request artifacts corresponding to connected devices', () async {
-      mockDeviceManager.devices = <Device>[FakeDevice(targetPlatform: TargetPlatform.android_arm)];
+      testDeviceManager.devices = <Device>[FakeDevice(targetPlatform: TargetPlatform.android_arm)];
 
       expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
         DevelopmentArtifact.universal,
         DevelopmentArtifact.androidGenSnapshot,
       }));
 
-      mockDeviceManager.devices = <Device>[FakeDevice()];
+      testDeviceManager.devices = <Device>[FakeDevice()];
 
       expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
         DevelopmentArtifact.universal,
         DevelopmentArtifact.iOS,
       }));
 
-      mockDeviceManager.devices = <Device>[
+      testDeviceManager.devices = <Device>[
         FakeDevice(),
         FakeDevice(targetPlatform: TargetPlatform.android_arm),
       ];
@@ -646,7 +621,7 @@
         DevelopmentArtifact.androidGenSnapshot,
       }));
 
-      mockDeviceManager.devices = <Device>[
+      testDeviceManager.devices = <Device>[
         FakeDevice(targetPlatform: TargetPlatform.web_javascript),
       ];
 
@@ -655,7 +630,7 @@
         DevelopmentArtifact.web,
       }));
     }, overrides: <Type, Generator>{
-      DeviceManager: () => mockDeviceManager,
+      DeviceManager: () => testDeviceManager,
       Cache: () => Cache.test(processManager: FakeProcessManager.any()),
       FileSystem: () => MemoryFileSystem.test(),
       ProcessManager: () => FakeProcessManager.any(),
@@ -881,28 +856,11 @@
   });
 }
 
-class FakeDeviceManager extends Fake implements DeviceManager {
+class TestDeviceManager extends DeviceManager {
+  TestDeviceManager({required this.logger}) : super(logger: logger);
   List<Device> devices = <Device>[];
-  List<Device> targetDevices = <Device>[];
 
-  @override
-  String? specifiedDeviceId;
-
-  @override
-  bool hasSpecifiedAllDevices = false;
-
-  @override
-  bool hasSpecifiedDeviceId = false;
-
-  @override
-  Future<List<Device>> getDevices() async {
-    return devices;
-  }
-
-  @override
-  Future<List<Device>> findTargetDevices(FlutterProject? flutterProject, {Duration? timeout}) async {
-    return targetDevices;
-  }
+  final Logger logger;
 
   @override
   List<DeviceDiscovery> get deviceDiscoverers {
@@ -910,9 +868,6 @@
     devices.forEach(discoverer.addDevice);
     return <DeviceDiscovery>[discoverer];
   }
-
-  @override
-  Future<List<Device>> getAllConnectedDevices() async => devices;
 }
 
 class FakeAndroidSdk extends Fake implements AndroidSdk {
@@ -929,10 +884,12 @@
     TargetPlatform targetPlatform = TargetPlatform.ios,
     String sdkNameAndVersion = '',
     PlatformType platformType = PlatformType.ios,
+    bool isSupported = true,
   }): _isLocalEmulator = isLocalEmulator,
       _targetPlatform = targetPlatform,
       _sdkNameAndVersion = sdkNameAndVersion,
-      _platformType = platformType;
+      _platformType = platformType,
+      _isSupported = isSupported;
 
   static const int kSuccess = 1;
   static const int kFailure = -1;
@@ -940,6 +897,7 @@
   final bool _isLocalEmulator;
   final String _sdkNameAndVersion;
   final PlatformType _platformType;
+  final bool _isSupported;
 
   @override
   Category get category => Category.mobile;
@@ -970,7 +928,7 @@
   bool supported = true;
 
   @override
-  bool isSupportedForProject(FlutterProject flutterProject) => true;
+  bool isSupportedForProject(FlutterProject flutterProject) => _isSupported;
 
   @override
   bool isSupported() => supported;
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
index 8fa7008..22b737a 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
@@ -10,8 +10,6 @@
 import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/logger.dart';
-import 'package:flutter_tools/src/base/terminal.dart';
-import 'package:flutter_tools/src/base/user_messages.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/commands/test.dart';
 import 'package:flutter_tools/src/device.dart';
@@ -851,7 +849,7 @@
 }
 
 class _FakeDeviceManager extends DeviceManager {
-  _FakeDeviceManager(this._connectedDevices) : super(logger: testLogger, terminal: Terminal.test(), userMessages: userMessages);
+  _FakeDeviceManager(this._connectedDevices) : super(logger: testLogger);
 
   final List<Device> _connectedDevices;
 
diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart
index a21e341..e03ed0e 100644
--- a/packages/flutter_tools/test/general.shard/device_test.dart
+++ b/packages/flutter_tools/test/general.shard/device_test.dart
@@ -7,8 +7,6 @@
 import 'package:fake_async/fake_async.dart';
 import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/base/logger.dart';
-import 'package:flutter_tools/src/base/terminal.dart';
-import 'package:flutter_tools/src/base/user_messages.dart';
 import 'package:flutter_tools/src/base/utils.dart';
 import 'package:flutter_tools/src/build_info.dart';
 import 'package:flutter_tools/src/convert.dart';
@@ -30,7 +28,6 @@
       final DeviceManager deviceManager = TestDeviceManager(
         devices,
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
 
       expect(await deviceManager.getDevices(), devices);
@@ -56,7 +53,6 @@
           LongPollingDeviceDiscovery(),
         ],
         logger: logger,
-        terminal: Terminal.test(),
       );
 
       Future<void> expectDevice(String id, List<Device> expected) async {
@@ -85,7 +81,6 @@
           LongPollingDeviceDiscovery(),
         ],
         logger: logger,
-        terminal: Terminal.test(),
         wellKnownId: 'windows',
       );
 
@@ -114,7 +109,6 @@
           ThrowingPollingDeviceDiscovery(),
         ],
         logger: logger,
-        terminal: Terminal.test(),
       );
 
       Future<void> expectDevice(String id, List<Device> expected) async {
@@ -133,7 +127,6 @@
       final TestDeviceManager deviceManager = TestDeviceManager(
         <Device>[device1],
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       expect(await deviceManager.getAllConnectedDevices(), <Device>[device1]);
 
@@ -147,7 +140,6 @@
       final TestDeviceManager deviceManager = TestDeviceManager(
         <Device>[device1],
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       expect(await deviceManager.refreshAllConnectedDevices(), <Device>[device1]);
 
@@ -199,95 +191,13 @@
       final DeviceManager deviceManager = TestDeviceManager(
         devices,
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
 
       expect(filtered.single, ephemeralOne);
     });
 
-    testWithoutContext('choose first non-ephemeral device', () async {
-      final List<Device> devices = <Device>[
-        nonEphemeralOne,
-        nonEphemeralTwo,
-      ];
-      final FakeTerminal terminal = FakeTerminal()
-        ..setPrompt(<String>['1', '2', 'q', 'Q'], '1');
-
-      final DeviceManager deviceManager = TestDeviceManager(
-        devices,
-        logger: BufferLogger.test(),
-        terminal: terminal,
-      );
-      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
-
-      expect(filtered, <Device>[
-        nonEphemeralOne,
-      ]);
-    });
-
-    testWithoutContext('choose second non-ephemeral device', () async {
-      final List<Device> devices = <Device>[
-        nonEphemeralOne,
-        nonEphemeralTwo,
-      ];
-      final FakeTerminal terminal = FakeTerminal()
-        ..setPrompt(<String>['1', '2', 'q', 'Q'], '2');
-
-      final DeviceManager deviceManager = TestDeviceManager(
-        devices,
-        logger: BufferLogger.test(),
-        terminal: terminal,
-      );
-      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
-
-      expect(filtered, <Device>[
-        nonEphemeralTwo,
-      ]);
-    });
-
-    testWithoutContext('choose first ephemeral device', () async {
-      final List<Device> devices = <Device>[
-        ephemeralOne,
-        ephemeralTwo,
-      ];
-
-      final FakeTerminal terminal = FakeTerminal()
-        ..setPrompt(<String>['1', '2', 'q', 'Q'], '1');
-
-      final DeviceManager deviceManager = TestDeviceManager(
-        devices,
-        logger: BufferLogger.test(),
-        terminal: terminal,
-      );
-      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
-
-      expect(filtered, <Device>[
-        ephemeralOne,
-      ]);
-    });
-
-    testWithoutContext('choose second ephemeral device', () async {
-      final List<Device> devices = <Device>[
-        ephemeralOne,
-        ephemeralTwo,
-      ];
-      final FakeTerminal terminal = FakeTerminal()
-        ..setPrompt(<String>['1', '2', 'q', 'Q'], '2');
-
-      final DeviceManager deviceManager = TestDeviceManager(
-        devices,
-        logger: BufferLogger.test(),
-        terminal: terminal,
-      );
-      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
-
-      expect(filtered, <Device>[
-        ephemeralTwo,
-      ]);
-    });
-
-    testWithoutContext('choose non-ephemeral device', () async {
+    testWithoutContext('returns all devices when multiple non ephemeral devices are found', () async {
       final List<Device> devices = <Device>[
         ephemeralOne,
         ephemeralTwo,
@@ -295,40 +205,19 @@
         nonEphemeralTwo,
       ];
 
-      final FakeTerminal terminal = FakeTerminal()
-        ..setPrompt(<String>['1', '2', '3', '4', 'q', 'Q'], '3');
-
       final DeviceManager deviceManager = TestDeviceManager(
         devices,
         logger: BufferLogger.test(),
-        terminal: terminal,
       );
 
       final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
 
       expect(filtered, <Device>[
-        nonEphemeralOne,
-      ]);
-    });
-
-    testWithoutContext('exit from choose one of available devices', () async {
-      final List<Device> devices = <Device>[
         ephemeralOne,
         ephemeralTwo,
-      ];
-
-      final FakeTerminal terminal = FakeTerminal()
-        ..setPrompt(<String>['1', '2', 'q', 'Q'], 'q');
-
-      final DeviceManager deviceManager = TestDeviceManager(
-        devices,
-        logger: BufferLogger.test(),
-        terminal: terminal,
-      );
-      await expectLater(
-        () async => deviceManager.findTargetDevices(FakeFlutterProject()),
-        throwsToolExit(),
-      );
+        nonEphemeralOne,
+        nonEphemeralTwo,
+      ]);
     });
 
     testWithoutContext('Unsupported devices listed in all connected devices', () async {
@@ -340,7 +229,6 @@
       final DeviceManager deviceManager = TestDeviceManager(
         devices,
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       final List<Device> filtered = await deviceManager.getAllConnectedDevices();
 
@@ -355,11 +243,9 @@
         unsupported,
         unsupportedForProject,
       ];
-
       final DeviceManager deviceManager = TestDeviceManager(
         devices,
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
 
@@ -375,7 +261,6 @@
       final DeviceManager deviceManager = TestDeviceManager(
         devices,
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       final List<Device> filtered = await deviceManager.findTargetDevices(null);
 
@@ -390,7 +275,6 @@
       final DeviceManager deviceManager = TestDeviceManager(
         devices,
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       deviceManager.specifiedDeviceId = 'all';
 
@@ -409,7 +293,6 @@
       final DeviceManager deviceManager = TestDeviceManager(
         devices,
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       deviceManager.specifiedDeviceId = 'all';
 
@@ -421,6 +304,57 @@
       ]);
     });
 
+    testWithoutContext('Returns device with the specified id', () async {
+      final List<Device> devices = <Device>[
+        nonEphemeralOne,
+      ];
+      final DeviceManager deviceManager = TestDeviceManager(
+        devices,
+        logger: BufferLogger.test(),
+      );
+      deviceManager.specifiedDeviceId = nonEphemeralOne.id;
+
+      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
+
+      expect(filtered, <Device>[
+        nonEphemeralOne,
+      ]);
+    });
+
+    testWithoutContext('Returns multiple devices when multiple devices matches the specified id', () async {
+      final List<Device> devices = <Device>[
+        nonEphemeralOne,
+        nonEphemeralTwo,
+      ];
+      final DeviceManager deviceManager = TestDeviceManager(
+        devices,
+        logger: BufferLogger.test(),
+      );
+      deviceManager.specifiedDeviceId = 'nonEphemeral'; // This prefix matches both devices
+
+      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
+
+      expect(filtered, <Device>[
+        nonEphemeralOne,
+        nonEphemeralTwo,
+      ]);
+    });
+
+    testWithoutContext('Returns empty when device of specified id is not found', () async {
+      final List<Device> devices = <Device>[
+        nonEphemeralOne,
+      ];
+      final DeviceManager deviceManager = TestDeviceManager(
+        devices,
+        logger: BufferLogger.test(),
+      );
+      deviceManager.specifiedDeviceId = nonEphemeralTwo.id;
+
+      final List<Device> filtered = await deviceManager.findTargetDevices(FakeFlutterProject());
+
+      expect(filtered, <Device>[]);
+    });
+
     testWithoutContext('uses DeviceManager.isDeviceSupportedForProject instead of device.isSupportedForProject', () async {
       final List<Device> devices = <Device>[
         unsupported,
@@ -429,7 +363,6 @@
       final TestDeviceManager deviceManager = TestDeviceManager(
         devices,
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       deviceManager.isAlwaysSupportedForProjectOverride = true;
 
@@ -453,7 +386,6 @@
           deviceDiscovery,
         ],
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       deviceManager.specifiedDeviceId = ephemeralOne.id;
       final List<Device> filtered = await deviceManager.findTargetDevices(
@@ -479,7 +411,6 @@
           deviceDiscovery,
         ],
         logger: BufferLogger.test(),
-        terminal: Terminal.test(),
       );
       deviceManager.specifiedDeviceId = ephemeralOne.id;
       final List<Device> filtered = await deviceManager.findTargetDevices(
@@ -542,11 +473,10 @@
     List<Device> allDevices, {
     List<DeviceDiscovery>? deviceDiscoveryOverrides,
     required super.logger,
-    required super.terminal,
     String? wellKnownId,
   }) : _fakeDeviceDiscoverer = FakePollingDeviceDiscovery(),
        _deviceDiscoverers = <DeviceDiscovery>[],
-       super(userMessages: UserMessages()) {
+       super() {
     if (wellKnownId != null) {
       _fakeDeviceDiscoverer.wellKnownIds.add(wellKnownId);
     }
@@ -650,31 +580,3 @@
   @override
   List<String> get wellKnownIds => <String>[];
 }
-
-class FakeTerminal extends Fake implements Terminal {
-  @override
-  bool stdinHasTerminal = true;
-
-  @override
-  bool usesTerminalUi = true;
-
-  void setPrompt(List<String> characters, String result) {
-    _nextPrompt = characters;
-    _nextResult = result;
-  }
-
-  List<String>? _nextPrompt;
-  late String _nextResult;
-
-  @override
-  Future<String> promptForCharInput(
-    List<String> acceptedCharacters, {
-    Logger? logger,
-    String? prompt,
-    int? defaultChoiceIndex,
-    bool displayAcceptedCharacters = true,
-  }) async {
-    expect(acceptedCharacters, _nextPrompt);
-    return _nextResult;
-  }
-}
diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart
index 2d73435..765773e 100644
--- a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart
+++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart
@@ -11,11 +11,15 @@
 import 'package:flutter_tools/src/base/error_handling_io.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/base/signals.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
 import 'package:flutter_tools/src/base/time.dart';
+import 'package:flutter_tools/src/base/user_messages.dart';
 import 'package:flutter_tools/src/build_info.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/dart/pub.dart';
+import 'package:flutter_tools/src/device.dart';
 import 'package:flutter_tools/src/globals.dart' as globals;
 import 'package:flutter_tools/src/pre_run_validator.dart';
 import 'package:flutter_tools/src/project.dart';
@@ -25,6 +29,7 @@
 
 import '../../src/common.dart';
 import '../../src/context.dart';
+import '../../src/fake_devices.dart';
 import '../../src/test_flutter_command_runner.dart';
 import 'utils.dart';
 
@@ -669,6 +674,138 @@
       FileSystem: () => fileSystem,
       ProcessManager: () => processManager,
     });
+
+    group('findAllTargetDevices', () {
+      final FakeDevice device1 = FakeDevice('device1', 'device1');
+      final FakeDevice device2 = FakeDevice('device2', 'device2');
+      group('when specified device id', () {
+        testUsingContext('returns device when device is found', () async {
+          testDeviceManager.specifiedDeviceId = 'device-id';
+          testDeviceManager.addDevice(device1);
+          final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+          final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+          expect(devices, <Device>[device1]);
+        });
+
+        testUsingContext('show error when no device found', () async {
+          testDeviceManager.specifiedDeviceId = 'device-id';
+          final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+          final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+          expect(devices, null);
+          expect(testLogger.statusText, contains(UserMessages().flutterNoMatchingDevice('device-id')));
+        });
+
+        testUsingContext('show error when multiple devices found', () async {
+          testDeviceManager.specifiedDeviceId = 'device-id';
+          testDeviceManager.addDevice(device1);
+          testDeviceManager.addDevice(device2);
+          final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+          final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+          expect(devices, null);
+          expect(testLogger.statusText, contains(UserMessages().flutterFoundSpecifiedDevices(2, 'device-id')));
+        });
+      });
+
+      group('when specified all', () {
+        testUsingContext('can return one device', () async {
+          testDeviceManager.specifiedDeviceId = 'all';
+          testDeviceManager.addDevice(device1);
+          final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+          final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+          expect(devices, <Device>[device1]);
+        });
+
+        testUsingContext('can return multiple devices', () async {
+          testDeviceManager.specifiedDeviceId = 'all';
+          testDeviceManager.addDevice(device1);
+          testDeviceManager.addDevice(device2);
+          final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+          final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+          expect(devices, <Device>[device1, device2]);
+        });
+
+        testUsingContext('show error when no device found', () async {
+          testDeviceManager.specifiedDeviceId = 'all';
+          final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+          final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+          expect(devices, null);
+          expect(testLogger.statusText, contains(UserMessages().flutterNoDevicesFound));
+        });
+      });
+
+      group('when device not specified', () {
+        testUsingContext('returns one device when only one device connected', () async {
+          testDeviceManager.addDevice(device1);
+          final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+          final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+          expect(devices, <Device>[device1]);
+        });
+
+        testUsingContext('show error when no device found', () async {
+          final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+          final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+          expect(devices, null);
+          expect(testLogger.statusText, contains(UserMessages().flutterNoSupportedDevices));
+        });
+
+        testUsingContext('show error when multiple devices found and not connected to terminal', () async {
+          testDeviceManager.addDevice(device1);
+          testDeviceManager.addDevice(device2);
+          final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+          final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+          expect(devices, null);
+          expect(testLogger.statusText, contains(UserMessages().flutterSpecifyDeviceWithAllOption));
+        }, overrides: <Type, Generator>{
+          AnsiTerminal: () => FakeTerminal(stdinHasTerminal: false),
+        });
+
+        // Prompt to choose device when multiple devices found and connected to terminal
+        group('show prompt', () {
+          late FakeTerminal terminal;
+          setUp(() {
+            terminal = FakeTerminal();
+          });
+
+          testUsingContext('choose first device', () async {
+            testDeviceManager.addDevice(device1);
+            testDeviceManager.addDevice(device2);
+            terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '1');
+            final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+            final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+
+            expect(devices, <Device>[device1]);
+          }, overrides: <Type, Generator>{
+            AnsiTerminal: () => terminal,
+          });
+
+          testUsingContext('choose second device', () async {
+            testDeviceManager.addDevice(device1);
+            testDeviceManager.addDevice(device2);
+            terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '2');
+            final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+            final List<Device>? devices = await flutterCommand.findAllTargetDevices();
+
+            expect(devices, <Device>[device2]);
+          }, overrides: <Type, Generator>{
+            AnsiTerminal: () => terminal,
+          });
+
+          testUsingContext('exits without choosing device', () async {
+            testDeviceManager.addDevice(device1);
+            testDeviceManager.addDevice(device2);
+            terminal.setPrompt(<String>['1', '2', 'q', 'Q'], 'q');
+            final DummyFlutterCommand flutterCommand = DummyFlutterCommand();
+
+            await expectLater(
+              flutterCommand.findAllTargetDevices(),
+              throwsToolExit(),
+            );
+          }, overrides: <Type, Generator>{
+            AnsiTerminal: () => terminal,
+          });
+        });
+      });
+    });
   });
 }
 
@@ -823,3 +960,33 @@
     bool printProgress = true,
   }) async { }
 }
+
+class FakeTerminal extends Fake implements AnsiTerminal {
+  FakeTerminal({this.stdinHasTerminal = true});
+
+  @override
+  final bool stdinHasTerminal;
+
+  @override
+  bool usesTerminalUi = true;
+
+  void setPrompt(List<String> characters, String result) {
+    _nextPrompt = characters;
+    _nextResult = result;
+  }
+
+  List<String>? _nextPrompt;
+  late String _nextResult;
+
+  @override
+  Future<String> promptForCharInput(
+    List<String> acceptedCharacters, {
+    Logger? logger,
+    String? prompt,
+    int? defaultChoiceIndex,
+    bool displayAcceptedCharacters = true,
+  }) async {
+    expect(acceptedCharacters, _nextPrompt);
+    return _nextResult;
+  }
+}
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart
index d292e99..8a2dd5d 100644
--- a/packages/flutter_tools/test/src/context.dart
+++ b/packages/flutter_tools/test/src/context.dart
@@ -241,7 +241,7 @@
   }
 
   @override
-  Future<List<Device>> findTargetDevices(FlutterProject? flutterProject, { Duration? timeout }) async {
+  Future<List<Device>> findTargetDevices(FlutterProject? flutterProject, { Duration? timeout, bool promptUserToChooseDevice = true }) async {
     return devices;
   }
 }