improve progress display when running apps; speed up startup (#9475)

* improve progress display when running apps; speed up startup

* review comments
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 9bc7f22..c83ad4a 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -222,7 +222,7 @@
   }
 
   @override
-  bool installApp(ApplicationPackage app) {
+  Future<bool> installApp(ApplicationPackage app) async {
     final AndroidApk apk = app;
     if (!fs.isFileSync(apk.apkPath)) {
       printError('"${apk.apkPath}" does not exist.');
@@ -233,16 +233,18 @@
       return false;
 
     final Status status = logger.startProgress('Installing ${apk.apkPath}...', expectSlowOperation: true);
-    final String installOut = runCheckedSync(adbCommandForDevice(<String>['install', '-r', apk.apkPath]));
+    final RunResult installResult = await runCheckedAsync(adbCommandForDevice(<String>['install', '-r', apk.apkPath]));
     status.stop();
     final RegExp failureExp = new RegExp(r'^Failure.*$', multiLine: true);
-    final String failure = failureExp.stringMatch(installOut);
+    final String failure = failureExp.stringMatch(installResult.stdout);
     if (failure != null) {
       printError('Package install error: $failure');
       return false;
     }
 
-    runCheckedSync(adbCommandForDevice(<String>['shell', 'echo', '-n', _getSourceSha1(app), '>', _getDeviceSha1Path(app)]));
+    await runCheckedAsync(adbCommandForDevice(<String>[
+      'shell', 'echo', '-n', _getSourceSha1(app), '>', _getDeviceSha1Path(app)
+    ]));
     return true;
   }
 
@@ -262,7 +264,7 @@
     return true;
   }
 
-  bool _installLatestApp(ApplicationPackage package) {
+  Future<bool> _installLatestApp(ApplicationPackage package) async {
     final bool wasInstalled = isAppInstalled(package);
     if (wasInstalled) {
       if (isLatestBuildInstalled(package)) {
@@ -271,7 +273,7 @@
       }
     }
     printTrace('Installing APK.');
-    if (!installApp(package)) {
+    if (!await installApp(package)) {
       printTrace('Warning: Failed to install APK.');
       if (wasInstalled) {
         printStatus('Uninstalling old version...');
@@ -279,7 +281,7 @@
           printError('Error: Uninstalling old version failed.');
           return false;
         }
-        if (!installApp(package)) {
+        if (!await installApp(package)) {
           printError('Error: Failed to install APK again.');
           return false;
         }
@@ -325,7 +327,7 @@
     printTrace("Stopping app '${package.name}' on $name.");
     await stopApp(package);
 
-    if (!_installLatestApp(package))
+    if (!await _installLatestApp(package))
       return new LaunchResult.failed();
 
     final bool traceStartup = platformArgs['trace-startup'] ?? false;
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index a4e0942..b712bda 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -23,6 +23,8 @@
 const String gradleAppOutV1 = 'android/app/build/outputs/apk/app-debug.apk';
 const String gradleAppOutDirV1 = 'android/app/build/outputs/apk';
 
+String _cachedGradleAppOutDirV2;
+
 enum FlutterPluginVersion {
   none,
   v1,
@@ -54,7 +56,7 @@
   return FlutterPluginVersion.none;
 }
 
-String get gradleAppOut {
+String getGradleAppOut() {
   switch (flutterPluginVersion) {
     case FlutterPluginVersion.none:
       // Fall through. Pretend we're v1, and just go with it.
@@ -63,12 +65,18 @@
     case FlutterPluginVersion.managed:
       // Fall through. The managed plugin matches plugin v2 for now.
     case FlutterPluginVersion.v2:
-      return '$gradleAppOutDirV2/app.apk';
+      return '${getGradleAppOutDirV2()}/app.apk';
   }
   return null;
 }
 
-String get gradleAppOutDirV2 {
+String getGradleAppOutDirV2() {
+  _cachedGradleAppOutDirV2 ??= _calculateGradleAppOutDirV2();
+  return _cachedGradleAppOutDirV2;
+}
+
+// Note: this call takes about a second to complete.
+String _calculateGradleAppOutDirV2() {
   final String gradle = ensureGradle();
   ensureLocalProperties();
   try {
@@ -224,7 +232,7 @@
   if (exitcode != 0)
     throwToolExit('Gradle build failed: $exitcode', exitCode: exitcode);
 
-  final String buildDirectory = gradleAppOutDirV2;
+  final String buildDirectory = getGradleAppOutDirV2();
   final String apkFilename = 'app-$buildModeName.apk';
   final File apkFile = fs.file('$buildDirectory/$apkFilename');
   // Copy the APK to app.apk, so `flutter run`, `flutter install`, etc. can find it.
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 0c4a9a3..52923ba 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -83,16 +83,16 @@
     String apkPath;
 
     if (isProjectUsingGradle()) {
-      if (fs.file(gradleAppOut).existsSync()) {
+      if (fs.file(getGradleAppOut()).existsSync()) {
         // Grab information from the .apk. The gradle build script might alter
         // the application Id, so we need to look at what was actually built.
-        return new AndroidApk.fromApk(gradleAppOut);
+        return new AndroidApk.fromApk(getGradleAppOut());
       }
       // The .apk hasn't been built yet, so we work with what we have. The run
       // command will grab a new AndroidApk after building, to get the updated
       // IDs.
       manifestPath = gradleManifestPath;
-      apkPath = gradleAppOut;
+      apkPath = getGradleAppOut();
     } else {
       manifestPath = fs.path.join('android', 'AndroidManifest.xml');
       apkPath = fs.path.join(getAndroidBuildDirectory(), 'app.apk');
diff --git a/packages/flutter_tools/lib/src/commands/install.dart b/packages/flutter_tools/lib/src/commands/install.dart
index 30e71d4..063585e 100644
--- a/packages/flutter_tools/lib/src/commands/install.dart
+++ b/packages/flutter_tools/lib/src/commands/install.dart
@@ -37,12 +37,12 @@
 
     printStatus('Installing $package to $device...');
 
-    if (!installApp(device, package))
+    if (!await installApp(device, package))
       throwToolExit('Install failed');
   }
 }
 
-bool installApp(Device device, ApplicationPackage package, { bool uninstall: true }) {
+Future<bool> installApp(Device device, ApplicationPackage package, { bool uninstall: true }) async {
   if (package == null)
     return false;
 
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 7489153..9a76b7e 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -150,7 +150,7 @@
   bool isLatestBuildInstalled(ApplicationPackage app);
 
   /// Install an app package on the current device
-  bool installApp(ApplicationPackage app);
+  Future<bool> installApp(ApplicationPackage app);
 
   /// Uninstall an app package from the current device
   bool uninstallApp(ApplicationPackage app);
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
index 3984567..0c4ce20 100644
--- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
+++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -49,7 +49,7 @@
   bool isLatestBuildInstalled(ApplicationPackage app) => false;
 
   @override
-  bool installApp(ApplicationPackage app) => false;
+  Future<bool> installApp(ApplicationPackage app) => new Future<bool>.value(false);
 
   @override
   bool uninstallApp(ApplicationPackage app) => false;
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index f9cd43b..fb6bc3d 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -155,7 +155,7 @@
   bool isLatestBuildInstalled(ApplicationPackage app) => false;
 
   @override
-  bool installApp(ApplicationPackage app) {
+  Future<bool> installApp(ApplicationPackage app) async {
     final IOSApp iosApp = app;
     final Directory bundle = fs.directory(iosApp.deviceBundlePath);
     if (!bundle.existsSync()) {
@@ -197,8 +197,7 @@
     bool applicationNeedsRebuild: false,
   }) async {
     if (!prebuiltApplication) {
-      // TODO(chinmaygarde): Use checked, mainPath, route.
-      // TODO(devoncarew): Handle startPaused, debugPort.
+      // TODO(chinmaygarde): Use mainPath, route.
       printTrace('Building ${app.name} for $id');
 
       // Step 1: Build the precompiled/DBC application if necessary.
@@ -210,7 +209,7 @@
         return new LaunchResult.failed();
       }
     } else {
-      if (!installApp(app))
+      if (!await installApp(app))
         return new LaunchResult.failed();
     }
 
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index fbd99dd..6fd46b8 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -343,7 +343,7 @@
   bool isLatestBuildInstalled(ApplicationPackage app) => false;
 
   @override
-  bool installApp(ApplicationPackage app) {
+  Future<bool> installApp(ApplicationPackage app) async {
     try {
       final IOSApp iosApp = app;
       SimControl.instance.install(id, iosApp.simulatorBundlePath);
@@ -435,7 +435,7 @@
         return new LaunchResult.failed();
       }
     } else {
-      if (!installApp(app))
+      if (!await installApp(app))
         return new LaunchResult.failed();
     }
 
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 4db138c..d114a7f 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -53,6 +53,14 @@
       }
     }
 
+    final String modeName = getModeName(debuggingOptions.buildMode);
+    if (mainPath == null) {
+      assert(prebuiltMode);
+      printStatus('Launching ${package.displayName} on ${device.name} in $modeName mode...');
+    } else {
+      printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
+    }
+
     package = getApplicationPackageForPlatform(device.targetPlatform, applicationBinary: applicationBinary);
 
     if (package == null) {
@@ -72,14 +80,6 @@
 
     await startEchoingDeviceLog(package);
 
-    final String modeName = getModeName(debuggingOptions.buildMode);
-    if (mainPath == null) {
-      assert(prebuiltMode);
-      printStatus('Launching ${package.displayName} on ${device.name} in $modeName mode...');
-    } else {
-      printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
-    }
-
     _result = await device.startApp(
       package,
       debuggingOptions.buildMode,
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index be25e64..823ff7e 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -174,6 +174,9 @@
       return 1;
     }
 
+    final String modeName = getModeName(debuggingOptions.buildMode);
+    printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
+
     package = getApplicationPackageForPlatform(device.targetPlatform, applicationBinary: applicationBinary);
 
     if (package == null) {
@@ -195,9 +198,6 @@
 
     await startEchoingDeviceLog(package);
 
-    final String modeName = getModeName(debuggingOptions.buildMode);
-    printStatus('Launching ${getDisplayPath(mainPath)} on ${device.name} in $modeName mode...');
-
     // Start the application.
     final Future<LaunchResult> futureResult = device.startApp(
       package,