Hot reload UI polish (#5193)

* General improvoments to the loader app:
   * Show a message after 8 seconds if no connection comes in.
   * Show a progress bar as files are being uploaded.
   * Hide the spinner just before launching the application.

* General improvements to the "flutter run" UI:
   * Add "?" key as a silent alias for "h".
   * Make the help text bold so it doesn't get mixed with the logs.
   * Make "R" do a cold restart when hot reload is enabled.

* Supporting features and bug fixes:
   * Add support for string service extensions.

* Other bug fixes:
   * Expose debugDumpRenderTree() outside debug mode.
   * Logger.supportsColor was missing a getter.
   * Mention in the usage docs that --hot requires --resident.
   * Trivial style fixes.
diff --git a/packages/flutter_tools/lib/src/observatory.dart b/packages/flutter_tools/lib/src/observatory.dart
index c04c9b9..b7d3337 100644
--- a/packages/flutter_tools/lib/src/observatory.dart
+++ b/packages/flutter_tools/lib/src/observatory.dart
@@ -9,6 +9,8 @@
 import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:web_socket_channel/io.dart';
 
+import 'globals.dart';
+
 // TODO(johnmccutchan): Rename this class to ServiceProtocol or VmService.
 class Observatory {
   Observatory._(this.peer, this.port) {
@@ -204,6 +206,38 @@
     }).then((dynamic result) => new Response(result));
   }
 
+  // Loader page extension methods.
+
+  Future<Response> flutterLoaderShowMessage(String isolateId, String message) {
+    return peer.sendRequest('ext.flutter.loaderShowMessage', <String, dynamic>{
+      'isolateId': isolateId,
+      'value': message
+    }).then(
+      (dynamic result) => new Response(result),
+      onError: (dynamic exception) { printTrace('ext.flutter.loaderShowMessage: $exception'); }
+    );
+  }
+
+  Future<Response> flutterLoaderSetProgress(String isolateId, double progress) {
+    return peer.sendRequest('ext.flutter.loaderSetProgress', <String, dynamic>{
+      'isolateId': isolateId,
+      'loaderSetProgress': progress
+    }).then(
+      (dynamic result) => new Response(result),
+      onError: (dynamic exception) { printTrace('ext.flutter.loaderSetProgress: $exception'); }
+    );
+  }
+
+  Future<Response> flutterLoaderSetProgressMax(String isolateId, double max) {
+    return peer.sendRequest('ext.flutter.loaderSetProgressMax', <String, dynamic>{
+      'isolateId': isolateId,
+      'loaderSetProgressMax': max
+    }).then(
+      (dynamic result) => new Response(result),
+      onError: (dynamic exception) { printTrace('ext.flutter.loaderSetProgressMax: $exception'); }
+    );
+  }
+
   /// Causes the application to pick up any changed code.
   Future<Response> flutterReassemble(String isolateId) {
     return peer.sendRequest('ext.flutter.reassemble', <String, dynamic>{