Simplify coverage workflow (#4725)

We now download the base coverage file automatically.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5163907..b18ab6f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -150,11 +150,14 @@
 You can download our current coverage data from cloud storage and visualize it
 in Atom as follows:
 
- * `mkdir packages/flutter/coverage`
- * Download the latest `lcov.info` file produced by Travis using
-   `curl https://storage.googleapis.com/flutter_infra/flutter/coverage/lcov.info -o packages/flutter/coverage/lcov.info`
  * Install the [lcov-info](https://atom.io/packages/lcov-info) package for Atom.
- * Open a file in `packages/flutter/lib` in Atom and type `Ctrl+Alt+C`.
+ * Open the `packages/flutter` folder in Atom.
+ * Open a Dart file in the `lib` directory an type `Ctrl+Alt+C` to bring up the
+   coverage data.
+
+If you don't see any coverage data, check that you have an `lcov.info` file in
+the `packages/flutter/coverage` directory. It should have been downloaded by the
+`flutter update-packages` command you ran previously.
 
 See [issue 4719](https://github.com/flutter/flutter/issues/4719) for ideas about
 how to improve this workflow.
diff --git a/packages/flutter_tools/lib/src/base/net.dart b/packages/flutter_tools/lib/src/base/net.dart
new file mode 100644
index 0000000..6eb69b6
--- /dev/null
+++ b/packages/flutter_tools/lib/src/base/net.dart
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium 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 'dart:io';
+
+import '../globals.dart';
+
+/// Download a file from the given URL and return the bytes.
+Future<List<int>> fetchUrl(Uri url) async {
+  printTrace('Downloading $url.');
+
+  HttpClient httpClient = new HttpClient();
+  HttpClientRequest request = await httpClient.getUrl(url);
+  HttpClientResponse response = await request.close();
+
+  printTrace('Received response statusCode=${response.statusCode}');
+  if (response.statusCode != 200)
+    throw new Exception(response.reasonPhrase);
+
+  BytesBuilder responseBody = new BytesBuilder(copy: false);
+  await for (List<int> chunk in response)
+    responseBody.add(chunk);
+
+  return responseBody.takeBytes();
+}
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index 08928b8..8d4274f 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -9,6 +9,7 @@
 
 import 'base/context.dart';
 import 'base/logger.dart';
+import 'base/net.dart';
 import 'base/os.dart';
 import 'globals.dart';
 
@@ -167,25 +168,6 @@
       await engine.download();
   }
 
-  /// Download a file from the given URL and return the bytes.
-  static Future<List<int>> _downloadFile(Uri url) async {
-    printTrace('Downloading $url.');
-
-    HttpClient httpClient = new HttpClient();
-    HttpClientRequest request = await httpClient.getUrl(url);
-    HttpClientResponse response = await request.close();
-
-    printTrace('Received response statusCode=${response.statusCode}');
-    if (response.statusCode != 200)
-      throw new Exception(response.reasonPhrase);
-
-    BytesBuilder responseBody = new BytesBuilder(copy: false);
-    await for (List<int> chunk in response)
-      responseBody.add(chunk);
-
-    return responseBody.takeBytes();
-  }
-
   /// Download a file from the given url and write it to the cache.
   /// If [unzip] is true, treat the url as a zip file, and unzip it to the
   /// directory given.
@@ -193,7 +175,7 @@
     if (!location.parent.existsSync())
       location.parent.createSync(recursive: true);
 
-    List<int> fileBytes = await _downloadFile(url);
+    List<int> fileBytes = await fetchUrl(url);
     if (unzip) {
       if (location is Directory && !location.existsSync())
         location.createSync(recursive: true);
diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart
index 6a03f50..3401b2b 100644
--- a/packages/flutter_tools/lib/src/commands/update_packages.dart
+++ b/packages/flutter_tools/lib/src/commands/update_packages.dart
@@ -5,6 +5,11 @@
 import 'dart:async';
 import 'dart:io';
 
+import 'package:path/path.dart' as path;
+
+import '../base/logger.dart';
+import '../base/net.dart';
+import '../cache.dart';
 import '../dart/pub.dart';
 import '../globals.dart';
 import '../runner/flutter_command.dart';
@@ -30,12 +35,25 @@
   @override
   bool get requiresProjectRoot => false;
 
+  Future<Null> _downloadCoverageData() async {
+    Status status = logger.startProgress("Downloading lcov data for package:flutter...");
+    final List<int> data = await fetchUrl(Uri.parse('https://storage.googleapis.com/flutter_infra/flutter/coverage/lcov.info'));
+    final String coverageDir = path.join(Cache.flutterRoot, 'packages/flutter/coverage');
+    new File(path.join(coverageDir, 'lcov.base.info'))
+      ..createSync(recursive: true)
+      ..writeAsBytesSync(data, flush: true);
+    new File(path.join(coverageDir, 'lcov.info'))
+      ..createSync(recursive: true)
+      ..writeAsBytesSync(data, flush: true);
+    status.stop(showElapsedTime: true);
+  }
+
   @override
   Future<int> runInProject() async {
     try {
-      Stopwatch timer = new Stopwatch()..start();
+      final Stopwatch timer = new Stopwatch()..start();
       int count = 0;
-      bool upgrade = argResults['upgrade'];
+      final bool upgrade = argResults['upgrade'];
 
       for (Directory dir in runner.getRepoPackages()) {
         int code = await pubGet(directory: dir.path, upgrade: upgrade, checkLastModified: false);
@@ -44,9 +62,10 @@
         count++;
       }
 
-      double seconds = timer.elapsedMilliseconds / 1000.0;
-      printStatus('\nRan \'pub\' $count time${count == 1 ? "" : "s"} in ${seconds.toStringAsFixed(1)}s.');
+      await _downloadCoverageData();
 
+      final double seconds = timer.elapsedMilliseconds / 1000.0;
+      printStatus('\nRan \'pub\' $count time${count == 1 ? "" : "s"} and fetched coverage data in ${seconds.toStringAsFixed(1)}s.');
       return 0;
     } on int catch (code) {
       return code;