Refactor core resident runner logic (#80462)

diff --git a/packages/flutter_tools/lib/src/base/file_system.dart b/packages/flutter_tools/lib/src/base/file_system.dart
index 6cf75ad..11b5046 100644
--- a/packages/flutter_tools/lib/src/base/file_system.dart
+++ b/packages/flutter_tools/lib/src/base/file_system.dart
@@ -41,18 +41,7 @@
   /// Appends a number to a filename in order to make it unique under a
   /// directory.
   File getUniqueFile(Directory dir, String baseName, String ext) {
-    final FileSystem fs = dir.fileSystem;
-    int i = 1;
-
-    while (true) {
-      final String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext';
-      final File file = fs.file(_fileSystem.path.join(dir.path, name));
-      if (!file.existsSync()) {
-        file.createSync(recursive: true);
-        return file;
-      }
-      i += 1;
-    }
+    return _getUniqueFile(dir, baseName, ext);
   }
 
   /// Appends a number to a directory name in order to make it unique under a
@@ -157,6 +146,27 @@
   }
 }
 
+File _getUniqueFile(Directory dir, String baseName, String ext) {
+  final FileSystem fs = dir.fileSystem;
+  int i = 1;
+
+  while (true) {
+    final String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext';
+    final File file = fs.file(dir.fileSystem.path.join(dir.path, name));
+    if (!file.existsSync()) {
+      file.createSync(recursive: true);
+      return file;
+    }
+    i += 1;
+  }
+}
+
+/// Appends a number to a filename in order to make it unique under a
+/// directory.
+File getUniqueFile(Directory dir, String baseName, String ext) {
+  return _getUniqueFile(dir, baseName, ext);
+}
+
 /// This class extends [local_fs.LocalFileSystem] in order to clean up
 /// directories and files that the tool creates under the system temporary
 /// directory when the tool exits either normally or when killed by a signal.
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index ea92183..f6a3f1f 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -29,6 +29,7 @@
 import '../run_cold.dart';
 import '../run_hot.dart';
 import '../runner/flutter_command.dart';
+import '../vmservice.dart';
 import '../web/web_runner.dart';
 
 const String protocolVersion = '0.6.0';
@@ -687,9 +688,16 @@
     if (app == null) {
       throw "app '$appId' not found";
     }
-
-    final Map<String, dynamic> result = await app.runner
-        .invokeFlutterExtensionRpcRawOnFirstIsolate(methodName, params: params);
+    final FlutterDevice device = app.runner.flutterDevices.first;
+    final List<FlutterView> views = await device.vmService.getFlutterViews();
+    final Map<String, dynamic> result = await device
+      .vmService
+      .invokeFlutterExtensionRpcRaw(
+        methodName,
+        args: params,
+        isolateId: views
+          .first.uiIsolate.id
+      );
     if (result == null) {
       throw 'method not available: $methodName';
     }
diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
index e988bc1..13ee47a 100644
--- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart
@@ -76,7 +76,6 @@
       systemClock: systemClock,
       fileSystem: fileSystem,
       logger: logger,
-      featureFlags: featureFlags,
     );
   }
 }
@@ -99,14 +98,12 @@
     @required SystemClock systemClock,
     @required Usage usage,
     @required UrlTunneller urlTunneller,
-    @required FeatureFlags featureFlags,
     ResidentDevtoolsHandlerFactory devtoolsHandler = createDefaultHandler,
   }) : _fileSystem = fileSystem,
        _logger = logger,
        _systemClock = systemClock,
        _usage = usage,
        _urlTunneller = urlTunneller,
-       _featureFlags = featureFlags,
        super(
           <FlutterDevice>[device],
           target: target ?? fileSystem.path.join('lib', 'main.dart'),
@@ -122,7 +119,6 @@
   final SystemClock _systemClock;
   final Usage _usage;
   final UrlTunneller _urlTunneller;
-  final FeatureFlags _featureFlags;
 
   FlutterDevice get device => flutterDevices.first;
   final FlutterProject flutterProject;
@@ -167,20 +163,7 @@
   FlutterVmService _instance;
 
   @override
-  bool get canHotRestart {
-    return true;
-  }
-
-  @override
-  Future<Map<String, dynamic>> invokeFlutterExtensionRpcRawOnFirstIsolate(
-    String method, {
-    FlutterDevice device,
-    Map<String, dynamic> params,
-  }) async {
-    final vmservice.Response response =
-        await _vmService.service.callServiceExtension(method, args: params);
-    return response.toJson();
-  }
+  bool get supportsRestart => true;
 
   @override
   Future<void> cleanupAfterSignal() async {
@@ -313,7 +296,7 @@
         ?.flutterPlatformOverride(
           isolateId: null,
         );
-      final String platform = nextPlatform(currentPlatform, _featureFlags);
+      final String platform = nextPlatform(currentPlatform);
       await _vmService
         ?.flutterPlatformOverride(
             platform: platform,
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 01cabab..8f2f73c 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -192,11 +192,11 @@
     );
   }
 
+  final TargetPlatform targetPlatform;
   final Device device;
   final ResidentCompiler generator;
   final BuildInfo buildInfo;
   final String userIdentifier;
-  final TargetPlatform targetPlatform;
 
   DevFSWriter devFSWriter;
   Stream<Uri> observatoryUris;
@@ -401,138 +401,16 @@
     return devFS.create();
   }
 
-  Future<void> debugDumpApp() async {
+  Future<List<vm_service.IsolateRef>> _getCurrentIsolates() async {
+    if (targetPlatform == TargetPlatform.web_javascript) {
+      final vm_service.VM vm = await vmService.service.getVM();
+      return vm.isolates;
+    }
     final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      final String data = await vmService.flutterDebugDumpApp(
-        isolateId: view.uiIsolate.id,
-      );
-      globals.printStatus(data);
-    }
-  }
-
-  Future<void> debugDumpRenderTree() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      final String data = await vmService.flutterDebugDumpRenderTree(
-        isolateId: view.uiIsolate.id,
-      );
-      globals.printStatus(data);
-    }
-  }
-
-  Future<void> debugDumpLayerTree() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      final String data = await vmService.flutterDebugDumpLayerTree(
-        isolateId: view.uiIsolate.id,
-      );
-      globals.printStatus(data);
-    }
-  }
-
-  Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      final String data = await vmService.flutterDebugDumpSemanticsTreeInTraversalOrder(
-        isolateId: view.uiIsolate.id,
-      );
-      globals.printStatus(data);
-    }
-  }
-
-  Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      final String data = await vmService.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(
-        isolateId: view.uiIsolate.id,
-      );
-      globals.printStatus(data);
-    }
-  }
-
-  Future<void> toggleDebugPaintSizeEnabled() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      await vmService.flutterToggleDebugPaintSizeEnabled(
-        isolateId: view.uiIsolate.id,
-      );
-    }
-  }
-
-  Future<void> toggleDebugCheckElevationsEnabled() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      await vmService.flutterToggleDebugCheckElevationsEnabled(
-        isolateId: view.uiIsolate.id,
-      );
-    }
-  }
-
-  Future<void> debugTogglePerformanceOverlayOverride() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      await vmService.flutterTogglePerformanceOverlayOverride(
-        isolateId: view.uiIsolate.id,
-      );
-    }
-  }
-
-  Future<void> toggleWidgetInspector() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      await vmService.flutterToggleWidgetInspector(
-        isolateId: view.uiIsolate.id,
-      );
-    }
-  }
-
-  Future<void> toggleInvertOversizedImages() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      await vmService.flutterToggleInvertOversizedImages(
-        isolateId: view.uiIsolate.id,
-      );
-    }
-  }
-
-  Future<void> toggleProfileWidgetBuilds() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    for (final FlutterView view in views) {
-      await vmService.flutterToggleProfileWidgetBuilds(
-        isolateId: view.uiIsolate.id,
-      );
-    }
-  }
-
-  Future<Brightness> toggleBrightness({ Brightness current }) async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    Brightness next;
-    if (current == Brightness.light) {
-      next = Brightness.dark;
-    } else if (current == Brightness.dark) {
-      next = Brightness.light;
-    }
-
-    for (final FlutterView view in views) {
-      next = await vmService.flutterBrightnessOverride(
-        isolateId: view.uiIsolate.id,
-        brightness: next,
-      );
-    }
-    return next;
-  }
-
-  Future<String> togglePlatform({ String from }) async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
-    final String to = nextPlatform(from, featureFlags);
-    for (final FlutterView view in views) {
-      await vmService.flutterPlatformOverride(
-        platform: to,
-        isolateId: view.uiIsolate.id,
-      );
-    }
-    return to;
+    return <vm_service.IsolateRef>[
+      for (FlutterView view in views)
+        view.uiIsolate,
+    ];
   }
 
   Future<void> startEchoingDeviceLog() async {
@@ -629,7 +507,6 @@
     return 0;
   }
 
-
   Future<int> runCold({
     ColdRunner coldRunner,
     String route,
@@ -755,8 +632,389 @@
   }
 }
 
+/// A subset of the [ResidentRunner] for delegating to attached flutter devices.
+abstract class ResidentHandlers {
+  List<FlutterDevice> get flutterDevices;
+
+  /// Whether the resident runner has hot reload and restart enabled.
+  bool get hotMode;
+
+  /// Whether the resident runner is connect to the device's VM Service.
+  bool get supportsServiceProtocol;
+
+  /// The application is running in debug mode.
+  bool get isRunningDebug;
+
+  /// The application is running in profile mode.
+  bool get isRunningProfile;
+
+  /// The application is running in release mode.
+  bool get isRunningRelease;
+
+  /// The resident runner should stay resident after establishing a connection with the
+  /// application.
+  bool get stayResident;
+
+  /// Whether all of the connected devices support hot restart.
+  ///
+  /// To prevent scenarios where only a subset of devices are hot restarted,
+  /// the runner requires that all attached devices can support hot restart
+  /// before enabling it.
+  bool get supportsRestart;
+
+  /// Whether all of the connected devices support gathering SkSL.
+  bool get supportsWriteSkSL;
+
+  /// Whether all of the connected devices support hot reload.
+  bool get canHotReload;
+
+  @protected
+  Logger get logger;
+
+  @protected
+  FileSystem get fileSystem;
+
+  /// Called to print help to the terminal.
+  void printHelp({ @required bool details });
+
+  /// Perfor a hot reload or hot restart of all attached applications.
+  ///
+  /// If [fullRestart] is true, a hot restart is performed. Otherwise a hot reload
+  /// is run instead. On web devices, this only performs a hot restart regardless of
+  /// the value of [fullRestart].
+  Future<OperationResult> restart({ bool fullRestart = false, bool pause = false, String reason }) {
+    final String mode = isRunningProfile ? 'profile' :isRunningRelease ? 'release' : 'this';
+    throw '${fullRestart ? 'Restart' : 'Reload'} is not supported in $mode mode';
+  }
+
+  /// Dump the application's current widget tree to the terminal.
+  Future<bool> debugDumpApp() async {
+    if (!supportsServiceProtocol) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        final String data = await device.vmService.flutterDebugDumpApp(
+          isolateId: view.id,
+        );
+        logger.printStatus(data);
+      }
+    }
+    return true;
+  }
+
+  /// Dump the application's current render tree to the terminal.
+  Future<bool> debugDumpRenderTree() async {
+    if (!supportsServiceProtocol) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        final String data = await device.vmService.flutterDebugDumpRenderTree(
+          isolateId: view.id,
+        );
+        logger.printStatus(data);
+      }
+    }
+    return true;
+  }
+
+  /// Dump the application's current layer tree to the terminal.
+  Future<bool> debugDumpLayerTree() async {
+    if (!supportsServiceProtocol || !isRunningDebug) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        final String data = await device.vmService.flutterDebugDumpLayerTree(
+          isolateId: view.id,
+        );
+        logger.printStatus(data);
+      }
+    }
+    return true;
+  }
+
+  /// Dump the application's current semantics tree to the terminal.
+  ///
+  /// If semantics are not enabled, nothing is returned.
+  Future<bool> debugDumpSemanticsTreeInTraversalOrder() async {
+    if (!supportsServiceProtocol) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        final String data = await device.vmService.flutterDebugDumpSemanticsTreeInTraversalOrder(
+          isolateId: view.id,
+        );
+        logger.printStatus(data);
+      }
+    }
+    return true;
+  }
+
+  /// Dump the application's current semantics tree to the terminal.
+  ///
+  /// If semantics are not enabled, nothing is returned.
+  Future<bool> debugDumpSemanticsTreeInInverseHitTestOrder() async {
+    if (!supportsServiceProtocol) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        final String data = await device.vmService.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(
+          isolateId: view.id,
+        );
+        logger.printStatus(data);
+      }
+    }
+    return true;
+  }
+
+  /// Toggle the "paint size" debugging feature.
+  Future<bool> debugToggleDebugPaintSizeEnabled() async {
+    if (!supportsServiceProtocol || !isRunningDebug) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        await device.vmService.flutterToggleDebugPaintSizeEnabled(
+          isolateId: view.id,
+        );
+      }
+    }
+    return true;
+  }
+
+  /// Toggle the "elevation check" debugging feature.
+  Future<bool> debugToggleDebugCheckElevationsEnabled() async {
+    if (!supportsServiceProtocol) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        await device.vmService.flutterToggleDebugCheckElevationsEnabled(
+          isolateId: view.id,
+        );
+      }
+    }
+    return true;
+  }
+
+  /// Toggle the performance overlay.
+  ///
+  /// This is not supported in web mode.
+  Future<bool> debugTogglePerformanceOverlayOverride() async {
+    if (!supportsServiceProtocol) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      if (device.targetPlatform == TargetPlatform.web_javascript) {
+        continue;
+      }
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        await device.vmService.flutterTogglePerformanceOverlayOverride(
+          isolateId: view.id,
+        );
+      }
+    }
+    return true;
+  }
+
+  /// Toggle the widget inspector.
+  Future<bool> debugToggleWidgetInspector() async {
+    if (!supportsServiceProtocol) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        await device.vmService.flutterToggleWidgetInspector(
+          isolateId: view.id,
+        );
+      }
+    }
+    return true;
+  }
+
+  /// Toggle the "invert images" debugging feature.
+  Future<bool> debugToggleInvertOversizedImages() async {
+    if (!supportsServiceProtocol || !isRunningDebug) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        await device.vmService.flutterToggleInvertOversizedImages(
+          isolateId: view.id,
+        );
+      }
+    }
+    return true;
+  }
+
+  /// Toggle the "profile widget builds" debugging feature.
+  Future<bool> debugToggleProfileWidgetBuilds() async {
+    if (!supportsServiceProtocol) {
+      return false;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        await device.vmService.flutterToggleProfileWidgetBuilds(
+          isolateId: view.id,
+        );
+      }
+    }
+    return true;
+  }
+
+  /// Toggle the operating system brightness (light or dart).
+  Future<bool> debugToggleBrightness() async {
+    if (!supportsServiceProtocol) {
+      return false;
+    }
+    final List<vm_service.IsolateRef> views = await flutterDevices.first._getCurrentIsolates();
+    final Brightness current = await flutterDevices.first.vmService.flutterBrightnessOverride(
+      isolateId: views.first.id,
+    );
+    Brightness next;
+    if (current == Brightness.light) {
+      next = Brightness.dark;
+    } else {
+      next = Brightness.light;
+    }
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        await device.vmService.flutterBrightnessOverride(
+          isolateId: view.id,
+          brightness: next,
+        );
+      }
+      logger.printStatus('Changed brightness to $next.');
+    }
+    return true;
+  }
+
+  /// Rotate the application through different `defaultTargetPlatform` values.
+  Future<bool> debugTogglePlatform() async {
+    if (!supportsServiceProtocol || !isRunningDebug) {
+      return false;
+    }
+    final List<vm_service.IsolateRef> views = await flutterDevices.first._getCurrentIsolates();
+    final String from = await flutterDevices
+      .first.vmService.flutterPlatformOverride(
+        isolateId: views.first.id,
+      );
+    final String to = nextPlatform(from);
+    for (final FlutterDevice device in flutterDevices) {
+      for (final vm_service.IsolateRef view in await device._getCurrentIsolates()) {
+        await device.vmService.flutterPlatformOverride(
+          platform: to,
+          isolateId: view.id,
+        );
+      }
+    }
+    logger.printStatus('Switched operating system to $to');
+    return true;
+  }
+
+  /// Write the SkSL shaders to a zip file in build directory.
+  ///
+  /// Returns the name of the file, or `null` on failures.
+  Future<String> writeSkSL() async {
+    if (!supportsWriteSkSL) {
+      throw Exception('writeSkSL is not supported by this runner.');
+    }
+    final List<FlutterView> views = await flutterDevices
+      .first
+      .vmService.getFlutterViews();
+    final Map<String, Object> data = await flutterDevices.first.vmService.getSkSLs(
+      viewId: views.first.id,
+    );
+    final Device device = flutterDevices.first.device;
+    return sharedSkSlWriter(device, data);
+  }
+
+  /// Take a screenshot on the provided [device].
+  ///
+  /// If the device has a connected vmservice, this method will attempt to hide
+  /// and restore the debug banner before taking the screenshot.
+  ///
+  /// Throws an [AssertionError] if [Device.supportsScreenshot] is not true.
+  Future<void> screenshot(FlutterDevice device) async {
+    assert(device.device.supportsScreenshot);
+
+    final Status status = logger.startProgress(
+      'Taking screenshot for ${device.device.name}...',
+    );
+    final File outputFile = getUniqueFile(
+      fileSystem.currentDirectory,
+      'flutter',
+      'png',
+    );
+    List<FlutterView> views = <FlutterView>[];
+    Future<bool> setDebugBanner(bool value) async {
+      try {
+        for (final FlutterView view in views) {
+          await device.vmService.flutterDebugAllowBanner(
+            value,
+            isolateId: view.uiIsolate.id,
+          );
+        }
+        return true;
+      } on Exception catch (error) {
+        status.cancel();
+        logger.printError('Error communicating with Flutter on the device: $error');
+        return false;
+      }
+    }
+
+    try {
+      if (supportsServiceProtocol && isRunningDebug) {
+        // Ensure that the vmService access is guarded by supportsServiceProtocol, it
+        // will be null in release mode.
+        views = await device.vmService.getFlutterViews();
+        if (!await setDebugBanner(false)) {
+          return;
+        }
+      }
+      try {
+        await device.device.takeScreenshot(outputFile);
+      } finally {
+        if (supportsServiceProtocol && isRunningDebug) {
+          await setDebugBanner(true);
+        }
+      }
+      final int sizeKB = outputFile.lengthSync() ~/ 1024;
+      status.stop();
+      logger.printStatus(
+        'Screenshot written to ${fileSystem.path.relative(outputFile.path)} (${sizeKB}kB).',
+      );
+    } on Exception catch (error) {
+      status.cancel();
+      logger.printError('Error taking screenshot: $error');
+    }
+  }
+
+  /// Remove sigusr signal handlers.
+  Future<void> cleanupAfterSignal();
+
+  /// Tear down the runner and leave the application running.
+  ///
+  /// This is not supported on web devices where the runner is running
+  /// the application server as well.
+  Future<void> detach();
+
+  /// Tear down the runner and exit the application.
+  Future<void> exit();
+
+  /// Run any source generators, such as localizations.
+  ///
+  /// These are automatically run during hot restart, but can be
+  /// triggered manually to see the updated generated code.
+  Future<void> runSourceGenerators();
+}
+
 // Shared code between different resident application runners.
-abstract class ResidentRunner {
+abstract class ResidentRunner extends ResidentHandlers {
   ResidentRunner(
     this.flutterDevices, {
     @required this.target,
@@ -788,12 +1046,19 @@
     _residentDevtoolsHandler = devtoolsHandler(DevtoolsLauncher.instance, this, globals.logger);
   }
 
-  @protected
-  @visibleForTesting
+  @override
+  Logger get logger => globals.logger;
+
+  @override
+  FileSystem get fileSystem => globals.fs;
+
+  @override
   final List<FlutterDevice> flutterDevices;
 
   final String target;
   final DebuggingOptions debuggingOptions;
+
+  @override
   final bool stayResident;
   final bool ipv6;
   final String _dillOutputPath;
@@ -812,6 +1077,10 @@
 
   bool _exited = false;
   Completer<int> _finished = Completer<int>();
+  BuildResult _lastBuild;
+  Environment _environment;
+
+  @override
   bool hotMode;
 
   /// Returns true if every device is streaming observatory URIs.
@@ -833,11 +1102,22 @@
   }
 
   bool get debuggingEnabled => debuggingOptions.debuggingEnabled;
+
+  @override
   bool get isRunningDebug => debuggingOptions.buildInfo.isDebug;
+
+  @override
   bool get isRunningProfile => debuggingOptions.buildInfo.isProfile;
+
+  @override
   bool get isRunningRelease => debuggingOptions.buildInfo.isRelease;
+
+  @override
   bool get supportsServiceProtocol => isRunningDebug || isRunningProfile;
+
+  @override
   bool get supportsWriteSkSL => supportsServiceProtocol;
+
   bool get trackWidgetCreation => debuggingOptions.buildInfo.trackWidgetCreation;
 
   // Returns the Uri of the first connected device for mobile,
@@ -850,38 +1130,14 @@
   /// Returns [true] if the resident runner exited after invoking [exit()].
   bool get exited => _exited;
 
-  /// Whether this runner can hot restart.
-  ///
-  /// To prevent scenarios where only a subset of devices are hot restarted,
-  /// the runner requires that all attached devices can support hot restart
-  /// before enabling it.
-  bool get canHotRestart {
-    return flutterDevices.every((FlutterDevice device) {
+  @override
+  bool get supportsRestart {
+    return isRunningDebug && flutterDevices.every((FlutterDevice device) {
       return device.device.supportsHotRestart;
     });
   }
 
-  /// Invoke an RPC extension method on the first attached ui isolate of the first device.
-  // TODO(jonahwilliams): Update/Remove this method when refactoring the resident
-  // runner to support a single flutter device.
-  Future<Map<String, dynamic>> invokeFlutterExtensionRpcRawOnFirstIsolate(
-    String method, {
-    FlutterDevice device,
-    Map<String, dynamic> params,
-  }) async {
-    device ??= flutterDevices.first;
-    final List<FlutterView> views = await device.vmService.getFlutterViews();
-    return device
-      .vmService
-      .invokeFlutterExtensionRpcRaw(
-        method,
-        args: params,
-        isolateId: views
-          .first.uiIsolate.id
-      );
-  }
-
-  /// Whether this runner can hot reload.
+  @override
   bool get canHotReload => hotMode;
 
   /// Start the app and keep the process running during its lifetime.
@@ -902,17 +1158,7 @@
     bool enableDevTools = false,
   });
 
-  bool get supportsRestart => false;
-
-  Future<OperationResult> restart({ bool fullRestart = false, bool pause = false, String reason }) {
-    final String mode = isRunningProfile ? 'profile' :
-        isRunningRelease ? 'release' : 'this';
-    throw '${fullRestart ? 'Restart' : 'Reload'} is not supported in $mode mode';
-  }
-
-
-  BuildResult _lastBuild;
-  Environment _environment;
+  @override
   Future<void> runSourceGenerators() async {
     _environment ??= Environment(
       artifacts: globals.artifacts,
@@ -944,23 +1190,6 @@
     globals.logger.printTrace('complete');
   }
 
-  /// Write the SkSL shaders to a zip file in build directory.
-  ///
-  /// Returns the name of the file, or `null` on failures.
-  Future<String> writeSkSL() async {
-    if (!supportsWriteSkSL) {
-      throw Exception('writeSkSL is not supported by this runner.');
-    }
-    final List<FlutterView> views = await flutterDevices
-      .first
-      .vmService.getFlutterViews();
-    final Map<String, Object> data = await flutterDevices.first.vmService.getSkSLs(
-      viewId: views.first.id,
-    );
-    final Device device = flutterDevices.first.device;
-    return sharedSkSlWriter(device, data);
-  }
-
   @protected
   void writeVmServiceFile() {
     if (debuggingOptions.vmserviceOutFile != null) {
@@ -975,6 +1204,7 @@
     }
   }
 
+  @override
   Future<void> exit() async {
     _exited = true;
     await residentDevtoolsHandler.shutdown();
@@ -984,6 +1214,7 @@
     await shutdownDartDevelopmentService();
   }
 
+  @override
   Future<void> detach() async {
     await residentDevtoolsHandler.shutdown();
     await stopEchoingDeviceLog();
@@ -992,212 +1223,6 @@
     appFinished();
   }
 
-  Future<bool> debugDumpApp() async {
-    if (!supportsServiceProtocol) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.debugDumpApp();
-    }
-    return true;
-  }
-
-  Future<bool> debugDumpRenderTree() async {
-    if (!supportsServiceProtocol) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.debugDumpRenderTree();
-    }
-    return true;
-  }
-
-  Future<bool> debugDumpLayerTree() async {
-    if (!supportsServiceProtocol || !isRunningDebug) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.debugDumpLayerTree();
-    }
-    return true;
-  }
-
-  Future<bool> debugDumpSemanticsTreeInTraversalOrder() async {
-    if (!supportsServiceProtocol) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.debugDumpSemanticsTreeInTraversalOrder();
-    }
-    return true;
-  }
-
-  Future<bool> debugDumpSemanticsTreeInInverseHitTestOrder() async {
-    if (!supportsServiceProtocol) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.debugDumpSemanticsTreeInInverseHitTestOrder();
-    }
-    return true;
-  }
-
-  Future<bool> debugToggleDebugPaintSizeEnabled() async {
-    if (!supportsServiceProtocol || !isRunningDebug) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.toggleDebugPaintSizeEnabled();
-    }
-    return true;
-  }
-
-  Future<bool> debugToggleDebugCheckElevationsEnabled() async {
-    if (!supportsServiceProtocol) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.toggleDebugCheckElevationsEnabled();
-    }
-    return true;
-  }
-
-  Future<bool> debugTogglePerformanceOverlayOverride() async {
-    if (!supportsServiceProtocol) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.debugTogglePerformanceOverlayOverride();
-    }
-    return true;
-  }
-
-  Future<bool> debugToggleWidgetInspector() async {
-    if (!supportsServiceProtocol) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.toggleWidgetInspector();
-    }
-    return true;
-  }
-
-  Future<bool> debugToggleInvertOversizedImages() async {
-    if (!supportsServiceProtocol || !isRunningDebug) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.toggleInvertOversizedImages();
-    }
-    return true;
-  }
-
-  Future<bool> debugToggleProfileWidgetBuilds() async {
-    if (!supportsServiceProtocol) {
-      return false;
-    }
-    for (final FlutterDevice device in flutterDevices) {
-      await device.toggleProfileWidgetBuilds();
-    }
-    return true;
-  }
-
-  Future<bool> debugToggleBrightness() async {
-    if (!supportsServiceProtocol) {
-      return false;
-    }
-    final Brightness brightness = await flutterDevices.first.toggleBrightness();
-    Brightness next;
-    for (final FlutterDevice device in flutterDevices) {
-      next = await device.toggleBrightness(
-        current: brightness,
-      );
-      globals.logger.printStatus('Changed brightness to $next.');
-    }
-    return true;
-  }
-
-  /// Take a screenshot on the provided [device].
-  ///
-  /// If the device has a connected vmservice, this method will attempt to hide
-  /// and restore the debug banner before taking the screenshot.
-  ///
-  /// Throws an [AssertionError] if [Device.supportsScreenshot] is not true.
-  Future<void> screenshot(FlutterDevice device) async {
-    assert(device.device.supportsScreenshot);
-
-    final Status status = globals.logger.startProgress(
-      'Taking screenshot for ${device.device.name}...',
-    );
-    final File outputFile = globals.fsUtils.getUniqueFile(
-      globals.fs.currentDirectory,
-      'flutter',
-      'png',
-    );
-    List<FlutterView> views = <FlutterView>[];
-    Future<bool> setDebugBanner(bool value) async {
-      try {
-        for (final FlutterView view in views) {
-          await device.vmService.flutterDebugAllowBanner(
-            value,
-            isolateId: view.uiIsolate.id,
-          );
-        }
-        return true;
-      } on Exception catch (error) {
-        status.cancel();
-        globals.printError('Error communicating with Flutter on the device: $error');
-        return false;
-      }
-    }
-
-    try {
-      if (supportsServiceProtocol && isRunningDebug) {
-        // Ensure that the vmService access is guarded by supportsServiceProtocol, it
-        // will be null in release mode.
-        views = await device.vmService.getFlutterViews();
-        if (!await setDebugBanner(false)) {
-          return;
-        }
-      }
-      try {
-        await device.device.takeScreenshot(outputFile);
-      } finally {
-        if (supportsServiceProtocol && isRunningDebug) {
-          await setDebugBanner(true);
-        }
-      }
-      final int sizeKB = outputFile.lengthSync() ~/ 1024;
-      status.stop();
-      globals.printStatus(
-        'Screenshot written to ${globals.fs.path.relative(outputFile.path)} (${sizeKB}kB).',
-      );
-    } on Exception catch (error) {
-      status.cancel();
-      globals.printError('Error taking screenshot: $error');
-    }
-  }
-
-  Future<bool> debugTogglePlatform() async {
-    if (!supportsServiceProtocol || !isRunningDebug) {
-      return false;
-    }
-    final List<FlutterView> views = await flutterDevices
-      .first
-      .vmService.getFlutterViews();
-    final String isolateId = views.first.uiIsolate.id;
-    final String from = await flutterDevices
-      .first.vmService.flutterPlatformOverride(
-        isolateId: isolateId,
-      );
-    String to;
-    for (final FlutterDevice device in flutterDevices) {
-      to = await device.togglePlatform(from: from);
-    }
-    globals.printStatus('Switched operating system to $to');
-    return true;
-  }
-
   Future<void> stopEchoingDeviceLog() async {
     await Future.wait<void>(
       flutterDevices.map<Future<void>>((FlutterDevice device) => device.stopEchoingDeviceLog())
@@ -1379,9 +1404,6 @@
     _reportedDebuggers = true;
   }
 
-  /// Called to print help to the terminal.
-  void printHelp({ @required bool details });
-
   void printHelpDetails() {
     if (flutterDevices.any((FlutterDevice d) => d.device.supportsScreenshot)) {
       commandHelp.s.print();
@@ -1413,14 +1435,11 @@
     }
   }
 
-  /// Called when a signal has requested we exit.
+  @override
   Future<void> cleanupAfterSignal();
 
   /// Called right before we exit.
   Future<void> cleanupAtFinish();
-
-  // Clears the screen.
-  void clearScreen() => globals.logger.clear();
 }
 
 class OperationResult {
@@ -1479,7 +1498,7 @@
   final bool _reportReady;
   final String _pidFile;
 
-  final ResidentRunner residentRunner;
+  final ResidentHandlers residentRunner;
   bool _processingUserRequest = false;
   StreamSubscription<void> subscription;
   File _actualPidFile;
@@ -1487,6 +1506,10 @@
   @visibleForTesting
   String lastReceivedCommand;
 
+  /// This is only a buffer logger in unit tests
+  @visibleForTesting
+  BufferLogger get logger => _logger as BufferLogger;
+
   void setupTerminal() {
     if (!_logger.quiet) {
       _logger.printStatus('');
@@ -1544,7 +1567,7 @@
       case 'b':
         return residentRunner.debugToggleBrightness();
       case 'c':
-        residentRunner.clearScreen();
+        _logger.clear();
         return true;
       case 'd':
       case 'D':
@@ -1597,7 +1620,7 @@
         return true;
       case 'R':
         // If hot restart is not supported for all devices, ignore the command.
-        if (!residentRunner.canHotRestart || !residentRunner.hotMode) {
+        if (!residentRunner.supportsRestart || !residentRunner.hotMode) {
           return false;
         }
         final OperationResult result = await residentRunner.restart(fullRestart: true);
@@ -1694,17 +1717,14 @@
 ///
 /// These values must match what is available in
 /// `packages/flutter/lib/src/foundation/binding.dart`.
-String nextPlatform(String currentPlatform, FeatureFlags featureFlags) {
+String nextPlatform(String currentPlatform) {
   switch (currentPlatform) {
     case 'android':
       return 'iOS';
     case 'iOS':
       return 'fuchsia';
     case 'fuchsia':
-      if (featureFlags.isMacOSEnabled) {
-        return 'macOS';
-      }
-      return 'android';
+      return 'macOS';
     case 'macOS':
       return 'android';
     default:
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 5bcd6a6..198fc93 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -10,6 +10,7 @@
 
 import 'base/common.dart';
 import 'base/file_system.dart';
+import 'base/logger.dart';
 import 'build_info.dart';
 import 'device.dart';
 import 'globals_null_migrated.dart' as globals;
@@ -51,7 +52,10 @@
   bool get canHotReload => false;
 
   @override
-  bool get canHotRestart => false;
+  Logger get logger => globals.logger;
+
+  @override
+  FileSystem get fileSystem => globals.fs;
 
   @override
   Future<int> run({
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 22d9a4a..d53ac8d 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -599,9 +599,6 @@
   }
 
   @override
-  bool get supportsRestart => true;
-
-  @override
   Future<OperationResult> restart({
     bool fullRestart = false,
     String reason,
@@ -670,7 +667,7 @@
     String reason,
     bool silent,
   }) async {
-    if (!canHotRestart) {
+    if (!supportsRestart) {
       return OperationResult(1, 'hotRestart not supported');
     }
     Status status;
@@ -1089,7 +1086,7 @@
   void printHelp({ @required bool details }) {
     globals.printStatus('Flutter run key commands.');
     commandHelp.r.print();
-    if (canHotRestart) {
+    if (supportsRestart) {
       commandHelp.R.print();
     }
     commandHelp.h.print(); // TODO(ianh): print different message if "details" is false
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
index 64aaf80..d84fdc9 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -1684,16 +1684,6 @@
     expect(testLogger.statusText, contains('1kB'));
   }));
 
-  testUsingContext('ResidentRunner clears the screen when it should', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    const String message = 'This should be cleared';
-    expect(testLogger.statusText, equals(''));
-    testLogger.printStatus(message);
-    expect(testLogger.statusText, equals(message + '\n'));  // printStatus makes a newline
-    residentRunner.clearScreen();
-    expect(testLogger.statusText, equals(''));
-  }));
-
   testUsingContext('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       listViews,
@@ -1906,361 +1896,6 @@
     expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
-  testUsingContext('ResidentRunner debugDumpApp calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugDumpApp(), true);
-    verify(mockFlutterDevice.debugDumpApp()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugDumpApp does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugDumpApp(), false);
-    verifyNever(mockFlutterDevice.debugDumpApp());
-  }));
-
-  testUsingContext('ResidentRunner debugDumpRenderTree calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugDumpRenderTree(), true);
-    verify(mockFlutterDevice.debugDumpRenderTree()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugDumpRenderTree does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugDumpRenderTree(), false);
-    verifyNever(mockFlutterDevice.debugDumpRenderTree());
-  }));
-
-  testUsingContext('ResidentRunner debugDumpLayerTree calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugDumpLayerTree(), true);
-    verify(mockFlutterDevice.debugDumpLayerTree()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugDumpLayerTree does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugDumpLayerTree(), false);
-    verifyNever(mockFlutterDevice.debugDumpLayerTree());
-  }));
-
-  testUsingContext('ResidentRunner debugDumpLayerTree does not call flutter device if not running in debug mode', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugDumpLayerTree(), false);
-    verifyNever(mockFlutterDevice.debugDumpLayerTree());
-  }));
-
-  testUsingContext('ResidentRunner debugDumpSemanticsTreeInTraversalOrder calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugDumpSemanticsTreeInTraversalOrder(), true);
-    verify(mockFlutterDevice.debugDumpSemanticsTreeInTraversalOrder()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugDumpSemanticsTreeInTraversalOrder does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugDumpSemanticsTreeInTraversalOrder(), false);
-    verifyNever(mockFlutterDevice.debugDumpSemanticsTreeInTraversalOrder());
-  }));
-
-  testUsingContext('ResidentRunner debugDumpSemanticsTreeInInverseHitTestOrder calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder(), true);
-    verify(mockFlutterDevice.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugDumpSemanticsTreeInInverseHitTestOrder does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder(), false);
-    verifyNever(mockFlutterDevice.debugDumpSemanticsTreeInInverseHitTestOrder());
-  }));
-
-  testUsingContext('ResidentRunner debugToggleDebugPaintSizeEnabled calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugToggleDebugPaintSizeEnabled(), true);
-    verify(mockFlutterDevice.toggleDebugPaintSizeEnabled()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugToggleDebugPaintSizeEnabled does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugToggleDebugPaintSizeEnabled(), false);
-    verifyNever(mockFlutterDevice.toggleDebugPaintSizeEnabled());
-  }));
-
-  testUsingContext('ResidentRunner debugToggleBrightness calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugToggleBrightness(), true);
-    verify(mockFlutterDevice.toggleBrightness()).called(2);
-  }));
-
-  testUsingContext('ResidentRunner debugToggleBrightness does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugToggleBrightness(), false);
-    verifyNever(mockFlutterDevice.toggleBrightness());
-  }));
-
-  testUsingContext('FlutterDevice.toggleBrightness invokes correct VM service request', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      const FakeVmServiceRequest(
-        method: 'ext.flutter.brightnessOverride',
-        args: <String, Object>{
-          'isolateId': '1',
-        },
-        jsonResponse: <String, Object>{
-          'value': 'Brightness.dark'
-        },
-      ),
-    ]);
-    final FlutterDevice flutterDevice = FlutterDevice(
-      mockDevice,
-      buildInfo: BuildInfo.debug,
-    );
-    flutterDevice.vmService = fakeVmServiceHost.vmService;
-
-    expect(await flutterDevice.toggleBrightness(), Brightness.dark);
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
-  }));
-
-  testUsingContext('ResidentRunner debugToggleInvertOversizedImages calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugToggleInvertOversizedImages(), true);
-    verify(mockFlutterDevice.toggleInvertOversizedImages()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugToggleInvertOversizedImages does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugToggleInvertOversizedImages(), false);
-    verifyNever(mockFlutterDevice.toggleInvertOversizedImages());
-  }));
-
-  testUsingContext('ResidentRunner debugToggleInvertOversizedImages does not call flutter device if in profile mode', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugToggleInvertOversizedImages(), false);
-    verifyNever(mockFlutterDevice.toggleInvertOversizedImages());
-  }));
-
-  testUsingContext('FlutterDevice.toggleInvertOversizedImages invokes correct VM service request', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      const FakeVmServiceRequest(
-        method: 'ext.flutter.invertOversizedImages',
-        args: <String, Object>{
-          'isolateId': '1',
-        },
-        jsonResponse: <String, Object>{
-          'value': 'false'
-        },
-      ),
-    ]);
-    final FlutterDevice flutterDevice = FlutterDevice(
-      mockDevice,
-      buildInfo: BuildInfo.debug,
-    );
-    flutterDevice.vmService = fakeVmServiceHost.vmService;
-
-    await flutterDevice.toggleInvertOversizedImages();
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
-  }));
-
-  testUsingContext('ResidentRunner debugToggleDebugCheckElevationsEnabled calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugToggleDebugCheckElevationsEnabled(), true);
-    verify(mockFlutterDevice.toggleDebugCheckElevationsEnabled()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugToggleDebugCheckElevationsEnabled does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugToggleDebugCheckElevationsEnabled(), false);
-    verifyNever(mockFlutterDevice.toggleDebugCheckElevationsEnabled());
-  }));
-
-  testUsingContext('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugTogglePerformanceOverlayOverride(), true);
-    verify(mockFlutterDevice.debugTogglePerformanceOverlayOverride()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugTogglePerformanceOverlayOverride does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugTogglePerformanceOverlayOverride(), false);
-    verifyNever(mockFlutterDevice.debugTogglePerformanceOverlayOverride());
-  }));
-
-
-  testUsingContext('ResidentRunner debugToggleWidgetInspector calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
-    expect(await residentRunner.debugToggleWidgetInspector(), true);
-    verify(mockFlutterDevice.toggleWidgetInspector()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugToggleWidgetInspector does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugToggleWidgetInspector(), false);
-    verifyNever(mockFlutterDevice.toggleWidgetInspector());
-  }));
-
-  testUsingContext('ResidentRunner debugToggleProfileWidgetBuilds calls flutter device', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    await residentRunner.debugToggleProfileWidgetBuilds();
-
-    verify(mockFlutterDevice.toggleProfileWidgetBuilds()).called(1);
-  }));
-
-  testUsingContext('ResidentRunner debugToggleProfileWidgetBuilds does not call flutter device if service protocol is unsupported', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-    residentRunner = HotRunner(
-      <FlutterDevice>[
-        mockFlutterDevice,
-      ],
-      stayResident: false,
-      debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
-      target: 'main.dart',
-      devtoolsHandler: createNoOpHandler,
-    );
-
-    expect(await residentRunner.debugToggleProfileWidgetBuilds(), false);
-    verifyNever(mockFlutterDevice.toggleProfileWidgetBuilds());
-  }));
-
   testUsingContext('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       listViews,
@@ -2811,11 +2446,11 @@
   }));
 
   testUsingContext('nextPlatform moves through expected platforms', () {
-    expect(nextPlatform('android', TestFeatureFlags()), 'iOS');
-    expect(nextPlatform('iOS', TestFeatureFlags()), 'fuchsia');
-    expect(nextPlatform('fuchsia', TestFeatureFlags()), 'android');
-    expect(nextPlatform('fuchsia', TestFeatureFlags(isMacOSEnabled: true)), 'macOS');
-    expect(() => nextPlatform('unknown', TestFeatureFlags()), throwsAssertionError);
+    expect(nextPlatform('android'), 'iOS');
+    expect(nextPlatform('iOS'), 'fuchsia');
+    expect(nextPlatform('fuchsia'), 'macOS');
+    expect(nextPlatform('macOS'), 'android');
+    expect(() => nextPlatform('unknown'), throwsAssertionError);
   });
 }
 
diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart
index 2033450..ad93839 100644
--- a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart
@@ -55,7 +55,6 @@
       ipv6: true,
       stayResident: true,
       urlTunneller: null,
-      featureFlags: TestFeatureFlags(),
       fileSystem: globals.fs,
       logger: globals.logger,
       systemClock: globals.systemClock,
diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
index f12042d..f65a4a4 100644
--- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
@@ -195,7 +195,6 @@
       fileSystem: fileSystem,
       logger: BufferLogger.test(),
       usage: globals.flutterUsage,
-      featureFlags: TestFeatureFlags(),
       systemClock: globals.systemClock,
     );
 
@@ -229,7 +228,7 @@
       fileSystem: fileSystem,
       logger: BufferLogger.test(),
       usage: globals.flutterUsage,
-      featureFlags: TestFeatureFlags(),
+
       systemClock: globals.systemClock,
     );
 
@@ -253,7 +252,6 @@
       fileSystem: fileSystem,
       logger: BufferLogger.test(),
       usage: globals.flutterUsage,
-      featureFlags: TestFeatureFlags(),
       systemClock: globals.systemClock,
     );
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
@@ -268,7 +266,6 @@
       fileSystem: fileSystem,
       logger: BufferLogger.test(),
       usage: globals.flutterUsage,
-      featureFlags: TestFeatureFlags(),
       systemClock: globals.systemClock,
     );
 
@@ -373,7 +370,6 @@
       fileSystem: fileSystem,
       logger: logger,
       usage: globals.flutterUsage,
-      featureFlags: TestFeatureFlags(),
       systemClock: globals.systemClock,
     );
 
@@ -398,7 +394,6 @@
       fileSystem: fileSystem,
       logger: BufferLogger.test(),
       usage: globals.flutterUsage,
-      featureFlags: TestFeatureFlags(),
       systemClock: globals.systemClock,
     );
 
@@ -517,7 +512,6 @@
       fileSystem: fileSystem,
       logger: BufferLogger.test(),
       usage: globals.flutterUsage,
-      featureFlags: TestFeatureFlags(),
       systemClock: globals.systemClock,
     );
     fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
@@ -1439,7 +1433,6 @@
       fileSystem: fileSystem,
       logger: logger,
       usage: globals.flutterUsage,
-      featureFlags: TestFeatureFlags(),
       systemClock: globals.systemClock,
     );
 
@@ -1487,7 +1480,6 @@
       fileSystem: fileSystem,
       logger: logger,
       usage: globals.flutterUsage,
-      featureFlags: TestFeatureFlags(),
       systemClock: globals.systemClock,
     );
 
@@ -1606,7 +1598,6 @@
     systemClock: systemClock ?? SystemClock.fixed(DateTime.now()),
     fileSystem: globals.fs,
     logger: logger ?? BufferLogger.test(),
-    featureFlags: TestFeatureFlags(),
   );
 }
 
diff --git a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart
index 7b7d59f..8a4408a 100644
--- a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart
+++ b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart
@@ -240,7 +240,7 @@
     });
 
     testWithoutContext('R - hotRestart supported and succeeds', () async {
-      when(mockResidentRunner.canHotRestart).thenReturn(true);
+      when(mockResidentRunner.supportsRestart).thenReturn(true);
       when(mockResidentRunner.hotMode).thenReturn(true);
       when(mockResidentRunner.restart(fullRestart: true))
         .thenAnswer((Invocation invocation) async {
@@ -252,7 +252,7 @@
     });
 
     testWithoutContext('R - hotRestart supported and fails', () async {
-      when(mockResidentRunner.canHotRestart).thenReturn(true);
+      when(mockResidentRunner.supportsRestart).thenReturn(true);
       when(mockResidentRunner.hotMode).thenReturn(true);
       when(mockResidentRunner.restart(fullRestart: true))
         .thenAnswer((Invocation invocation) async {
@@ -266,7 +266,7 @@
     });
 
     testWithoutContext('R - hotRestart supported and fails fatally', () async {
-      when(mockResidentRunner.canHotRestart).thenReturn(true);
+      when(mockResidentRunner.supportsRestart).thenReturn(true);
       when(mockResidentRunner.hotMode).thenReturn(true);
       when(mockResidentRunner.restart(fullRestart: true))
         .thenAnswer((Invocation invocation) async {
@@ -276,12 +276,23 @@
     });
 
     testWithoutContext('R - hot restart unsupported', () async {
-      when(mockResidentRunner.canHotRestart).thenReturn(false);
+      when(mockResidentRunner.supportsRestart).thenReturn(false);
       await terminalHandler.processTerminalInput('R');
 
       verifyNever(mockResidentRunner.restart(fullRestart: true));
     });
 
+    testWithoutContext('ResidentRunner clears the screen when it should', () async {
+      const String message = 'This should be cleared';
+
+      expect(testLogger.statusText, equals(''));
+      testLogger.printStatus(message);
+      expect(testLogger.statusText, equals(message + '\n'));  // printStatus makes a newline
+
+      await terminalHandler.processTerminalInput('c');
+      expect(testLogger.statusText, equals(''));
+    });
+
     testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder with service protocol', () async {
       await terminalHandler.processTerminalInput('S');