[flutter_tool] Reland: support --fast-start for Android applications (as an opt-in) (#46140)

diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index d0e8014..a98d0ef 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -279,6 +279,13 @@
   await selectSubshard(subshards);
 }
 
+// Example apps that should not be built by _runBuildTests`
+const List<String> _excludedExampleApplications = <String>[
+  // This application contains no platform code and cannot be built, except for
+  // as a part of a '--fast-start' Android application.
+  'splash',
+];
+
 /// Verifies that AOT, APK, and IPA (if on macOS) builds the examples apps
 /// without crashing. It does not actually launch the apps. That happens later
 /// in the devicelab. This is just a smoke-test. In particular, this will verify
@@ -290,6 +297,9 @@
     if (fileEntity is! Directory) {
       continue;
     }
+    if (_excludedExampleApplications.any(fileEntity.path.endsWith)) {
+      continue;
+    }
     final String examplePath = fileEntity.path;
     await _flutterBuildAot(examplePath);
     await _flutterBuildApk(examplePath);
@@ -763,6 +773,7 @@
     if (Platform.isMacOS) () => _runDevicelabTest('flutter_create_offline_test_mac'),
     if (Platform.isLinux) () => _runDevicelabTest('flutter_create_offline_test_linux'),
     if (Platform.isWindows) () => _runDevicelabTest('flutter_create_offline_test_windows'),
+    () => _runDevicelabTest('gradle_fast_start_test', environment: gradleEnvironment),
     // TODO(ianh): Fails on macOS looking for "dexdump", https://github.com/flutter/flutter/issues/42494
     if (!Platform.isMacOS) () => _runDevicelabTest('gradle_jetifier_test', environment: gradleEnvironment),
     () => _runDevicelabTest('gradle_non_android_plugin_test', environment: gradleEnvironment),
diff --git a/dev/devicelab/bin/tasks/gradle_fast_start_test.dart b/dev/devicelab/bin/tasks/gradle_fast_start_test.dart
new file mode 100644
index 0000000..73fdf12
--- /dev/null
+++ b/dev/devicelab/bin/tasks/gradle_fast_start_test.dart
@@ -0,0 +1,42 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter_devicelab/framework/apk_utils.dart';
+import 'package:flutter_devicelab/framework/framework.dart';
+import 'package:flutter_devicelab/framework/utils.dart';
+
+Future<void> main() async {
+  await task(() async {
+    try {
+      await runPluginProjectTest((FlutterPluginProject pluginProject) async {
+        section('APK content for task assembleDebug with --fast-start');
+        await pluginProject.runGradleTask('assembleDebug',
+            options: <String>['-Pfast-start=true']);
+
+        final Iterable<String> apkFiles = await getFilesInApk(pluginProject.debugApkPath);
+
+        checkCollectionContains<String>(<String>[
+          ...debugAssets,
+          ...baseApkFiles,
+          'lib/x86/libflutter.so',
+          'lib/x86_64/libflutter.so',
+          'lib/armeabi-v7a/libflutter.so',
+          'lib/arm64-v8a/libflutter.so',
+        ], apkFiles);
+
+        checkCollectionDoesNotContain<String>(<String>[
+          ...flutterAssets,
+        ], apkFiles);
+      });
+
+      return TaskResult.success(null);
+    } on TaskResult catch (taskResult) {
+      return taskResult;
+    } catch (e) {
+      return TaskResult.failure(e.toString());
+    }
+  });
+}
diff --git a/examples/splash/lib/main.dart b/examples/splash/lib/main.dart
new file mode 100644
index 0000000..3f55136
--- /dev/null
+++ b/examples/splash/lib/main.dart
@@ -0,0 +1,16 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+
+void main() {
+  runApp(
+    const DecoratedBox(
+      decoration: BoxDecoration(color: Colors.white),
+      child: Center(
+        child: FlutterLogo(size: 48),
+      ),
+    ),
+  );
+}
diff --git a/examples/splash/pubspec.yaml b/examples/splash/pubspec.yaml
new file mode 100644
index 0000000..40a17ca
--- /dev/null
+++ b/examples/splash/pubspec.yaml
@@ -0,0 +1,41 @@
+name: splash
+
+environment:
+  # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
+  sdk: ">=2.0.0-dev.68.0 <3.0.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+
+  collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+
+  archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  async: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  image: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  path: 1.6.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  pedantic: 1.8.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  petitparser: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  quiver: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  source_span: 1.5.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  test_api: 0.2.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+# PUBSPEC CHECKSUM: f789
diff --git a/examples/splash/test/splash_test.dart b/examples/splash/test/splash_test.dart
new file mode 100644
index 0000000..e1d597f
--- /dev/null
+++ b/examples/splash/test/splash_test.dart
@@ -0,0 +1,16 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:splash/main.dart' as entrypoint;
+
+void main() {
+  testWidgets('Displays flutter logo', (WidgetTester tester) async {
+    entrypoint.main();
+
+    expect(find.byType(FlutterLogo), findsOneWidget);
+  });
+}
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index fde4000..e852f2f 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -472,6 +472,15 @@
         return false
     }
 
+    /// Whether to build the debug app in "fast-start" mode.
+    private Boolean isFastStart() {
+        if (project.hasProperty("fast-start")) {
+            return project.property("fast-start").toBoolean()
+        }
+        return false
+    }
+
+
     private static Boolean shouldShrinkResources(Project project) {
         if (project.hasProperty("shrink")) {
             return project.property("shrink").toBoolean()
@@ -610,6 +619,7 @@
                 localEngineSrcPath this.localEngineSrcPath
                 targetPath target
                 verbose isVerbose()
+                fastStart isFastStart()
                 fileSystemRoots fileSystemRootsValue
                 fileSystemScheme fileSystemSchemeValue
                 trackWidgetCreation trackWidgetCreationValue
@@ -729,6 +739,8 @@
     String localEngine
     String localEngineSrcPath
     @Input
+    Boolean fastStart
+    @Input
     String targetPath
     @Optional
     Boolean verbose
@@ -769,9 +781,13 @@
         // cache.
         String[] ruleNames;
         if (buildMode == "debug") {
-          ruleNames = ["debug_android_application"]
+            if (fastStart) {
+                ruleNames = ["faststart_android_application"]
+            } else {
+                ruleNames = ["debug_android_application"]
+            }
         } else {
-          ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
+            ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
         }
         project.exec {
             executable flutterExecutable.absolutePath
@@ -788,7 +804,11 @@
             args "assemble"
             args "--depfile", "${intermediateDir}/flutter_build.d"
             args "--output", "${intermediateDir}"
-            args "-dTargetFile=${targetPath}"
+            if (!fastStart || buildMode != "debug") {
+                args "-dTargetFile=${targetPath}"
+            } else {
+                args "-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}"
+            }
             args "-dTargetPlatform=android"
             args "-dBuildMode=${buildMode}"
             if (extraFrontEndOptions != null) {
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 0678841..5a523f6 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -527,6 +527,7 @@
           androidBuildInfo: AndroidBuildInfo(
             debuggingOptions.buildInfo,
             targetArchs: <AndroidArch>[androidArch],
+            fastStart: debuggingOptions.fastStart
           ),
       );
       // Package has been built, so we can get the updated application ID and
@@ -644,6 +645,9 @@
   bool get supportsHotRestart => true;
 
   @override
+  bool get supportsFastStart => true;
+
+  @override
   Future<bool> stopApp(AndroidApk app) {
     final List<String> command = adbCommandForDevice(<String>['shell', 'am', 'force-stop', app.id]);
     return processUtils.stream(command).then<bool>(
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 7f904c7..4440d98 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -331,6 +331,9 @@
     // Don't use settings.gradle from the current project since it includes the plugins as subprojects.
     command.add('--settings-file=settings_aar.gradle');
   }
+  if (androidBuildInfo.fastStart) {
+    command.add('-Pfast-start=true');
+  }
   command.add(assembleTask);
 
   GradleHandledError detectedGradleError;
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 775461a..2f51d60 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -103,6 +103,7 @@
     ],
     this.splitPerAbi = false,
     this.shrink = false,
+    this.fastStart = false,
   });
 
   // The build info containing the mode and flavor.
@@ -120,6 +121,9 @@
 
   /// The target platforms for the build.
   final Iterable<AndroidArch> targetArchs;
+
+  /// Whether to bootstrap an empty application.
+  final bool fastStart;
 }
 
 /// A summary of the compilation strategy used for Dart.
diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
index 36f1898..b13f632 100644
--- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
@@ -423,7 +423,7 @@
   @override
   Future<OperationResult> restart({
     bool fullRestart = false,
-    bool pauseAfterRestart = false,
+    bool pause = false,
     String reason,
     bool benchmarkMode = false,
   }) async {
@@ -692,7 +692,7 @@
   @override
   Future<OperationResult> restart({
     bool fullRestart = false,
-    bool pauseAfterRestart = false,
+    bool pause = false,
     String reason,
     bool benchmarkMode = false,
   }) async {
diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart
index 5a030fb..1798d95 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/android.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart
@@ -31,10 +31,13 @@
   List<Source> get outputs => const <Source>[];
 
   @override
-  List<String> get depfiles => const <String>[
-    'flutter_assets.d',
+  List<String> get depfiles => <String>[
+    if (_copyAssets)
+      'flutter_assets.d',
   ];
 
+  bool get _copyAssets => true;
+
   @override
   Future<void> build(Environment environment) async {
     if (environment.defines[kBuildMode] == null) {
@@ -56,8 +59,10 @@
       fs.file(isolateSnapshotData)
           .copySync(outputDirectory.childFile('isolate_snapshot_data').path);
     }
-    final Depfile assetDepfile = await copyAssets(environment, outputDirectory);
-    assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
+    if (_copyAssets) {
+      final Depfile assetDepfile = await copyAssets(environment, outputDirectory);
+      assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d'));
+    }
   }
 
   @override
@@ -90,6 +95,17 @@
   ];
 }
 
+/// A minimal android application that does not include assets.
+class FastStartAndroidApplication extends DebugAndroidApplication {
+  const FastStartAndroidApplication();
+
+  @override
+  String get name => 'faststart_android_application';
+
+  @override
+  bool get _copyAssets => false;
+}
+
 /// An implementation of [AndroidAssetBundle] that only includes assets.
 class AotAndroidAssetBundle extends AndroidAssetBundle {
   const AotAndroidAssetBundle();
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index 74856e3..5265b5c 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -37,6 +37,7 @@
   DebugBundleLinuxAssets(),
   WebReleaseBundle(),
   DebugAndroidApplication(),
+  FastStartAndroidApplication(),
   ProfileAndroidApplication(),
   ReleaseAndroidApplication(),
   // These are one-off rules for bundle and aot compat
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 5c1b9db..a53b373 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -578,7 +578,7 @@
     }
 
     _inProgressHotReload = app._runInZone<OperationResult>(this, () {
-      return app.restart(fullRestart: fullRestart, pauseAfterRestart: pauseAfterRestart, reason: restartReason);
+      return app.restart(fullRestart: fullRestart, pause: pauseAfterRestart, reason: restartReason);
     });
     return _inProgressHotReload.whenComplete(() {
       _inProgressHotReload = null;
@@ -945,8 +945,8 @@
 
   _AppRunLogger _logger;
 
-  Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) {
-    return runner.restart(fullRestart: fullRestart, pauseAfterRestart: pauseAfterRestart, reason: reason);
+  Future<OperationResult> restart({ bool fullRestart = false, bool pause = false, String reason }) {
+    return runner.restart(fullRestart: fullRestart, pause: pause, reason: reason);
   }
 
   Future<OperationResult> reloadMethod({ String classId, String libraryId }) {
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index fb13cb7..222f207 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -187,6 +187,14 @@
         hide: true,
         help: 'Whether to automatically invoke webOnlyInitializePlatform.',
       )
+      ..addFlag('fast-start',
+        negatable: true,
+        defaultsTo: false,
+        hide: true,
+        help: 'Whether to quickly bootstrap applications with a minimal app. '
+              'Currently this is only supported on Android devices. This option '
+              'cannot be paired with --use-application-binary.'
+      )
       ..addOption(FlutterOptions.kExtraFrontEndOptions, hide: true)
       ..addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true)
       ..addMultiOption(FlutterOptions.kEnableExperiment,
@@ -284,6 +292,11 @@
     if (!runningWithPrebuiltApplication) {
       await super.validateCommand();
     }
+
+    if (boolArg('fast-start') && runningWithPrebuiltApplication) {
+      throwToolExit('--fast-start is not supported with --use-application-binary');
+    }
+
     devices = await findAllTargetDevices();
     if (devices == null) {
       throwToolExit(null);
@@ -322,6 +335,9 @@
         hostname: featureFlags.isWebEnabled ? stringArg('web-hostname') : '',
         port: featureFlags.isWebEnabled ? stringArg('web-port') : '',
         vmserviceOutFile: stringArg('vmservice-out-file'),
+        // Allow forcing fast-start to off to prevent doing more work on devices that
+        // don't support it.
+        fastStart: boolArg('fast-start') && devices.every((Device device) => device.supportsFastStart),
       );
     }
   }
@@ -384,6 +400,12 @@
     }
 
     for (Device device in devices) {
+      if (!device.supportsFastStart && boolArg('fast-start')) {
+        printStatus(
+          'Using --fast-start option with device ${device.name}, but this device '
+          'does not support it. Overriding the setting to false.'
+        );
+      }
       if (await device.isLocalEmulator) {
         if (await device.supportsHardwareRendering) {
           final bool enableSoftwareRendering = boolArg('enable-software-rendering') == true;
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 2b081ba..b335d3e 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -420,6 +420,9 @@
   /// application.
   bool get supportsScreenshot => false;
 
+  /// Whether the device supports the '--fast-start' development mode.
+  bool get supportsFastStart => false;
+
   /// Stop an app package on the current device.
   Future<bool> stopApp(covariant ApplicationPackage app);
 
@@ -534,6 +537,7 @@
     this.hostname,
     this.port,
     this.vmserviceOutFile,
+    this.fastStart = false,
    }) : debuggingEnabled = true;
 
   DebuggingOptions.disabled(this.buildInfo, { this.initializePlatform = true, this.port, this.hostname, this.cacheSkSL = false, })
@@ -550,7 +554,8 @@
       verboseSystemLogs = false,
       hostVmServicePort = null,
       deviceVmServicePort = null,
-      vmserviceOutFile = null;
+      vmserviceOutFile = null,
+      fastStart = false;
 
   final bool debuggingEnabled;
 
@@ -574,6 +579,7 @@
   final String hostname;
   /// A file where the vmservice URL should be written after the application is started.
   final String vmserviceOutFile;
+  final bool fastStart;
 
   bool get hasObservatoryPort => hostVmServicePort != null;
 }
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 0402b83..e666af6 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -714,7 +714,7 @@
 
   bool get supportsRestart => false;
 
-  Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) {
+  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';
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 9629942..a3fbe18 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -103,7 +103,7 @@
     bool pause = false,
   }) async {
     // TODO(cbernaschina): check that isolateId is the id of the UI isolate.
-    final OperationResult result = await restart(pauseAfterRestart: pause);
+    final OperationResult result = await restart(pause: pause);
     if (!result.isOk) {
       throw rpc.RpcException(
         rpc_error_code.INTERNAL_ERROR,
@@ -114,7 +114,7 @@
 
   Future<void> _restartService({ bool pause = false }) async {
     final OperationResult result =
-      await restart(fullRestart: true, pauseAfterRestart: pause);
+      await restart(fullRestart: true, pause: pause);
     if (!result.isOk) {
       throw rpc.RpcException(
         rpc_error_code.INTERNAL_ERROR,
@@ -263,6 +263,18 @@
       }
     }
 
+    // In fast-start mode, apps are initialized from a placeholder splashscreen
+    // app. We must do a restart here to load the program and assets for the
+    // real app.
+    if (debuggingOptions.fastStart) {
+      await restart(
+        fullRestart: true,
+        benchmarkMode: !debuggingOptions.startPaused,
+        reason: 'restart',
+        silent: true,
+      );
+    }
+
     appStartedCompleter?.complete();
 
     if (benchmarkMode) {
@@ -408,12 +420,10 @@
     Uri packagesUri,
     Uri assetsDirectoryUri,
   ) {
-    final List<Future<void>> futures = <Future<void>>[
-      for (FlutterView view in device.views) view.runFromSource(entryUri, packagesUri, assetsDirectoryUri),
-    ];
-    final Completer<void> completer = Completer<void>();
-    Future.wait(futures).whenComplete(() { completer.complete(null); });
-    return completer.future;
+    return Future.wait(<Future<void>>[
+      for (FlutterView view in device.views)
+        view.runFromSource(entryUri, packagesUri, assetsDirectoryUri),
+    ]);
   }
 
   Future<void> _launchFromDevFS(String mainScript) async {
@@ -510,9 +520,11 @@
       for (FlutterDevice device in flutterDevices) {
         for (FlutterView view in device.views) {
           isolateNotifications.add(
-            view.owner.vm.vmService.onIsolateEvent.then((Stream<ServiceEvent> serviceEvents) async {
+            view.owner.vm.vmService.onIsolateEvent
+              .then((Stream<ServiceEvent> serviceEvents) async {
               await for (ServiceEvent serviceEvent in serviceEvents) {
-                if (serviceEvent.owner.name.contains('_spawn') && serviceEvent.kind == ServiceEvent.kIsolateExit) {
+                if (serviceEvent.owner.name.contains('_spawn')
+                  && serviceEvent.kind == ServiceEvent.kIsolateExit) {
                   return;
                 }
               }
@@ -573,9 +585,10 @@
   @override
   Future<OperationResult> restart({
     bool fullRestart = false,
-    bool pauseAfterRestart = false,
     String reason,
     bool benchmarkMode = false,
+    bool silent = false,
+    bool pause = false,
   }) async {
     String targetPlatform;
     String sdkName;
@@ -602,8 +615,11 @@
         emulator: emulator,
         reason: reason,
         benchmarkMode: benchmarkMode,
+        silent: silent,
       );
-      printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
+      if (!silent) {
+        printStatus('Restarted application in ${getElapsedAsMilliseconds(timer.elapsed)}.');
+      }
       return result;
     }
     final OperationResult result = await _hotReloadHelper(
@@ -611,11 +627,13 @@
       sdkName: sdkName,
       emulator: emulator,
       reason: reason,
-      pauseAfterRestart: pauseAfterRestart,
+      pause: pause,
     );
     if (result.isOk) {
       final String elapsed = getElapsedAsMilliseconds(timer.elapsed);
-      printStatus('${result.message} in $elapsed.');
+      if (!silent) {
+        printStatus('${result.message} in $elapsed.');
+      }
     }
     return result;
   }
@@ -626,15 +644,19 @@
     bool emulator,
     String reason,
     bool benchmarkMode,
+    bool silent,
   }) async {
     if (!canHotRestart) {
       return OperationResult(1, 'hotRestart not supported');
     }
-    final Status status = logger.startProgress(
-      'Performing hot restart...',
-      timeout: timeoutConfiguration.fastOperation,
-      progressId: 'hot.restart',
-    );
+    Status status;
+    if (!silent) {
+      status = logger.startProgress(
+        'Performing hot restart...',
+        timeout: timeoutConfiguration.fastOperation,
+        progressId: 'hot.restart',
+      );
+    }
     OperationResult result;
     String restartEvent = 'restart';
     try {
@@ -662,7 +684,7 @@
         emulator: emulator,
         fullRestart: true,
         reason: reason).send();
-      status.cancel();
+      status?.cancel();
     }
     return result;
   }
@@ -672,7 +694,7 @@
     String sdkName,
     bool emulator,
     String reason,
-    bool pauseAfterRestart = false,
+    bool pause,
   }) async {
     final bool reloadOnTopOfSnapshot = _runningFromSnapshot;
     final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';
@@ -687,8 +709,8 @@
         targetPlatform: targetPlatform,
         sdkName: sdkName,
         emulator: emulator,
-        pause: pauseAfterRestart,
         reason: reason,
+        pause: pause,
         onSlow: (String message) {
           status?.cancel();
           status = logger.startProgress(
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 87a0a15..fad57ed 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -11,12 +11,15 @@
 import 'package:flutter_tools/src/artifacts.dart';
 import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/context.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.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/commands/run.dart';
 import 'package:flutter_tools/src/device.dart';
 import 'package:flutter_tools/src/features.dart';
+import 'package:flutter_tools/src/globals.dart';
 import 'package:flutter_tools/src/project.dart';
 import 'package:flutter_tools/src/reporting/reporting.dart';
 import 'package:flutter_tools/src/resident_runner.dart';
@@ -56,6 +59,72 @@
       }
     });
 
+    testUsingContext('does not support "--use-application-binary" and "--fast-start"', () async {
+      fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+      fs.file('pubspec.yaml').createSync();
+      fs.file('.packages').createSync();
+
+      final RunCommand command = RunCommand();
+      applyMocksToCommand(command);
+      try {
+        await createTestCommandRunner(command).run(<String>[
+          'run',
+          '--use-application-binary=app/bar/faz',
+          '--fast-start',
+          '--no-pub',
+          '--show-test-device',
+        ]);
+        fail('Expect exception');
+      } catch (e) {
+        expect(e.toString(), contains('--fast-start is not supported with --use-application-binary'));
+      }
+    }, overrides: <Type, Generator>{
+      FileSystem: () => MemoryFileSystem(),
+      ProcessManager: () => FakeProcessManager.any(),
+    });
+
+    testUsingContext('Forces fast start off for devices that do not support it', () async {
+      final MockDevice mockDevice = MockDevice(TargetPlatform.android_arm);
+      when(mockDevice.name).thenReturn('mockdevice');
+      when(mockDevice.supportsFastStart).thenReturn(false);
+      when(mockDevice.supportsHotReload).thenReturn(true);
+      when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async => false);
+      when(deviceManager.hasSpecifiedAllDevices).thenReturn(false);
+      when(deviceManager.findTargetDevices(any)).thenAnswer((Invocation invocation) {
+        return Future<List<Device>>.value(<Device>[mockDevice]);
+      });
+      when(deviceManager.getDevices()).thenAnswer((Invocation invocation) {
+        return Stream<Device>.value(mockDevice);
+      });
+      fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+      fs.file('pubspec.yaml').createSync();
+      fs.file('.packages').createSync();
+
+      final RunCommand command = RunCommand();
+      applyMocksToCommand(command);
+      try {
+        await createTestCommandRunner(command).run(<String>[
+          'run',
+          '--fast-start',
+          '--no-pub',
+        ]);
+        fail('Expect exception');
+      } catch (e) {
+        expect(e, isInstanceOf<ToolExit>());
+      }
+
+      final BufferLogger bufferLogger = logger as BufferLogger;
+      expect(bufferLogger.statusText, contains(
+        'Using --fast-start option with device mockdevice, but this device '
+        'does not support it. Overriding the setting to false.'
+      ));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => MemoryFileSystem(),
+      ProcessManager: () => FakeProcessManager.any(),
+      DeviceManager: () => MockDeviceManager(),
+    });
+
+
     group('run app', () {
       MemoryFileSystem fs;
       MockArtifacts mockArtifacts;
@@ -166,6 +235,7 @@
         final MockDevice mockDevice = MockDevice(TargetPlatform.ios);
         when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) => Future<bool>.value(false));
         when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(MockDeviceLogReader());
+        when(mockDevice.supportsFastStart).thenReturn(true);
         // App fails to start because we're only interested in usage
         when(mockDevice.startApp(
           any,
@@ -498,6 +568,9 @@
   bool get supportsHotReload => false;
 
   @override
+  bool get supportsFastStart => false;
+
+  @override
   Future<String> get sdkNameAndVersion => Future<String>.value('');
 
   @override
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 39b55c6..0dd418d 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -154,6 +154,41 @@
     expect(onAppStart.isCompleted, true);
   }));
 
+  test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
+    when(mockDevice.supportsHotRestart).thenReturn(true);
+    when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
+      return 'Example';
+    });
+    when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
+      return TargetPlatform.android_arm;
+    });
+    when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
+      return false;
+    });
+    residentRunner = HotRunner(
+      <FlutterDevice>[
+        mockFlutterDevice,
+      ],
+      stayResident: false,
+      debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, fastStart: true, startPaused: true),
+    );
+    final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
+    final Completer<void> onAppStart = Completer<void>.sync();
+    final Future<int> result = residentRunner.attach(
+      appStartedCompleter: onAppStart,
+      connectionInfoCompleter: onConnectionInfo,
+    );
+    final Future<DebugConnectionInfo> connectionInfo = onConnectionInfo.future;
+
+    expect(await result, 0);
+
+    verify(mockFlutterDevice.initLogReader()).called(1);
+
+    expect(onConnectionInfo.isCompleted, true);
+    expect((await connectionInfo).baseUri, 'foo://bar');
+    expect(onAppStart.isCompleted, true);
+  }));
+
   test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
     when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
       return 'Example';
diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart
index 4279b7a..ff881e1 100644
--- a/packages/flutter_tools/test/integration.shard/test_driver.dart
+++ b/packages/flutter_tools/test/integration.shard/test_driver.dart
@@ -497,7 +497,7 @@
     // fast.
     unawaited(_process.exitCode.then((_) {
       if (!prematureExitGuard.isCompleted) {
-        prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}');
+        prematureExitGuard.completeError('Process existed prematurely: ${args.join(' ')}: $_errorBuffer');
       }
     }));